[
  {
    "path": ".dockerignore",
    "content": "# artifacts\n/nerdctl\n_output\n*.gomodjail\n\n# golangci-lint\n/build\n\n# vagrant\n/.vagrant\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: Bug report\ndescription: Create a bug report to help improve nerdctl\nlabels: kind/unconfirmed-bug-claim\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        If you are reporting a new issue, make sure that we do not have any duplicates\n        already open. You can ensure this by searching the issue list for this\n        repository. If there is a duplicate, please close your issue and add a comment\n        to the existing issue instead.\n\n        Please also see [the FAQs and Troubleshooting](https://github.com/containerd/nerdctl/blob/main/docs/faq.md).\n\n  - type: textarea\n    attributes:\n      label: Description\n      description: |\n        Briefly describe the problem you are having in a few paragraphs.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Steps to reproduce the issue\n      value: |\n        1.\n        2.\n        3.\n\n  - type: textarea\n    attributes:\n      label: Describe the results you received and expected\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: What version of nerdctl are you using?\n      placeholder: nerdctl version\n    validations:\n      required: true\n\n  - type: dropdown\n    attributes:\n      label: Are you using a variant of nerdctl? (e.g., Rancher Desktop)\n      options:\n        - Rancher Desktop for Windows\n        - Rancher Desktop for macOS\n        - Lima\n        - Colima\n        - Others\n\n  - type: textarea\n    attributes:\n      label: Host information\n      placeholder: nerdctl info\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Ask a question (GitHub Discussions)\n    url: https://github.com/containerd/nerdctl/discussions\n    about: |\n      Please do not submit \"a bug report\" for asking a question.\n      In most cases, GitHub Discussions is the best place to ask a question.\n      If you are not sure whether you are going to report a bug or ask a question,\n      please consider asking in GitHub Discussions first.\n  - name: Chat with containerd/nerdctl users and developers\n    url: https://slack.cncf.io/\n    about: CNCF slack has `#containerd` and `#containerd-dev` channels\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "content": "name: Feature request\ndescription: Suggest an idea for nerdctl\nlabels: kind/feature\nbody:\n  - type: textarea\n    attributes:\n      label: What is the problem you're trying to solve\n      description: |\n        A clear and concise description of what the problem is.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Describe the solution you'd like\n      description: |\n        A clear and concise description of what you'd like to happen.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Additional context\n      description: |\n        Add any other context about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# -----------------------------------------------------------------------------\n# Forked from https://raw.githubusercontent.com/opencontainers/runc/2888e6e54339e52ae45710daa9e47cdb2e1926f9/.github/dependabot.yml\n# Copyright The runc Authors.\n# Licensed under the Apache License, Version 2.0\n# -----------------------------------------------------------------------------\n\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  # Dependencies listed in go.mod\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"  # Location of package manifests\n    schedule:\n      interval: \"daily\"\n    groups:\n      golang-x:\n        patterns:\n          - \"golang.org/x/*\"\n      moby-sys:\n        patterns:\n          - \"github.com/moby/sys/*\"\n      docker:\n        patterns:\n          - \"github.com/docker/docker\"\n          - \"github.com/docker/cli\"\n      containerd:\n        patterns:\n          - \"github.com/containerd/containerd\"\n          - \"github.com/containerd/containerd/api\"\n      stargz:\n        patterns:\n          - \"github.com/containerd/stargz-snapshotter\"\n          - \"github.com/containerd/stargz-snapshotter/estargz\"\n          - \"github.com/containerd/stargz-snapshotter/ipfs\"\n\n  # Dependencies listed in .github/workflows/*.yml\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n\n  # Dependencies listed in Dockerfile\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/ghcr-image-build-and-publish.yml",
    "content": "name: image\n\n# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n\non:\n  push:\n    branches: [main]\n    # Publish semver tags as releases.\n    tags: ['v*.*.*']\n  pull_request:\n    branches: [main]\n    paths-ignore:\n      - '**.md'\n\nenv:\n  # Use docker.io for Docker Hub if empty\n  REGISTRY: ghcr.io\n  # github.repository as <account>/<repo>\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build:\n\n    runs-on: ubuntu-24.04\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n\n      # FIXME: setup-qemu-action is depended by `gomodjail pack`\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a  # v4.0.0\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd  # v4.0.0\n\n      # Login against a Docker registry except on PR\n      # https://github.com/docker/login-action\n      - name: Log into registry ${{ env.REGISTRY }}\n        if: github.event_name != 'pull_request'\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2  # v4.0.0\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      # Extract metadata (tags, labels) for Docker\n      # https://github.com/docker/metadata-action\n      - name: Extract Docker metadata\n        id: meta\n        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf  # v6.0.0\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n\n      # Build and push Docker image with Buildx (don't push on PR)\n      # https://github.com/docker/build-push-action\n      - name: Build and push Docker image\n        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294  # v7.0.0\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          secrets: |\n            github_token=${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/job-build.yml",
    "content": "# This job just builds nerdctl for the golang versions we support (as a smoke test)\nname: job-build\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      go-version:\n        required: true\n        type: string\n      runner:\n        required: true\n        type: string\n      canary:\n        required: false\n        default: false\n        type: boolean\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  build-all-targets:\n    name: ${{ format('go {0}', inputs.canary && 'canary' || inputs.go-version ) }}\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    defaults:\n      run:\n        shell: bash\n\n    env:\n      GO_VERSION: ${{ inputs.go-version }}\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - if: ${{ inputs.canary }}\n        name: \"Init (canary): retrieve GO_VERSION\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          . ./hack/github/action-helpers.sh\n          latest_go=\"$(. ./hack/provisioning/version/fetch.sh; go::canary::for::go-setup)\"\n          printf \"GO_VERSION=%s\\n\" \"$latest_go\" >> \"$GITHUB_ENV\"\n          [ \"$latest_go\" != \"\" ] || \\\n            github::log::warning \"No canary go\" \"There is currently no canary go version to test. Steps will not run.\"\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Init: install go\"\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          check-latest: true\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Run: make binaries\"\n        run: |\n          . ./hack/github/action-helpers.sh\n\n          github::md::table::header \"OS\" \"Arch\" \"Result\" \"Time\" >> $GITHUB_STEP_SUMMARY\n\n          failure=\n\n          build(){\n            local goos=\"$1\"\n            local goarch=\"${2:-amd64}\"\n            local goarm=\"${3:-}\"\n            local result\n\n            GOOS=\"$goos\" GOARCH=\"$goarch\" GOARM=\"$goarm\" go build ./examples/...\n\n            github::timer::begin\n\n            GOOS=\"$goos\" GOARCH=\"$goarch\" GOARM=\"$goarm\" make binaries \\\n              && result=\"$decorator_success\" \\\n              || {\n                failure=true\n                result=\"$decorator_failure\"\n              }\n\n            [ ! \"$goarm\" ] || goarch=\"$goarch/v$goarm\"\n            github::md::table::line \"$goos\" \"$goarch\" \"$result\" \"$(github::timer::format <(github::timer::tick))\" >> $GITHUB_STEP_SUMMARY\n          }\n\n          # We officially support these\n          build linux\n          build linux arm64\n          build windows\n          build freebsd\n          # These architectures are not released, but we still verify that we can at least compile\n          build darwin\n          build linux arm 6\n          build linux loong64\n          build linux ppc64le\n          build linux riscv64\n          build linux s390x\n\n          [ ! \"$failure\" ] || exit 1\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Run: make binaries with custom BUILDTAGS\"\n        run: |\n          set -eux\n          # no_ipfs: make sure it does not incur any IPFS-related dependency\n          go mod vendor\n          rm -rf vendor/github.com/ipfs vendor/github.com/multiformats\n          BUILDTAGS=no_ipfs make binaries\n"
  },
  {
    "path": ".github/workflows/job-lint-go.yml",
    "content": "# This job runs golangci-lint\n# Note that technically, `make lint-go-all` would run the linter for all targets, and could be called once, on a single instance.\n# The point of running it on a matrix instead, each GOOS separately, is to verify that the tooling itself is working on the target OS.\nname: job-lint-go\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      go-version:\n        required: true\n        type: string\n      runner:\n        required: true\n        type: string\n      canary:\n        required: false\n        default: false\n        type: boolean\n      goos:\n        required: true\n        type: string\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  lint-go:\n    name: ${{ format('{0}{1}', inputs.goos, inputs.canary && ' (go canary)' || '') }}\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    defaults:\n      run:\n        shell: bash\n    env:\n      GO_VERSION: ${{ inputs.go-version }}\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - if: ${{ inputs.canary }}\n        name: \"Init (canary): retrieve GO_VERSION\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          latest_go=\"$(. ./hack/provisioning/version/fetch.sh; go::canary::for::go-setup)\"\n          printf \"GO_VERSION=%s\\n\" \"$latest_go\" >> \"$GITHUB_ENV\"\n          [ \"$latest_go\" != \"\" ] || \\\n            echo \"::warning title=No canary go::There is currently no canary go version to test. Steps will not run.\"\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Init: install go\"\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          check-latest: true\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Init: install dev-tools\"\n        run: |\n          echo \"::group:: make install-dev-tools\"\n          make install-dev-tools\n          echo \"::endgroup::\"\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Run\"\n        run: |\n          # On canary, lint for all supported targets\n          if [ \"${{ inputs.canary }}\" == \"true\" ]; then\n            NO_COLOR=true make lint-go-all\n          else\n            NO_COLOR=true GOOS=\"${{ inputs.goos }}\" make lint-go\n          fi\n"
  },
  {
    "path": ".github/workflows/job-lint-other.yml",
    "content": "# This job runs any subsidiary linter not part of golangci (shell, yaml, etc)\nname: job-lint-other\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      runner:\n        required: true\n        type: string\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  lint-other:\n    name: \"yaml | shell\"\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: ${{ inputs.runner }}\n    defaults:\n      run:\n        shell: bash\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - name: \"Run: yaml\"\n        run: |\n          make lint-yaml\n\n      - name: \"Run: shell\"\n        run: |\n          make lint-shell\n"
  },
  {
    "path": ".github/workflows/job-lint-project.yml",
    "content": "# This job runs containerd shared project-checks, that verifies licenses, headers, and commits.\n# To run locally, you may just use `make lint` instead, that does the same thing\n# (albeit `make lint` uses more modern versions).\nname: job-lint-project\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      go-version:\n        required: true\n        type: string\n      runner:\n        required: true\n        type: string\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  project:\n    name: \"commits, licenses...\"\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: ${{ inputs.runner }}\n    defaults:\n      run:\n        shell: bash\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 100\n          path: src/github.com/containerd/nerdctl\n\n      - name: \"Init: install go\"\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n        with:\n          go-version: ${{ inputs.go-version }}\n          check-latest: true\n          cache-dependency-path: src/github.com/containerd/nerdctl\n\n      - name: \"Run\"\n        uses: containerd/project-checks@d7751f3c375b8fe4a84c02a068184ee4c1f59bc4  # v1.2.2\n        with:\n          working-directory: src/github.com/containerd/nerdctl\n          repo-access-token: ${{ secrets.GITHUB_TOKEN }}\n          # go-licenses-ignore is set because go-licenses cannot detect the license of the following package:\n          # * go-base36: Apache-2.0 OR MIT (https://github.com/multiformats/go-base36/blob/master/LICENSE.md)\n          # * filepath-securejoin: MPL-2.0 AND BSD-3-Clause, exceptionally approved by CNCF\n          #   (https://github.com/cncf/foundation/issues/1154#issuecomment-3562385979)\n          #\n          # The list of the CNCF-approved licenses can be found here:\n          # https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md\n          go-licenses-ignore: |\n            github.com/multiformats/go-base36\n            github.com/cyphar/filepath-securejoin\n"
  },
  {
    "path": ".github/workflows/job-test-dependencies.yml",
    "content": "# This job pre-heats the cache for the test image by building all dependencies\nname: job-test-dependencies\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      runner:\n        required: true\n        type: string\n      containerd-version:\n        required: false\n        default: ''\n        type: string\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  # This job builds the dependency target of the test docker image for all supported architectures and cache it in GHA\n  build-dependencies:\n    # Note: for whatever reason, you cannot access env.RUNNER_ARCH here\n    name: \"${{ contains(inputs.runner, 'arm') && 'arm64' || 'amd64' }}${{ inputs.containerd-version && format(' | {0}', inputs.containerd-version) || ''}}\"\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    defaults:\n      run:\n        shell: bash\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - name: \"Init: expose GitHub Runtime variables for gha\"\n        uses: crazy-max/ghaction-github-runtime@04d248b84655b509d8c44dc1d6f990c879747487  # v4.0.0\n\n      - name: \"Run: build dependencies for the integration test environment image\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          # Cache is sharded per-architecture\n          arch=${{ env.RUNNER_ARCH == 'ARM64' && 'arm64' || 'amd64' }}\n          docker buildx create --name with-gha --use\n          # Honor old containerd if requested\n          args=()\n          if [ \"${{ inputs.containerd-version }}\" != \"\" ]; then\n            args=(--build-arg CONTAINERD_VERSION=${{ inputs.containerd-version }})\n          fi\n          docker buildx build \\\n            --secret id=github_token,env=GITHUB_TOKEN \\\n            --cache-to type=gha,compression=zstd,mode=max,scope=test-integration-dependencies-\"$arch\" \\\n            --cache-from type=gha,scope=test-integration-dependencies-\"$arch\" \\\n            --target build-dependencies \"${args[@]}\" .\n"
  },
  {
    "path": ".github/workflows/job-test-in-container.yml",
    "content": "# This job runs integration tests inside a container, for all supported variants (ipv6, canary, etc)\n# Note that it is linux and nerdctl (+/- gomodjail) only.\nname: job-test-in-container\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      runner:\n        required: true\n        type: string\n      canary:\n        required: false\n        default: false\n        type: boolean\n      target:\n        required: false\n        default: ''\n        type: string\n      binary:\n        required: false\n        default: nerdctl\n        type: string\n      containerd-version:\n        required: false\n        default: ''\n        type: string\n      rootlesskit-version:\n        required: false\n        default: ''\n        type: string\n      ipv6:\n        required: false\n        default: false\n        type: boolean\n      skip-flaky:\n        required: false\n        default: false\n        type: boolean\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  test:\n    name: |\n      ${{ inputs.binary != 'nerdctl' && format('{0} < ', inputs.binary) || '' }}\n      ${{ inputs.target }}\n      ${{ contains(inputs.runner, 'arm') && '(arm)' || '' }}\n      ${{ contains(inputs.runner, '22.04') && '(old ubuntu)' || '' }}\n      ${{ inputs.ipv6 && ' (ipv6)' || '' }}\n      ${{ inputs.canary && ' (canary)' || '' }}\n      ${{ inputs.containerd-version && format(' (ctd: {0})', inputs.containerd-version) || '' }}\n      ${{ inputs.rootlesskit-version && format(' (rlk: {0})', inputs.rootlesskit-version) || '' }}\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: ${{ inputs.runner }}\n    defaults:\n      run:\n        shell: bash\n\n    env:\n      # https://github.com/containerd/nerdctl/issues/622\n      # The only case when rootlesskit-version is force-specified is when we downgrade explicitly to v1\n      WORKAROUND_ISSUE_622: ${{ inputs.rootlesskit-version }}\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - name: \"Init: expose GitHub Runtime variables for gha\"\n        uses: crazy-max/ghaction-github-runtime@04d248b84655b509d8c44dc1d6f990c879747487  # v4.0.0\n\n      - name: \"Init: install br-netfilter\"\n        run: |\n          # This ensures that bridged traffic goes through netfilter\n          sudo modprobe br-netfilter\n      - name: \"Init: register QEMU (tonistiigi/binfmt)\"\n        run: |\n          # `--install all` will only install emulation for architectures that cannot be natively executed\n          # Since some arm64 platforms do provide native fallback execution for 32 bits,\n          # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`.\n          # To avoid that, we explicitly list the architectures we do want emulation for.\n          docker run --privileged --rm tonistiigi/binfmt --install linux/amd64\n          docker run --privileged --rm tonistiigi/binfmt --install linux/arm64\n          docker run --privileged --rm tonistiigi/binfmt --install linux/arm/v7\n      - if: ${{ inputs.canary }}\n        name: \"Init (canary): prepare updated test image\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          . ./hack/build-integration-canary.sh\n          canary::build::integration\n      - if: ${{ ! inputs.canary }}\n        name: \"Init: prepare test image\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          buildargs=()\n          # If the runner is old, use old ubuntu inside the container as well\n          [ \"${{ contains(inputs.runner, '22.04') }}\" != \"true\" ] || buildargs=(--build-arg UBUNTU_VERSION=22.04)\n          # Honor if we want old containerd\n          [ \"${{ inputs.containerd-version }}\" == \"\" ] || buildargs+=(--build-arg CONTAINERD_VERSION=${{ inputs.containerd-version }})\n          # Honor custom targets and if we want old rootlesskit\n          target=test-integration\n          if [ \"${{ inputs.target }}\" != \"rootful\" ]; then\n            target+=-${{ inputs.target }}\n            if [ \"${{ inputs.rootlesskit-version }}\" != \"\" ]; then\n              buildargs+=(--build-arg ROOTLESSKIT_VERSION=${{ inputs.rootlesskit-version }})\n            fi\n          fi\n          # Cache is sharded per-architecture\n          arch=${{ env.RUNNER_ARCH == 'ARM64' && 'arm64' || 'amd64' }}\n          docker buildx create --name with-gha --use\n          docker buildx build \\\n            --secret id=github_token,env=GITHUB_TOKEN \\\n            --output=type=docker \\\n            --cache-from type=gha,scope=test-integration-dependencies-\"$arch\" \\\n            -t \"$target\" --target \"$target\" \\\n            \"${buildargs[@]}\" \\\n            .\n      # Rootful needs to disable snap\n      - if: ${{ inputs.target == 'rootful' }}\n        name: \"Init: remove snap loopback devices (conflicts with our loopback devices in TestRunDevice)\"\n        run: |\n          sudo systemctl disable --now snapd.service snapd.socket\n          sudo apt-get purge -qq snapd\n          sudo losetup -Dv\n          sudo losetup -lv\n      # Rootless on modern ubuntu wants apparmor\n      - if: ${{ inputs.target != 'rootful' && ! contains(inputs.runner, '22.04') }}\n        name: \"Init: prepare apparmor for rootless + ubuntu 24+\"\n        run: |\n          cat <<EOT | sudo tee \"/etc/apparmor.d/usr.local.bin.rootlesskit\"\n          abi <abi/4.0>,\n          include <tunables/global>\n          /usr/local/bin/rootlesskit flags=(unconfined) {\n            userns,\n            # Site-specific additions and overrides. See local/README for details.\n            include if exists <local/usr.local.bin.rootlesskit>\n          }\n          EOT\n          sudo systemctl restart apparmor.service\n      # ipv6 wants... ipv6\n      - if: ${{ inputs.ipv6 }}\n        name: \"Init: ipv6\"\n        run: |\n          # Enable ipv4 and ipv6 forwarding\n          sudo sysctl -w net.ipv6.conf.all.forwarding=1\n          sudo sysctl -w net.ipv4.ip_forward=1\n          # Enable IPv6 for Docker, and configure docker to use containerd for gha\n          sudo mkdir -p /etc/docker\n          echo '{\"ipv6\": true, \"fixed-cidr-v6\": \"2001:db8:1::/64\", \"ip6tables\": true}' | sudo tee /etc/docker/daemon.json\n      - name: \"Init: enable Docker experimental features\"\n        run: |\n          sudo mkdir -p /etc/docker\n          if [ -f /etc/docker/daemon.json ]; then\n            tmpfile=\"$(sudo mktemp)\"\n            sudo jq '.experimental = true' /etc/docker/daemon.json | sudo tee \"$tmpfile\" >/dev/null\n            sudo mv \"$tmpfile\" /etc/docker/daemon.json\n          else\n            echo '{\"experimental\": true}' | sudo tee /etc/docker/daemon.json >/dev/null\n          fi\n          sudo systemctl restart docker\n      - name: \"Run: integration tests\"\n        run: |\n          . ./hack/github/action-helpers.sh\n          github::md::h2 \"non-flaky\" >> \"$GITHUB_STEP_SUMMARY\"\n\n          # IPV6 note: nested IPv6 network inside docker and qemu is complex and needs a bunch of sysctl config.\n          # Therefore, it's hard to debug why the IPv6 tests fail in such an isolation layer.\n          # On the other side, using the host network is easier at configuration.\n          # Besides, each job is running on a different instance, which means using host network here\n          # is safe and has no side effects on others.\n          [ \"${{ inputs.target }}\" == \"rootful\" ] \\\n            && args=(test-integration ./hack/test-integration.sh -test.allow-modify-users=true) \\\n            || args=(test-integration-${{ inputs.target }} /test-integration-rootless.sh ./hack/test-integration.sh)\n          if [ \"${{ inputs.ipv6 }}\" == true ]; then\n            docker run --network host -t --rm --privileged -e GITHUB_STEP_SUMMARY=\"$GITHUB_STEP_SUMMARY\" -v \"$GITHUB_STEP_SUMMARY\":\"$GITHUB_STEP_SUMMARY\" -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622:-} \"${args[@]}\" -test.only-flaky=false -test.only-ipv6 -test.target=${{ inputs.binary }}\n          else\n            docker run -t --rm --privileged -e GITHUB_STEP_SUMMARY=\"$GITHUB_STEP_SUMMARY\" -v \"$GITHUB_STEP_SUMMARY\":\"$GITHUB_STEP_SUMMARY\" -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622:-} \"${args[@]}\" -test.only-flaky=false -test.target=${{ inputs.binary }}\n          fi\n      # FIXME: this NEEDS to go away\n      - name: \"Run: integration tests (flaky)\"\n        if: ${{ !fromJSON(inputs.skip-flaky) }}\n        run: |\n          . ./hack/github/action-helpers.sh\n          github::md::h2 \"flaky\" >> \"$GITHUB_STEP_SUMMARY\"\n\n          [ \"${{ inputs.target }}\" == \"rootful\" ] \\\n            && args=(test-integration ./hack/test-integration.sh) \\\n            || args=(test-integration-${{ inputs.target }} /test-integration-rootless.sh ./hack/test-integration.sh)\n          if [ \"${{ inputs.ipv6 }}\" == true ]; then\n            docker run --network host -t --rm --privileged -e GITHUB_STEP_SUMMARY=\"$GITHUB_STEP_SUMMARY\" -v \"$GITHUB_STEP_SUMMARY\":\"$GITHUB_STEP_SUMMARY\" -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622:-} \"${args[@]}\" -test.only-flaky=true -test.only-ipv6 -test.target=${{ inputs.binary }}\n          else\n            docker run -t --rm --privileged -e GITHUB_STEP_SUMMARY=\"$GITHUB_STEP_SUMMARY\" -v \"$GITHUB_STEP_SUMMARY\":\"$GITHUB_STEP_SUMMARY\" -e WORKAROUND_ISSUE_622=${WORKAROUND_ISSUE_622:-} \"${args[@]}\" -test.only-flaky=true -test.target=${{ inputs.binary }}\n          fi\n"
  },
  {
    "path": ".github/workflows/job-test-in-host.yml",
    "content": "# This currently test docker and nerdctl on windows (w/o canary)\n# Structure is in to allow testing nerdctl on linux as well, though more work is required to make it functional.\nname: job-test-in-host\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      runner:\n        required: true\n        type: string\n      canary:\n        required: false\n        default: false\n        type: boolean\n      binary:\n        required: false\n        default: nerdctl\n        type: string\n      go-version:\n        required: true\n        type: string\n      docker-version:\n        required: true\n        type: string\n      containerd-version:\n        required: true\n        type: string\n      containerd-sha:\n        required: true\n        type: string\n      containerd-service-sha:\n        required: true\n        type: string\n      windows-cni-version:\n        required: true\n        type: string\n      linux-cni-version:\n        required: true\n        type: string\n      linux-cni-sha:\n        required: true\n        type: string\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  test:\n    name: |\n      ${{ inputs.binary != 'nerdctl' && format('{0} < ', inputs.binary) || '' }}\n      ${{ contains(inputs.runner, 'ubuntu') && ' linux' || ' windows' }}\n      ${{ contains(inputs.runner, 'arm') && '(arm)' || '' }}\n      ${{ contains(inputs.runner, '22.04') && '(old ubuntu)' || '' }}\n      ${{ inputs.canary && ' (canary)' || '' }}\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    defaults:\n      run:\n        shell: bash\n\n    env:\n      SHOULD_RUN: \"yes\"\n      GO_VERSION: ${{ inputs.go-version }}\n      # Both Docker and nerdctl on linux need rootful right now\n      WITH_SUDO: ${{ contains(inputs.runner, 'ubuntu') }}\n      CONTAINERD_VERSION: ${{ inputs.containerd-version }}\n      CONTAINERD_SHA: ${{ inputs.containerd-sha }}\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - if: ${{ inputs.canary }}\n        name: \"Init (canary): retrieve latest go and containerd\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          latest_go=\"$(. ./hack/provisioning/version/fetch.sh; go::canary::for::go-setup)\"\n          latest_containerd=\"$(. ./hack/provisioning/version/fetch.sh; github::project::latest \"containerd/containerd\")\"\n\n          [ \"$latest_go\" == \"\" ] || \\\n            printf \"GO_VERSION=%s\\n\" \"$latest_go\" >> \"$GITHUB_ENV\"\n          [ \"${latest_containerd:1}\" == \"$CONTAINERD_VERSION\" ] || {\n            printf \"CONTAINERD_VERSION=%s\\n\" \"${latest_containerd:1}\" >> \"$GITHUB_ENV\"\n            printf \"CONTAINERD_SHA=canary is volatile and I accept the risk\\n\" >> \"$GITHUB_ENV\"\n          }\n          if [ \"$latest_go\" == \"\" ] && [ \"${latest_containerd:1}\" == \"$CONTAINERD_VERSION\" ]; then\n            echo \"::warning title=No canary::There is currently no canary versions to test. Steps will not run.\";\n            printf \"SHOULD_RUN=no\\n\" >> \"$GITHUB_ENV\"\n          fi\n\n      - if: ${{ env.SHOULD_RUN == 'yes' }}\n        name: \"Init: install go\"\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          check-latest: true\n\n      # XXX RUNNER_OS and generally env is too unreliable\n      # - if: ${{ env.RUNNER_OS == 'Linux' }}\n      - if: ${{ contains(inputs.runner, 'ubuntu') && env.SHOULD_RUN == 'yes' }}\n        name: \"Init (linux): prepare host\"\n        run: |\n          if [ \"${{ contains(inputs.binary, 'docker') }}\" == true ]; then\n            echo \"::group:: configure cdi and experimental for docker\"\n            sudo mkdir -p /etc/docker\n            sudo jq -n '.features.cdi = true | .experimental = true' | sudo tee /etc/docker/daemon.json\n            echo \"::endgroup::\"\n            echo \"::group:: downgrade docker to the specific version we want to test (${{ inputs.docker-version }})\"\n            sudo apt-get update -qq\n            sudo apt-get install -qq ca-certificates curl\n            sudo install -m 0755 -d /etc/apt/keyrings\n            sudo cp ./hack/provisioning/gpg/docker /etc/apt/keyrings/docker.asc\n            sudo chmod a+r /etc/apt/keyrings/docker.asc\n            echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \\\n              $(. /etc/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\") stable\" \\\n              | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null\n            sudo apt-get update -qq\n            sudo apt-get install -qq --allow-downgrades docker-ce=${{ inputs.docker-version }} docker-ce-cli=${{ inputs.docker-version }}\n            sudo systemctl restart docker\n            echo \"::endgroup::\"\n          else\n            # FIXME: this is missing runc (see top level workflow note about the state of this)\n            echo \"::group:: install dependencies\"\n            sudo ./hack/provisioning/linux/containerd.sh uninstall\n            ./hack/provisioning/linux/containerd.sh rootful \"$CONTAINERD_VERSION\" \"amd64\" \"$CONTAINERD_SHA\" \"${{ inputs.containerd-service-sha }}\"\n            sudo ./hack/provisioning/linux/cni.sh uninstall\n            ./hack/provisioning/linux/cni.sh install \"${{ inputs.linux-cni-version }}\" \"amd64\" \"${{ inputs.linux-cni-sha }}\"\n            echo \"::endgroup::\"\n\n            echo \"::group:: build nerctl\"\n            go install ./cmd/nerdctl\n            echo \"$HOME/go/bin\" >> \"$GITHUB_PATH\"\n            # Since tests are going to run root, we need nerdctl to be in a PATH that will survive `sudo`\n            sudo cp \"$(which nerdctl)\" /usr/local/bin\n            echo \"::endgroup::\"\n          fi\n\n          # Register QEMU (tonistiigi/binfmt)\n          # `--install all` will only install emulation for architectures that cannot be natively executed\n          # Since some arm64 platforms do provide native fallback execution for 32 bits,\n          # armv7 emulation may or may not be installed, causing variance in the result of `uname -m`.\n          # To avoid that, we explicitly list the architectures we do want emulation for.\n          echo \"::group:: install binfmt\"\n          docker run --quiet --privileged --rm tonistiigi/binfmt --install linux/amd64\n          docker run --quiet --privileged --rm tonistiigi/binfmt --install linux/arm64\n          docker run --quiet --privileged --rm tonistiigi/binfmt --install linux/arm/v7\n          echo \"::endgroup::\"\n\n          # FIXME: remove expect when we are done removing unbuffer from tests\n          echo \"::group:: installing test dependencies\"\n          sudo add-apt-repository ppa:criu/ppa -y\n          sudo apt-get install -qq expect criu\n          echo \"::endgroup::\"\n\n          # This ensures that bridged traffic goes through netfilter\n          sudo modprobe br-netfilter\n\n      - if: ${{ contains(inputs.runner, 'windows') && env.SHOULD_RUN == 'yes' }}\n        name: \"Init (windows): prepare host\"\n        env:\n          ctrdVersion: ${{ env.CONTAINERD_VERSION }}\n        run: |\n          # Install WinCNI\n          echo \"::group:: install wincni\"\n          GOPATH=$(go env GOPATH) WINCNI_VERSION=${{ inputs.windows-cni-version }} ./hack/provisioning/windows/cni.sh\n          echo \"::endgroup::\"\n\n          # Install containerd\n          echo \"::group:: install containerd\"\n          powershell hack/provisioning/windows/containerd.ps1\n          echo \"::endgroup::\"\n\n          # Install nerdctl\n          echo \"::group:: build nerctl\"\n          go install ./cmd/nerdctl\n          echo \"::endgroup::\"\n\n          choco install jq\n\n      - if: ${{ env.SHOULD_RUN == 'yes' }}\n        name: \"Init: install dev tools\"\n        run: |\n          echo \"::group:: make install-dev-tools\"\n          make install-dev-tools\n          echo \"::endgroup::\"\n\n      # ipv6 is tested only on linux\n      - if: ${{ contains(inputs.runner, 'ubuntu') && env.SHOULD_RUN == 'yes' }}\n        name: \"Run (linux): integration tests (IPv6)\"\n        run: |\n          . ./hack/github/action-helpers.sh\n          github::md::h2 \"ipv6\" >> \"$GITHUB_STEP_SUMMARY\"\n\n          ./hack/test-integration.sh -test.target=${{ inputs.binary }} -test.only-ipv6\n\n      - if: ${{ env.SHOULD_RUN == 'yes' }}\n        name: \"Run: integration tests\"\n        run: |\n          . ./hack/github/action-helpers.sh\n          github::md::h2 \"non-flaky\" >> \"$GITHUB_STEP_SUMMARY\"\n\n          ./hack/test-integration.sh -test.target=${{ inputs.binary }} -test.only-flaky=false\n\n      # FIXME: this must go\n      - if: ${{ env.SHOULD_RUN == 'yes' }}\n        name: \"Run: integration tests (flaky)\"\n        run: |\n          . ./hack/github/action-helpers.sh\n          github::md::h2 \"flaky\" >> \"$GITHUB_STEP_SUMMARY\"\n\n          ./hack/test-integration.sh -test.target=${{ inputs.binary }} -test.only-flaky=true\n"
  },
  {
    "path": ".github/workflows/job-test-in-lima.yml",
    "content": "# Currently, Lima job test only for EL, though in the future it could be used to also test FreeBSD or other linux-es\nname: job-test-in-lima\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      runner:\n        required: true\n        type: string\n      target:\n        required: true\n        type: string\n      guest:\n        required: true\n        type: string\n      skip-flaky:\n        required: false\n        default: false\n        type: boolean\n\njobs:\n  test:\n    name: \"${{ inputs.guest }} ${{ inputs.target }}\"\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    env:\n      TARGET: ${{ inputs.target }}\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - name: \"Init: lima\"\n        uses: lima-vm/lima-actions/setup@55627e31b78637bf254a8b2a14da8ea7d12564e5  # v1.1.0\n        id: lima-actions-setup\n\n      - name: \"Init: Cache\"\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306  # v5.0.3\n        with:\n          path: ~/.cache/lima\n          key: lima-${{ steps.lima-actions-setup.outputs.version }}\n\n      - name: \"Init: start the guest VM\"\n        run: |\n          set -eux\n          # containerd=none is set because the built-in containerd support conflicts with Docker\n          limactl start \\\n            --name=default \\\n            --cpus=4 \\\n            --memory=12 \\\n            --containerd=none \\\n            --set '.mounts=null | .portForwards=[{\"guestSocket\":\"/var/run/docker.sock\",\"hostSocket\":\"{{.Dir}}/sock/docker.sock\"}]' \\\n            template://${{ inputs.guest }}\n\n      # FIXME: the tests should be directly executed in the VM without nesting Docker inside it\n      # https://github.com/containerd/nerdctl/issues/3858\n      - name: \"Init: install dockerd in the guest VM\"\n        run: |\n          set -eux\n          lima sudo mkdir -p /etc/systemd/system/docker.socket.d\n          cat <<-EOF | lima sudo tee /etc/systemd/system/docker.socket.d/override.conf\n          [Socket]\n          SocketUser=$(whoami)\n          EOF\n          lima sudo dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo\n          lima sudo dnf -q -y install docker-ce --nobest\n          lima sudo systemctl enable --now docker\n\n      - name: \"Init: configure the host to use dockerd in the guest VM\"\n        run: |\n          set -eux\n          sudo systemctl disable --now docker.service docker.socket\n          export DOCKER_HOST=\"unix://$(limactl ls --format '{{.Dir}}/sock/docker.sock' default)\"\n          echo \"DOCKER_HOST=${DOCKER_HOST}\" >>$GITHUB_ENV\n          docker info\n          docker version\n\n      - name: \"Init: install br-netfilter in the guest VM\"\n        run: |\n          lima sudo modprobe br-netfilter\n\n      - name: \"Init: expose GitHub Runtime variables for gha\"\n        uses: crazy-max/ghaction-github-runtime@04d248b84655b509d8c44dc1d6f990c879747487  # v4.0.0\n\n      - name: \"Init: prepare integration tests\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          set -eux\n\n          sudo losetup -Dv\n          sudo losetup -lv\n\n          [ \"$TARGET\" = \"rootless\" ] && TARGET=test-integration-rootless || TARGET=test-integration\n          docker buildx create --name with-gha --use\n          docker buildx build \\\n            --secret id=github_token,env=GITHUB_TOKEN \\\n            --output=type=docker \\\n            --cache-from type=gha,scope=test-integration-dependencies-amd64 \\\n            -t test-integration --target \"${TARGET}\" \\\n            .\n\n      - name: \"Run integration tests\"\n        # Presumably, something is broken with the way docker exposes /dev to the container, as it appears to only\n        # randomly work. Mounting /dev does workaround the issue.\n        # This might be due to the old kernel shipped with Alma (4.18), or something else between centos/docker.\n        run: |\n          set -eux\n          if [ \"$TARGET\" = \"rootless\" ]; then\n            echo \"rootless\"\n            docker run -t -v /dev:/dev --rm --privileged test-integration /test-integration-rootless.sh ./hack/test-integration.sh -test.only-flaky=false\n          else\n            echo \"rootful\"\n            docker run -t -v /dev:/dev --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=false\n          fi\n      - name: \"Run: integration tests (flaky)\"\n        if: ${{ !fromJSON(inputs.skip-flaky) }}\n        run: |\n          set -eux\n          if [ \"$TARGET\" = \"rootless\" ]; then\n            echo \"rootless\"\n            docker run -t -v /dev:/dev --rm --privileged test-integration /test-integration-rootless.sh ./hack/test-integration.sh -test.only-flaky=true\n          else\n            echo \"rootful\"\n            docker run -t -v /dev:/dev --rm --privileged test-integration ./hack/test-integration.sh -test.only-flaky=true\n          fi\n"
  },
  {
    "path": ".github/workflows/job-test-in-vagrant.yml",
    "content": "# Right now, this is testing solely FreeBSD, but could be used to test other targets.\n# Alternatively, this might get replaced entirely by Lima eventually.\nname: job-test-in-vagrant\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      runner:\n        required: true\n        type: string\n\njobs:\n  test:\n    # Will appear as freebsd / 14 in GitHub UI\n    name: \"14\"\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      - name: \"Init: setup cache\"\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306  # v5.0.3\n        with:\n          path: /root/.vagrant.d\n          key: vagrant\n\n      - name: \"Init: set up vagrant\"\n        run: |\n          # from https://github.com/containerd/containerd/blob/v2.0.2/.github/workflows/ci.yml#L583-L596\n          # which is based on https://github.com/opencontainers/runc/blob/v1.1.8/.cirrus.yml#L41-L49\n          # FIXME: https://github.com/containerd/nerdctl/issues/4163\n          cat ./hack/provisioning/gpg/hashicorp | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg\n          echo \"deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main\" | sudo tee /etc/apt/sources.list.d/hashicorp.list\n          sudo sed -i 's/^Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources\n          sudo apt-get update -qq\n          sudo apt-get install -qq libvirt-daemon libvirt-daemon-system vagrant ovmf\n          # https://github.com/vagrant-libvirt/vagrant-libvirt/issues/1725#issuecomment-1454058646\n          sudo cp /usr/share/OVMF/OVMF_VARS_4M.fd /var/lib/libvirt/qemu/nvram/\n          sudo systemctl enable --now libvirtd\n          sudo apt-get build-dep -qq ruby-libvirt\n          sudo apt-get install -qq --no-install-recommends libxslt-dev libxml2-dev libvirt-dev ruby-bundler ruby-dev zlib1g-dev\n          # Disable strict dependency enforcement to bypass gem version conflicts during the installation of the vagrant-libvirt plugin.\n          sudo env VAGRANT_DISABLE_STRICT_DEPENDENCY_ENFORCEMENT=1 vagrant plugin install vagrant-libvirt\n\n      - name: \"Init: boot VM\"\n        run: |\n          ln -sf Vagrantfile.freebsd Vagrantfile\n          sudo vagrant up --no-tty\n\n      - name: \"Run: test-unit\"\n        run: sudo vagrant up --provision-with=test-unit\n\n      - name: \"Run: test-integration\"\n        run: sudo vagrant up --provision-with=test-integration\n"
  },
  {
    "path": ".github/workflows/job-test-unit.yml",
    "content": "# Note: freebsd tests are not ran here (see integration instead)\nname: job-test-unit\n\non:\n  workflow_call:\n    inputs:\n      timeout:\n        required: true\n        type: number\n      go-version:\n        required: true\n        type: string\n      runner:\n        required: true\n        type: string\n      canary:\n        required: false\n        default: false\n        type: boolean\n      windows-cni-version:\n        required: true\n        type: string\n      linux-cni-version:\n        required: true\n        type: string\n      linux-cni-sha:\n        required: true\n        type: string\n\nenv:\n  GOTOOLCHAIN: local\n  # Windows fails without this\n  CGO_ENABLED: 0\n\njobs:\n  test-unit:\n    name: ${{ format('{0}{1}', inputs.runner, inputs.canary && ' (go canary)' || '') }}\n    timeout-minutes: ${{ inputs.timeout }}\n    runs-on: \"${{ inputs.runner }}\"\n    defaults:\n      run:\n        shell: bash\n\n    env:\n      GO_VERSION: ${{ inputs.go-version }}\n\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n\n      # If canary is requested, check for the latest unstable release\n      - if: ${{ inputs.canary }}\n        name: \"Init (canary): retrieve GO_VERSION\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          latest_go=\"$(. ./hack/provisioning/version/fetch.sh; go::canary::for::go-setup)\"\n          printf \"GO_VERSION=%s\\n\" \"$latest_go\" >> \"$GITHUB_ENV\"\n          [ \"$latest_go\" != \"\" ] || \\\n            echo \"::warning title=No canary go::There is currently no canary go version to test. Following steps will not run.\"\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Init: install go\"\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          check-latest: true\n\n      # Install CNI and CRIU\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Init: set up CNI and CRIU\"\n        run: |\n          if [ \"$RUNNER_OS\" == \"Windows\" ]; then\n            GOPATH=$(go env GOPATH) WINCNI_VERSION=${{ inputs.windows-cni-version }} ./hack/provisioning/windows/cni.sh\n          elif [ \"$RUNNER_OS\" == \"Linux\" ]; then\n            ./hack/provisioning/linux/cni.sh install \"${{ inputs.linux-cni-version }}\" \"amd64\" \"${{ inputs.linux-cni-sha }}\"\n            sudo apt-get update -qq\n            sudo add-apt-repository ppa:criu/ppa -y\n            sudo apt-get install -qq criu\n          fi\n\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Run\"\n        run: |\n          make test-unit\n\n      # On linux, also run with root\n      - if: ${{ env.GO_VERSION != '' && env.RUNNER_OS == 'Linux' }}\n        name: \"Run: with root\"\n        run: |\n          sudo make test-unit\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# See https://github.com/containerd/nerdctl/blob/main/MAINTAINERS_GUIDE.md for how to make a release.\nname: Release\non:\n  push:\n    tags:\n    - 'v*'\n    - 'test-action-release-*'\n  pull_request:\n    paths-ignore:\n    - '**.md'\n\nenv:\n  GOTOOLCHAIN: local\n\njobs:\n  release:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 40\n    # The maximum access is \"read\" for PRs from public forked repos\n    # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token\n    permissions:\n      contents: write  # for releases\n      id-token: write  # for provenances\n      attestations: write  # for provenances\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n    # FIXME: setup-qemu-action is depended by `gomodjail pack`\n    - name: \"Set up QEMU\"\n      uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a  # v4.0.0\n    - name: \"Install go\"\n      uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n      with:\n        go-version: \"1.25\"\n        check-latest: true\n    - name: \"Compile binaries\"\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: make artifacts\n    - name: \"SHA256SUMS\"\n      run: |\n        ( cd _output; sha256sum nerdctl-* ) | tee /tmp/SHA256SUMS\n        mv /tmp/SHA256SUMS _output/SHA256SUMS\n    - name: \"The sha256sum of the SHA256SUMS file\"\n      run: (cd _output; sha256sum SHA256SUMS)\n    - name: \"Prepare the release note\"\n      run: |\n        shasha=$(sha256sum _output/SHA256SUMS | awk '{print $1}')\n        cat <<-EOF | tee /tmp/release-note.txt\n        $(hack/generate-release-note.sh)\n        - - -\n        The binaries were built automatically on GitHub Actions.\n        The build log is available for 90 days: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\n        The sha256sum of the SHA256SUMS file itself is \\`${shasha}\\` .\n        - - -\n        Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE])\n        EOF\n    - name: \"Generate artifact attestation\"\n      uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32  # v4.1.0\n      if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')\n      with:\n        subject-path: _output/*\n    - name: \"Create release\"\n      if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      run: |\n        tag=\"${GITHUB_REF##*/}\"\n        gh release create -F /tmp/release-note.txt --draft --title \"${tag}\" \"${tag}\" _output/*\n"
  },
  {
    "path": ".github/workflows/workflow-flaky.yml",
    "content": "# This workflow puts together all known \"flaky\" and experimental targets\nname: \"[flaky, see #3988]\"\n\non:\n  push:\n    branches:\n      - main\n      - 'release/**'\n  pull_request:\n    paths-ignore:\n      - '**.md'\n\njobs:\n  test-integration-el:\n    name: \"EL${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-test-in-lima.yml\n    strategy:\n      fail-fast: false\n      # EL8 is used for testing compatibility with cgroup v1.\n      # Unfortunately, EL8 is hard to debug for ARM Mac users (as Lima+ARM Mac+EL8 is not runnable because of page size),\n      # and it currently shows numerous issues.\n      # ARM Mac users may use oraclelinux-8 instead for debugging cgroup v1 issues, although its kernel is different from\n      # other EL8 variants.\n      matrix:\n        guest: [\"almalinux-8\"]\n        target: [\"rootful\", \"rootless\"]\n    with:\n      timeout: 60\n      runner: ubuntu-24.04\n      guest: ${{ matrix.guest }}\n      target: ${{ matrix.target }}\n      skip-flaky: true  # skip the most flaky ones for now\n\n  test-integration-freebsd:\n    name: \"FreeBSD\"\n    uses: ./.github/workflows/job-test-in-vagrant.yml\n    with:\n      timeout: 15\n      runner: ubuntu-24.04\n\n  kube:\n    name: \"kubernetes\"\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    env:\n      ROOTFUL: true\n    steps:\n      - name: \"Init: checkout\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 1\n      - name: \"Run\"\n        run: |\n          # FIXME: this should be a bit more elegant to use.\n          ./hack/provisioning/kube/kind.sh\n          # See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization\n          sudo ./_output/nerdctl exec nerdctl-test-control-plane bash -c -- 'export TMPDIR=\"$HOME\"/tmp; mkdir -p \"$TMPDIR\"; cd /nerdctl-source; /usr/local/go/bin/go test -p 1 ./cmd/nerdctl/... -test.only-kubernetes'\n"
  },
  {
    "path": ".github/workflows/workflow-lint.yml",
    "content": "name: lint\n\non:\n  push:\n    branches:\n      - main\n      - 'release/**'\n  pull_request:\n\njobs:\n  # Runs golangci to ensure that:\n  # 1. the tooling is working on the target platform\n  # 2. the linter is happy\n  # 3. for canary (if there is a canary go version), does lint for all supported goos\n  lint-go:\n    name: \"go${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-lint-go.yml\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - runner: ubuntu-24.04\n            goos: linux\n          - runner: ubuntu-24.04\n            goos: freebsd\n          - runner: macos-15\n            goos: darwin\n          # FIXME: this is currently failing in a nonsensical way, so, running on linux instead...\n          # - runner: windows-2022\n          - runner: ubuntu-24.04\n            goos: windows\n          # Additionally lint for canary\n          - runner: ubuntu-24.04\n            goos: linux\n            canary: true\n    with:\n      timeout: 10\n      go-version: \"1.25\"\n      runner: ubuntu-24.04\n      # Note: in GitHub yaml world, if `matrix.canary` is undefined, and is passed to `inputs.canary`, the job\n      # will not run. However, if you test it, it will coerce to `false`, hence:\n      canary: ${{ matrix.canary && true || false }}\n      goos: ${{ matrix.goos }}\n\n  # Run common project checks (commits, licenses, etc)\n  lint-project-checks:\n    name: \"project checks\"\n    uses: ./.github/workflows/job-lint-project.yml\n    with:\n      timeout: 5\n      go-version: \"1.25\"\n      runner: ubuntu-24.04\n\n  # Lint for shell and yaml files\n  lint-other:\n    name: \"other\"\n    uses: ./.github/workflows/job-lint-other.yml\n    with:\n      timeout: 5\n      runner: ubuntu-24.04\n\n  # Verify we can actually build on all supported platforms, and a bunch of architectures\n  build-for-go:\n    name: \"build for${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-build.yml\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Build for both old and stable go\n          - go-version: \"1.24\"\n          - go-version: \"1.25\"\n          # Additionally build for canary\n          - go-version: \"1.25\"\n            canary: true\n    with:\n      timeout: 10\n      go-version: ${{ matrix.go-version }}\n      runner: ubuntu-24.04\n      canary: ${{ matrix.canary && true || false }}\n"
  },
  {
    "path": ".github/workflows/workflow-test.yml",
    "content": "name: test\n\non:\n  push:\n    branches:\n      - main\n      - 'release/**'\n  pull_request:\n    paths-ignore:\n      - '**.md'\n\njobs:\n  test-unit:\n    # Note: inputs.hack is undefined - its purpose is to prevent GitHub Actions from displaying all matrix variants as part of the name.\n    name: \"unit${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-test-unit.yml\n    strategy:\n      fail-fast: false\n      matrix:\n        # Run on all supported platforms but freebsd\n        # Additionally run on canary for linux\n        include:\n          - runner: \"ubuntu-24.04\"\n          - runner: \"macos-15\"\n          - runner: \"windows-2025\"\n          - runner: \"ubuntu-24.04\"\n            canary: true\n    with:\n      runner: ${{ matrix.runner }}\n      canary: ${{ matrix.canary && true || false }}\n      # Windows routinely go over 5 minutes\n      timeout: 10\n      go-version: 1.25\n      windows-cni-version: v0.3.1\n      linux-cni-version: v1.7.1\n      linux-cni-sha: 1a28a0506bfe5bcdc981caf1a49eeab7e72da8321f1119b7be85f22621013098\n\n  # This job builds the dependency target of the test-image for all supported architectures and cache it in GHA\n  build-dependencies:\n    name: \"dependencies${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-test-dependencies.yml\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Build for arm & amd, current containerd\n          - runner: ubuntu-24.04\n          - runner: ubuntu-24.04-arm\n          # Additionally build for old containerd on amd\n          - runner: ubuntu-24.04\n            containerd-version: v1.7.30\n    with:\n      runner: ${{ matrix.runner }}\n      containerd-version: ${{ matrix.containerd-version }}\n      timeout: 20\n\n  test-integration-container:\n    name: \"in-container${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-test-in-container.yml\n    needs: build-dependencies\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          ###### Rootless\n          # amd64\n          - runner: ubuntu-24.04\n            target: rootless\n          # arm64\n          - runner: ubuntu-24.04-arm\n            target: rootless\n            skip-flaky: true\n          # port-slirp4netns\n          - runner: ubuntu-24.04\n            target: rootless-port-slirp4netns\n            skip-flaky: true\n          # old containerd + old ubuntu + old rootlesskit\n          - runner: ubuntu-22.04\n            target: rootless\n            containerd-version: v1.7.30\n            rootlesskit-version: v1.1.1\n          # gomodjail\n          - runner: ubuntu-24.04\n            target: rootless\n            binary: \"nerdctl.gomodjail\"\n          ###### Rootful\n          # amd64\n          - runner: ubuntu-24.04\n            target: rootful\n          # arm64\n          - runner: ubuntu-24.04-arm\n            target: rootful\n            skip-flaky: true\n          # old containerd + old ubuntu\n          - runner: ubuntu-22.04\n            target: rootful\n            containerd-version: v1.7.30\n          # ipv6\n          - runner: ubuntu-24.04\n            target: rootful\n            ipv6: true\n            skip-flaky: true\n          # all canary\n          - runner: ubuntu-24.04\n            target: rootful\n            canary: true\n\n    with:\n      timeout: 80\n      runner: ${{ matrix.runner }}\n      target: ${{ matrix.target }}\n      binary: ${{ matrix.binary && matrix.binary || 'nerdctl' }}\n      containerd-version: ${{ matrix.containerd-version }}\n      rootlesskit-version: ${{ matrix.rootlesskit-version }}\n      ipv6: ${{ matrix.ipv6 && true || false }}\n      canary: ${{ matrix.canary && true || false }}\n      skip-flaky: ${{ matrix.skip-flaky && true || false }}\n\n  test-integration-host:\n    name: \"in-host${{ inputs.hack }}\"\n    uses: ./.github/workflows/job-test-in-host.yml\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Test on windows w/o canary\n          - runner: windows-2022\n          - runner: windows-2025\n            canary: true\n          # Test docker on linux\n          - runner: ubuntu-24.04\n            binary: docker\n\n          # FIXME: running nerdctl on the host is work in progress\n          # (we miss runc to be installed on the host - and obviously other deps)\n          # Plan is to pause this for now and first consolidate dependencies management (wrt Dockerfile vs. host-testing CI)\n          # before we can really start testing linux nerdctl on the host.\n          # - runner: ubuntu-24.04\n          # - runner: ubuntu-24.04\n          #  canary: true\n    with:\n      timeout: 45\n      runner: ${{ matrix.runner }}\n      binary: ${{ matrix.binary != '' && matrix.binary || 'nerdctl' }}\n      canary: ${{ matrix.canary && true || false }}\n      go-version: 1.25\n      windows-cni-version: v0.3.1\n      docker-version: 5:28.0.4-1~ubuntu.24.04~noble\n      containerd-version: 2.2.1\n      # Note: these as for amd64\n      containerd-sha: f5d8e90ecb6c1c7e33ecddf8cc268a93b9e5b54e0e850320d765511d76624f41\n      containerd-service-sha: 1941362cbaa89dd591b99c32b050d82c583d3cd2e5fa63085d7017457ec5fca8\n      linux-cni-version: v1.9.0\n      linux-cni-sha: 58c03705426e929658f45a851df15a86d06ef680cacbf3f2dc127731ca265c28\n"
  },
  {
    "path": ".github/workflows/workflow-tigron.yml",
    "content": "name: tigron\n\non:\n  push:\n    branches:\n      - main\n      - 'release/**'\n  pull_request:\n    paths: 'mod/tigron/**'\n\nenv:\n  GO_VERSION: \"1.25\"\n  GOTOOLCHAIN: local\n\njobs:\n  lint:\n    timeout-minutes: 15\n    name: \"${{ matrix.goos }} ${{ matrix.runner }} | go ${{ matrix.canary }}\"\n    runs-on: ${{ matrix.runner }}\n    defaults:\n      run:\n        shell: bash\n    strategy:\n      matrix:\n        include:\n          - runner: ubuntu-24.04\n          - runner: macos-15\n          - runner: windows-2022\n          - runner: ubuntu-24.04\n            goos: freebsd\n          - runner: ubuntu-24.04\n            canary: go-canary\n    steps:\n      - name: \"Checkout project\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2\n        with:\n          fetch-depth: 100\n      - if: ${{ matrix.canary }}\n        name: \"Init (canary): retrieve GO_VERSION\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          latest_go=\"$(. ./hack/provisioning/version/fetch.sh; go::canary::for::go-setup)\"\n          printf \"GO_VERSION=%s\\n\" \"$latest_go\" >> \"$GITHUB_ENV\"\n          [ \"$latest_go\" != \"\" ] || \\\n            echo \"::warning title=No canary go::There is currently no canary go version to test. Steps will not run.\"\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Install go\"\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417  # v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          check-latest: true\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"Install tools\"\n        run: |\n          cd mod/tigron\n          echo \"::group:: make install-dev-tools\"\n          make install-dev-tools\n          if [ \"$RUNNER_OS\" == macOS ]; then\n            brew install yamllint shellcheck\n          fi\n          echo \"::endgroup::\"\n      - if: ${{ env.GO_VERSION != '' && matrix.goos == '' }}\n        name: \"lint\"\n        env:\n          NO_COLOR: true\n        run: |\n          if [ \"$RUNNER_OS\" == Linux ]; then\n            echo \"::group:: lint\"\n            cd mod/tigron\n            export LINT_COMMIT_RANGE=\"$(jq -r '.after + \"..HEAD\"' ${GITHUB_EVENT_PATH})\"\n            make lint\n            echo \"::endgroup::\"\n          else\n            echo \"Lint is disabled on $RUNNER_OS\"\n          fi\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"test-unit\"\n        run: |\n          echo \"::group:: unit test\"\n          cd mod/tigron\n          make test-unit\n          echo \"::endgroup::\"\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"test-unit-race\"\n        run: |\n          echo \"::group:: race test\"\n          cd mod/tigron\n          make test-unit-race\n          echo \"::endgroup::\"\n      - if: ${{ env.GO_VERSION != '' }}\n        name: \"test-unit-bench\"\n        run: |\n          echo \"::group:: bench\"\n          cd mod/tigron\n          make test-unit-bench\n          echo \"::endgroup::\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# artifacts\n/nerdctl\n_output\n*.gomodjail\n\n# golangci-lint\n/build\n\n# vagrant\n/.vagrant\nVagrantfile\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\n\nrun:\n  modules-download-mode: readonly\n\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\n\nlinters:\n  default: none\n  enable:\n    # 1. This is the default enabled set of golanci\n\n    # We should consider enabling errcheck\n    # - errcheck\n    - govet\n    - ineffassign\n    - staticcheck\n    - unused\n\n    # 2. These are not part of the default set\n\n    # Important to prevent import of certain packages\n    - depguard\n    # Removes unnecessary conversions\n    - unconvert\n    # Flag common typos\n    - misspell\n    # A meta-linter seen as a good replacement for golint\n    - revive\n    # Gocritic\n    - gocritic\n    - forbidigo\n\n    # 3. We used to use these, but have now removed them\n\n    # Use of prealloc is generally premature optimization and performance profiling should be done instead\n    # https://golangci-lint.run/usage/linters/#prealloc\n    # - prealloc\n    # Provided by revive in a better way\n    # - nakedret\n\n  settings:\n    forbidigo:\n      forbid:\n        # FIXME: there are still calls to os.WriteFile in tests under `cmd`\n        - pattern: ^os\\.WriteFile.*$\n          pkg: github.com/containerd/nerdctl/v2/pkg\n          msg: os.WriteFile is neither atomic nor durable - use nerdctl filesystem.WriteFile instead\n        - pattern: ^os\\.ReadFile.*$\n          pkg: github.com/containerd/nerdctl/v2/pkg\n          msg: use filesystem.ReadFile instead of os.ReadFile\n    staticcheck:\n      checks:\n        # Below is the default set\n        - \"all\"\n        - \"-ST1000\"\n        - \"-ST1003\"\n        - \"-ST1016\"\n        - \"-ST1020\"\n        - \"-ST1021\"\n        - \"-ST1022\"\n\n        ##### TODO: fix and enable these\n        # 6 occurrences.\n        # Apply De Morgan’s law https://staticcheck.dev/docs/checks#QF1001\n        - \"-QF1001\"\n        # 10 occurrences.\n        # Convert if/else-if chain to tagged switch https://staticcheck.dev/docs/checks#QF1003\n        - \"-QF1003\"\n\n        ##### These have been vetted to be disabled.\n        # 55 occurrences. Omit embedded fields from selector expression https://staticcheck.dev/docs/checks#QF1008\n        # Usefulness is questionable.\n        - \"-QF1008\"\n\n    revive:\n      enable-all-rules: true\n      rules:\n        # See https://revive.run/r\n\n        ##### P0: we should do it ASAP.\n        - name: max-control-nesting\n          # 10 occurences (at default 5). Deep nesting hurts readibility.\n          arguments: [7]\n        - name: deep-exit\n          # 11 occurrences. Do not exit in random places.\n          disabled: true\n        - name: unchecked-type-assertion\n          # 14 occurrences. This is generally risky and encourages bad coding for newcomers.\n          disabled: true\n        - name: bare-return\n          # 31 occurrences. Bare returns are just evil, very unfriendly, and make reading and editing much harder.\n          disabled: true\n        - name: import-shadowing\n          # 44 occurrences. Shadowing makes things prone to errors / confusing to read.\n          disabled: true\n        - name: use-errors-new\n          # 84 occurrences. Improves error testing.\n          disabled: true\n        - name: struct-tag\n          # 2 occurrences.\n          disabled: true\n\n        ##### P1: consider making a dent on these, but not critical.\n        - name: argument-limit\n          # 4 occurrences (at default 8). Long windy arguments list for functions are hard to read. Use structs instead.\n          arguments: [12]\n        - name: unnecessary-stmt\n          # 5 occurrences. Increase readability.\n          disabled: true\n        - name: defer\n          # 7 occurrences. Confusing to read for newbies.\n          disabled: true\n        - name: confusing-naming\n          # 10 occurrences. Hurts readability.\n          disabled: true\n        - name: early-return\n          # 10 occurrences. Would improve readability.\n          disabled: true\n        - name: function-result-limit\n          # 12 occurrences (at default 3). A function returning many results is probably too big.\n          arguments: [7]\n        - name: function-length\n          # 155 occurrences (at default 0, 75). Really long functions should really be broken up in most cases.\n          arguments: [0, 500]\n        - name: cyclomatic\n          # 204 occurrences (at default 10)\n          arguments: [100]\n        - name: unhandled-error\n          # 222 occurrences. Could indicate failure to handle broken conditions.\n          disabled: true\n        - name: cognitive-complexity\n          arguments: [205]\n          # 441 occurrences (at default 7). We should try to lower it (involves significant refactoring).\n        - name: var-naming\n          # 1 occurrence.\n          disabled: true\n\n        ##### P2: nice to have.\n        - name: max-public-structs\n          # 7 occurrences (at default 5). Might indicate overcrowding of public API.\n          arguments: [25]\n        - name: confusing-results\n          # 13 occurrences. Have named returns when the type stutters.\n          # Makes it a bit easier to figure out function behavior just looking at signature.\n          disabled: true\n        - name: comment-spacings\n          # 50 occurrences. Makes code look less wonky / ease readability.\n          disabled: true\n        - name: use-any\n          # 30 occurrences. `any` instead of `interface{}`. Cosmetic.\n          disabled: true\n        - name: empty-lines\n          # 85 occurrences. Makes code look less wonky / ease readability.\n          disabled: true\n        - name: package-comments\n          # 100 occurrences. Better for documentation...\n          disabled: true\n        - name: exported\n          # 577 occurrences. Forces documentation of any exported symbol.\n          disabled: true\n        - name: unnecessary-format\n          # Many occurrences.\n          disabled: true\n\n        ###### Permanently disabled. Below have been reviewed and vetted to be unnecessary.\n        - name: line-length-limit\n          # Formatter `golines` takes care of this.\n          disabled: true\n        - name: nested-structs\n          # 5 occurrences. Trivial. This is not that hard to read.\n          disabled: true\n        - name: flag-parameter\n          # 52 occurrences. Not sure if this is valuable.\n          disabled: true\n        - name: unused-parameter\n          # 505 occurrences. A lot of work for a marginal improvement.\n          disabled: true\n        - name: unused-receiver\n          # 31 occurrences. Ibid.\n          disabled: true\n        - name: add-constant\n          # 2605 occurrences. Kind of useful in itself, but unacceptable amount of effort to fix\n          disabled: true\n        - name: enforce-switch-style\n          # Many occurrences.\n          disabled: true\n\n    depguard:\n      rules:\n        no-patent:\n          # do not link in golang-lru anywhere (problematic patent)\n          deny:\n            - pkg: github.com/hashicorp/golang-lru/arc/v2\n              desc: patented (https://github.com/hashicorp/golang-lru/blob/arc/v2.0.7/arc/arc.go#L18)\n        pkg:\n          # pkg files must not depend on cobra nor anything in cmd\n          files:\n            - '**/pkg/**/*.go'\n          deny:\n            - pkg: github.com/spf13/cobra\n              desc: pkg must not depend on cobra\n            - pkg: github.com/spf13/pflag\n              desc: pkg must not depend on pflag\n            - pkg: github.com/spf13/viper\n              desc: pkg must not depend on viper\n            - pkg: github.com/containerd/nerdctl/v2/cmd\n              desc: pkg must not depend on any cmd files\n    gocritic:\n      disabled-checks:\n        # Below are normally enabled by default, but we do not pass\n        - appendAssign\n        - ifElseChain\n        - unslice\n        - badCall\n        - assignOp\n        - commentFormatting\n        - captLocal\n        - singleCaseSwitch\n        - wrapperFunc\n        - elseif\n        - regexpMust\n      enabled-checks:\n        # Below used to be enabled, but we do not pass anymore\n        # - paramTypeCombine\n        # - octalLiteral\n        # - unnamedResult\n        # - equalFold\n        # - sloppyReassign\n        # - emptyStringTest\n        # - hugeParam\n        # - appendCombine\n        # - stringXbytes\n        # - ptrToRefParam\n        # - commentedOutCode\n        # - rangeValCopy\n        # - methodExprCall\n        # - yodaStyleExpr\n        # - typeUnparen\n\n        # We enabled these and we pass\n        - nilValReturn\n        - weakCond\n        - indexAlloc\n        - rangeExprCopy\n        - boolExprSimplify\n        - commentedOutImport\n        - docStub\n        - emptyFallthrough\n        - hexLiteral\n        - typeAssertChain\n        - unlabelStmt\n        - builtinShadow\n        - initClause\n        - nestingReduce\n        - unnecessaryBlock\n  exclusions:\n    generated: disable\n\nformatters:\n  settings:\n    gci:\n      sections:\n        - standard\n        - default\n        - prefix(github.com/containerd)\n        - localmodule\n      no-inline-comments: true\n      no-prefix-comments: true\n      custom-order: true\n    gofumpt:\n      extra-rules: true\n    golines:\n      max-len: 500\n      tab-len: 4\n      shorten-comments: true\n  enable:\n    - gci\n    - gofmt\n    # We might consider enabling the following:\n    #    - gofumpt\n    - golines\n  exclusions:\n    generated: disable\n"
  },
  {
    "path": ".yamllint",
    "content": "---\n\nextends: default\n\nrules:\n  indentation:\n    spaces: 2\n    indent-sequences: consistent\n  truthy:\n    allowed-values: ['true', 'false', 'on', 'off']\n  comments-indentation: disable\n  document-start: disable\n  line-length: disable\n"
  },
  {
    "path": "BUILDING.md",
    "content": "# Building nerdctl\n\nTo build nerdctl, use `make`:\n\n```bash\nmake\nsudo make install\n```\n\nAlternatively, nerdctl can be also built with `go build ./cmd/nerdctl`.\nHowever, this is not recommended as it does not populate the version string (`nerdctl -v`).\n\n## Customization\n\nTo specify build tags, set the `BUILDTAGS` variable as follows:\n\n```bash\nBUILDTAGS=no_ipfs make\n```\n\nThe following build tags are supported:\n* `no_ipfs` (since v2.1.3): Disable IPFS\n"
  },
  {
    "path": "Dockerfile",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# -----------------------------------------------------------------------------\n# Usage: `docker run -it --privileged <IMAGE>`. Make sure to add `-t` and `--privileged`.\n\n# Basic deps\n# @BINARY: the binary checksums are verified via Dockerfile.d/SHA256SUMS.d/<COMPONENT>-<VERSION>\nARG CONTAINERD_VERSION=v2.2.1@dea7da592f5d1d2b7755e3a161be07f43fad8f75\nARG RUNC_VERSION=v1.4.0@8bd78a9977e604c4d5f67a7415d7b8b8c109cdc4\nARG CNI_PLUGINS_VERSION=v1.9.0@BINARY\n\n# Extra deps: Build\nARG BUILDKIT_VERSION=v0.26.3@BINARY\n# Extra deps: Lazy-pulling\nARG STARGZ_SNAPSHOTTER_VERSION=v0.18.1@BINARY\n# Extra deps: Encryption\nARG IMGCRYPT_VERSION=v2.0.2@6892f4df2405cd15acbefd1dca970f53ba38bfda\n# Extra deps: Rootless\nARG ROOTLESSKIT_VERSION=v2.3.6@BINARY\nARG SLIRP4NETNS_VERSION=v1.3.3@BINARY\n# Extra deps: bypass4netns\nARG BYPASS4NETNS_VERSION=v0.4.2@aa04bd3dcc48c6dae6d7327ba219bda8fe2a4634\n# Extra deps: FUSE-OverlayFS\nARG FUSE_OVERLAYFS_VERSION=v1.16@BINARY\nARG CONTAINERD_FUSE_OVERLAYFS_VERSION=v2.1.7@BINARY\n# Extra deps: Init\nARG TINI_VERSION=v0.19.0@BINARY\n# Extra deps: Debug\nARG BUILDG_VERSION=v0.5.3@BINARY\n# Extra deps: gomodjail\nARG GOMODJAIL_VERSION=v0.1.3@cea529ddd971b677c67d8af7e936fbc62b35b98c\n\n# Test deps\n# Currently, the Docker Official Images and the test deps are not pinned by the hash\nARG GO_VERSION=1.25\nARG UBUNTU_VERSION=24.04\nARG CONTAINERIZED_SYSTEMD_VERSION=v0.1.1\nARG GOTESTSUM_VERSION=v1.13.0\nARG NYDUS_VERSION=v2.3.9\nARG SOCI_SNAPSHOTTER_VERSION=0.12.1\nARG KUBO_VERSION=v0.39.0\n\nFROM --platform=$BUILDPLATFORM tonistiigi/xx:1.9.0@sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707 AS xx\n\n\nFROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-trixie AS build-base\nCOPY --from=xx / /\nENV DEBIAN_FRONTEND=noninteractive\nRUN apt-get update -qq && apt-get install -qq --no-install-recommends \\\n  make \\\n  git \\\n  jq \\\n  curl \\\n  dpkg-dev\nARG TARGETARCH\n# libbtrfs: for containerd\n# libseccomp: for runc and bypass4netns\nRUN xx-apt-get update -qq && xx-apt-get install -qq --no-install-recommends \\\n  binutils \\\n  gcc \\\n  libc6-dev \\\n  libbtrfs-dev \\\n  libseccomp-dev \\\n  pkg-config\nRUN git config --global advice.detachedHead false\nADD hack/git-checkout-tag-with-hash.sh /usr/local/bin/\nADD hack/scripts/lib.sh /usr/local/bin/http::helper\n\nFROM build-base AS build-containerd\nARG TARGETARCH\nARG CONTAINERD_VERSION\nRUN git clone --quiet --depth 1 --branch \"${CONTAINERD_VERSION%%@*}\" https://github.com/containerd/containerd.git /go/src/github.com/containerd/containerd\nWORKDIR /go/src/github.com/containerd/containerd\nRUN git-checkout-tag-with-hash.sh ${CONTAINERD_VERSION} && \\\n  mkdir -p /out /out/$TARGETARCH && \\\n  cp -a containerd.service /out\nRUN GO=xx-go make STATIC=1 && \\\n  cp -a bin/containerd bin/containerd-shim-runc-v2 bin/ctr /out/$TARGETARCH\n\nFROM build-base AS build-runc\nARG RUNC_VERSION\nARG TARGETARCH\nRUN git clone --quiet --depth 1 --branch \"${RUNC_VERSION%%@*}\" https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc\nWORKDIR /go/src/github.com/opencontainers/runc\nRUN git-checkout-tag-with-hash.sh ${RUNC_VERSION} && \\\n  mkdir -p /out\nENV CGO_ENABLED=1\n# FIXME: avoid omitting libpathrs\nRUN set -x ; GO=xx-go CC=$(xx-info)-gcc STRIP=$(xx-info)-strip make BUILDTAGS=\"$(grep -oP \"^BUILDTAGS := \\K.*\" Makefile  | sed -e s/libpathrs//)\" static && \\\n  xx-verify --static runc && cp -v -a runc /out/runc.${TARGETARCH}\n\nFROM build-base AS build-bypass4netns\nARG BYPASS4NETNS_VERSION\nARG TARGETARCH\nRUN git clone --quiet --depth 1 --branch \"${BYPASS4NETNS_VERSION%%@*}\" https://github.com/rootless-containers/bypass4netns.git /go/src/github.com/rootless-containers/bypass4netns\nWORKDIR /go/src/github.com/rootless-containers/bypass4netns\nRUN git-checkout-tag-with-hash.sh ${BYPASS4NETNS_VERSION} && \\\n  mkdir -p /out/${TARGETARCH}\nENV CGO_ENABLED=1\nRUN GO=xx-go make static && \\\n  xx-verify --static bypass4netns && cp -a bypass4netns bypass4netnsd /out/${TARGETARCH}\n\nFROM build-base AS build-gomodjail\nARG GOMODJAIL_VERSION\nARG TARGETARCH\nRUN git clone --quiet --depth 1 --branch \"${GOMODJAIL_VERSION%%@*}\" https://github.com/AkihiroSuda/gomodjail.git /go/src/github.com/AkihiroSuda/gomodjail\nWORKDIR /go/src/github.com/AkihiroSuda/gomodjail\nRUN git-checkout-tag-with-hash.sh ${GOMODJAIL_VERSION} && \\\n  mkdir -p /out/${TARGETARCH}\nRUN GO=xx-go make STATIC=1 && \\\n  xx-verify --static _output/bin/gomodjail && cp -a _output/bin/gomodjail /out/${TARGETARCH}\n\nFROM build-base AS build-kubo\nARG KUBO_VERSION\nARG TARGETARCH\nRUN git clone --quiet --depth 1 --branch \"${KUBO_VERSION%%@*}\" https://github.com/ipfs/kubo.git /go/src/github.com/ipfs/kubo\nWORKDIR /go/src/github.com/ipfs/kubo\nRUN git-checkout-tag-with-hash.sh ${KUBO_VERSION} && \\\n  mkdir -p /out/${TARGETARCH}\nENV CGO_ENABLED=0\nRUN xx-go --wrap && \\\n  make build && \\\n  xx-verify --static cmd/ipfs/ipfs && cp -a cmd/ipfs/ipfs /out/${TARGETARCH}\n\nFROM build-base AS build-minimal\nRUN BINDIR=/out/bin make binaries install\n# We do not set CMD to `go test` here, because it requires systemd\n\nFROM build-base AS build-dependencies\nARG TARGETARCH\nENV GOARCH=${TARGETARCH}\nCOPY ./Dockerfile.d/SHA256SUMS.d/ /SHA256SUMS.d\nWORKDIR /nowhere\nRUN echo \"${TARGETARCH:-amd64}\" | sed -e s/amd64/x86_64/ -e s/arm64/aarch64/ | tee /target_uname_m\nRUN mkdir -p /out/share/doc/nerdctl-full && touch /out/share/doc/nerdctl-full/README.md\nARG CONTAINERD_VERSION\nCOPY --from=build-containerd /out/${TARGETARCH:-amd64}/* /out/bin/\nCOPY --from=build-containerd /out/containerd.service /out/lib/systemd/system/containerd.service\nRUN echo \"- containerd: ${CONTAINERD_VERSION%%@*}\" >> /out/share/doc/nerdctl-full/README.md\nARG RUNC_VERSION\nCOPY --from=build-runc /out/runc.${TARGETARCH:-amd64} /out/bin/runc\nRUN echo \"- runc: ${RUNC_VERSION%%@*}\" >> /out/share/doc/nerdctl-full/README.md\nARG CNI_PLUGINS_VERSION\nRUN CNI_PLUGINS_VERSION=${CNI_PLUGINS_VERSION%%@*}; \\\n  fname=\"cni-plugins-${TARGETOS:-linux}-${TARGETARCH:-amd64}-${CNI_PLUGINS_VERSION}.tgz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/cni-plugins-${CNI_PLUGINS_VERSION}\" | sha256sum -c && \\\n  mkdir -p /out/libexec/cni && \\\n  tar xzf \"${fname}\" -C /out/libexec/cni && \\\n  rm -f \"${fname}\" && \\\n  echo \"- CNI plugins: ${CNI_PLUGINS_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG BUILDKIT_VERSION\nRUN BUILDKIT_VERSION=${BUILDKIT_VERSION%%@*}; \\\n  fname=\"buildkit-${BUILDKIT_VERSION}.${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/moby/buildkit/releases/download/${BUILDKIT_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/buildkit-${BUILDKIT_VERSION}\" | sha256sum -c && \\\n  tar xzf \"${fname}\" -C /out && \\\n  rm -f \"${fname}\" /out/bin/buildkit-qemu-* /out/bin/buildkit-cni-* /out/bin/buildkit-runc && \\\n  for f in /out/libexec/cni/*; do [ -x \"$f\" ] && [ -f \"$f\" ] && ln -s ../libexec/cni/$(basename $f) /out/bin/buildkit-cni-$(basename $f); done && \\\n  echo \"- BuildKit: ${BUILDKIT_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\n# NOTE: github.com/moby/buildkit/examples/systemd is not included in BuildKit v0.8.x, will be included in v0.9.x\nRUN cd /out/lib/systemd/system && \\\n  sedcomm='s@bin/containerd@bin/buildkitd@g; s@(Description|Documentation)=.*@@' && \\\n  sed -E \"${sedcomm}\" containerd.service > buildkit.service && \\\n  echo \"\" >> buildkit.service && \\\n  echo \"# This file was converted from containerd.service, with \\`sed -E '${sedcomm}'\\`\" >> buildkit.service\nARG STARGZ_SNAPSHOTTER_VERSION\nRUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN \\\n  STARGZ_SNAPSHOTTER_VERSION=${STARGZ_SNAPSHOTTER_VERSION%%@*}; \\\n  fname=\"stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/containerd/stargz-snapshotter/releases/download/${STARGZ_SNAPSHOTTER_VERSION}/${fname}\" && \\\n  http::helper github::file containerd/stargz-snapshotter script/config/etc/systemd/system/stargz-snapshotter.service \"${STARGZ_SNAPSHOTTER_VERSION}\" > \"stargz-snapshotter.service\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}\" | sha256sum -c - && \\\n  grep \"stargz-snapshotter.service\" \"/SHA256SUMS.d/stargz-snapshotter-${STARGZ_SNAPSHOTTER_VERSION}\" | sha256sum -c - && \\\n  tar xzf \"${fname}\" -C /out/bin && \\\n  rm -f \"${fname}\" /out/bin/stargz-store && \\\n  mv stargz-snapshotter.service /out/lib/systemd/system/stargz-snapshotter.service && \\\n  echo \"- Stargz Snapshotter: ${STARGZ_SNAPSHOTTER_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG IMGCRYPT_VERSION\nRUN git clone --quiet --depth 1 --branch \"${IMGCRYPT_VERSION%%@*}\" https://github.com/containerd/imgcrypt.git /go/src/github.com/containerd/imgcrypt && \\\n  cd /go/src/github.com/containerd/imgcrypt && \\\n  git-checkout-tag-with-hash.sh \"${IMGCRYPT_VERSION}\" && \\\n  CGO_ENABLED=0 make && DESTDIR=/out make install && \\\n  echo \"- imgcrypt: ${IMGCRYPT_VERSION%%@*}\" >> /out/share/doc/nerdctl-full/README.md\nARG SLIRP4NETNS_VERSION\nRUN SLIRP4NETNS_VERSION=${SLIRP4NETNS_VERSION%%@*}; \\\n  fname=\"slirp4netns-$(cat /target_uname_m)\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/rootless-containers/slirp4netns/releases/download/${SLIRP4NETNS_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/slirp4netns-${SLIRP4NETNS_VERSION}\" | sha256sum -c && \\\n  mv \"${fname}\" /out/bin/slirp4netns && \\\n  chmod +x /out/bin/slirp4netns && \\\n  echo \"- slirp4netns: ${SLIRP4NETNS_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG BYPASS4NETNS_VERSION\nCOPY --from=build-bypass4netns /out/${TARGETARCH:-amd64}/* /out/bin/\nRUN echo \"- bypass4netns: ${BYPASS4NETNS_VERSION%%@*}\" >> /out/share/doc/nerdctl-full/README.md\nARG FUSE_OVERLAYFS_VERSION\nRUN FUSE_OVERLAYFS_VERSION=${FUSE_OVERLAYFS_VERSION%%@*}; \\\n  fname=\"fuse-overlayfs-$(cat /target_uname_m)\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/containers/fuse-overlayfs/releases/download/${FUSE_OVERLAYFS_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/fuse-overlayfs-${FUSE_OVERLAYFS_VERSION}\" | sha256sum -c && \\\n  mv \"${fname}\" /out/bin/fuse-overlayfs && \\\n  chmod +x /out/bin/fuse-overlayfs && \\\n  echo \"- fuse-overlayfs: ${FUSE_OVERLAYFS_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG CONTAINERD_FUSE_OVERLAYFS_VERSION\nRUN CONTAINERD_FUSE_OVERLAYFS_VERSION=${CONTAINERD_FUSE_OVERLAYFS_VERSION%%@*}; \\\n  fname=\"containerd-fuse-overlayfs-${CONTAINERD_FUSE_OVERLAYFS_VERSION##*v}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/containerd/fuse-overlayfs-snapshotter/releases/download/${CONTAINERD_FUSE_OVERLAYFS_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/containerd-fuse-overlayfs-${CONTAINERD_FUSE_OVERLAYFS_VERSION}\" | sha256sum -c && \\\n  tar xzf \"${fname}\" -C /out/bin && \\\n  rm -f \"${fname}\" && \\\n  echo \"- containerd-fuse-overlayfs: ${CONTAINERD_FUSE_OVERLAYFS_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG TINI_VERSION\nRUN TINI_VERSION=${TINI_VERSION%%@*}; \\\n  fname=\"tini-static-${TARGETARCH:-amd64}\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/krallin/tini/releases/download/${TINI_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/tini-${TINI_VERSION}\" | sha256sum -c && \\\n  cp -a \"${fname}\" /out/bin/tini && chmod +x /out/bin/tini && \\\n  echo \"- Tini: ${TINI_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG BUILDG_VERSION\n# FIXME: this is a mildly-confusing approach. Buildkit will perform some \"smart\" replacement at build time and output\n# confusing debugging information, eg: BUILDG_VERSION will appear as if the original ARG value was used.\nRUN BUILDG_VERSION=${BUILDG_VERSION%%@*}; \\\n  fname=\"buildg-${BUILDG_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/ktock/buildg/releases/download/${BUILDG_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/buildg-${BUILDG_VERSION}\" | sha256sum -c && \\\n  tar xzf \"${fname}\" -C /out/bin && \\\n  rm -f \"${fname}\" && \\\n  echo \"- buildg: ${BUILDG_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG ROOTLESSKIT_VERSION\nRUN ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION%%@*}; \\\n  fname=\"rootlesskit-$(cat /target_uname_m).tar.gz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/rootless-containers/rootlesskit/releases/download/${ROOTLESSKIT_VERSION}/${fname}\" && \\\n  grep \"${fname}\" \"/SHA256SUMS.d/rootlesskit-${ROOTLESSKIT_VERSION}\" | sha256sum -c && \\\n  tar xzf \"${fname}\" -C /out/bin && \\\n  rm -f \"${fname}\" /out/bin/rootlesskit-docker-proxy && \\\n  echo \"- RootlessKit: ${ROOTLESSKIT_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG GOMODJAIL_VERSION\nCOPY --from=build-gomodjail /out/${TARGETARCH:-amd64}/* /out/bin/\nRUN echo \"- gomodjail: ${GOMODJAIL_VERSION}\" >> /out/share/doc/nerdctl-full/README.md\nARG CONTAINERIZED_SYSTEMD_VERSION\nRUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN \\\n  http::helper github::file AkihiroSuda/containerized-systemd docker-entrypoint.sh \"${CONTAINERIZED_SYSTEMD_VERSION}\" > /docker-entrypoint.sh && \\\n  chmod +x /docker-entrypoint.sh\n\nRUN echo \"\" >> /out/share/doc/nerdctl-full/README.md && \\\n  echo \"## License\" >> /out/share/doc/nerdctl-full/README.md && \\\n  echo \"- bin/slirp4netns:    [GNU GENERAL PUBLIC LICENSE, Version 2](https://github.com/rootless-containers/slirp4netns/blob/${SLIRP4NETNS_VERSION%%@*}/COPYING)\" >> /out/share/doc/nerdctl-full/README.md && \\\n  echo \"- bin/fuse-overlayfs: [GNU GENERAL PUBLIC LICENSE, Version 2](https://github.com/containers/fuse-overlayfs/blob/${FUSE_OVERLAYFS_VERSION%%@*}/COPYING)\" >> /out/share/doc/nerdctl-full/README.md && \\\n  echo \"- bin/{runc,bypass4netns,bypass4netnsd}: Apache License 2.0, statically linked with libseccomp ([LGPL 2.1](https://github.com/seccomp/libseccomp/blob/main/LICENSE), source code available at https://github.com/seccomp/libseccomp/)\" >> /out/share/doc/nerdctl-full/README.md && \\\n  echo \"- bin/tini: [MIT License](https://github.com/krallin/tini/blob/${TINI_VERSION%%@*}/LICENSE)\" >> /out/share/doc/nerdctl-full/README.md && \\\n  echo \"- Other files: [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)\" >> /out/share/doc/nerdctl-full/README.md\n\nFROM build-dependencies AS build-full\nCOPY . /go/src/github.com/containerd/nerdctl\nRUN { echo \"# nerdctl (full distribution)\"; echo \"- nerdctl: $(cd /go/src/github.com/containerd/nerdctl && git describe --tags)\"; cat /out/share/doc/nerdctl-full/README.md; } > /out/share/doc/nerdctl-full/README.md.new; mv /out/share/doc/nerdctl-full/README.md.new /out/share/doc/nerdctl-full/README.md\nWORKDIR /go/src/github.com/containerd/nerdctl\nRUN BINDIR=/out/bin make binaries install\n# FIXME: `gomodjail pack` depends on QEMU for non-native architecture\n# TODO: gomodjail should provide a plain shell script that utilizes `zip(1)` for packing the self-extract archive, without running `gomodjail pack`..\nRUN /out/bin/gomodjail pack --go-mod=/go/src/github.com/containerd/nerdctl/go.mod /out/bin/nerdctl && \\\n  cp -a nerdctl.gomodjail /out/bin/\nCOPY README.md /out/share/doc/nerdctl/\nCOPY docs /out/share/doc/nerdctl/docs\nRUN (cd /out && find ! -type d | sort | xargs sha256sum > /tmp/SHA256SUMS ) && \\\n  mv /tmp/SHA256SUMS /out/share/doc/nerdctl-full/SHA256SUMS && \\\n  chown -R 0:0 /out\n\nFROM scratch AS out-full\nCOPY --from=build-full /out /\n\nFROM ubuntu:${UBUNTU_VERSION} AS base\n# fuse3 is required by stargz snapshotter\nRUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \\\n  apparmor \\\n  bash-completion \\\n  ca-certificates curl \\\n  iproute2 iptables \\\n  dbus dbus-user-session systemd systemd-sysv \\\n  fuse3\nCOPY --from=build-full /docker-entrypoint.sh /docker-entrypoint.sh\nCOPY --from=out-full / /usr/local/\nRUN perl -pi -e 's/multi-user.target/docker-entrypoint.target/g' /usr/local/lib/systemd/system/*.service && \\\n  systemctl enable containerd buildkit stargz-snapshotter && \\\n  mkdir -p /etc/bash_completion.d && \\\n  nerdctl completion bash >/etc/bash_completion.d/nerdctl && \\\n  mkdir -p -m 0755 /etc/cni\nCOPY ./Dockerfile.d/etc_containerd_config.toml /etc/containerd/config.toml\nCOPY ./Dockerfile.d/etc_buildkit_buildkitd.toml /etc/buildkit/buildkitd.toml\nVOLUME /var/lib/containerd\nVOLUME /var/lib/buildkit\nVOLUME /var/lib/containerd-stargz-grpc\nVOLUME /var/lib/nerdctl\nENTRYPOINT [\"/docker-entrypoint.sh\"]\nCMD [\"bash\", \"--login\", \"-i\"]\n\nFROM base AS test-integration\nARG DEBIAN_FRONTEND=noninteractive\n# `expect` package contains `unbuffer(1)`, which is used for emulating TTY for testing\n# `jq` is required to generate test summaries\nRUN apt-get update -qq && apt-get install -qq --no-install-recommends \\\n    software-properties-common \\\n    gnupg \\\n    gpg-agent \\\n    ca-certificates && \\\n    add-apt-repository ppa:criu/ppa && \\\n    apt-get update -qq && apt-get install -qq --no-install-recommends \\\n    expect \\\n    jq \\\n    git \\\n    make \\\n    criu\n# We wouldn't need this if Docker Hub could have \"golang:${GO_VERSION}-ubuntu\"\nCOPY --from=build-base /usr/local/go /usr/local/go\nARG TARGETARCH\nENV PATH=/usr/local/go/bin:$PATH\nARG GOTESTSUM_VERSION\nRUN GOBIN=/usr/local/bin go install gotest.tools/gotestsum@${GOTESTSUM_VERSION}\nCOPY . /go/src/github.com/containerd/nerdctl\nWORKDIR /go/src/github.com/containerd/nerdctl\nVOLUME /tmp\nENV CGO_ENABLED=0\n# copy cosign binary for integration test\nCOPY --from=ghcr.io/sigstore/cosign/cosign:v2.2.3@sha256:8fc9cad121611e8479f65f79f2e5bea58949e8a87ffac2a42cb99cf0ff079ba7 /ko-app/cosign /usr/local/bin/cosign\n# installing soci for integration test\nARG SOCI_SNAPSHOTTER_VERSION\nRUN fname=\"soci-snapshotter-${SOCI_SNAPSHOTTER_VERSION}-${TARGETOS:-linux}-${TARGETARCH:-amd64}.tar.gz\" && \\\n  curl -o \"${fname}\" -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/awslabs/soci-snapshotter/releases/download/v${SOCI_SNAPSHOTTER_VERSION}/${fname}\" && \\\n  tar -C /usr/local/bin -xvf \"${fname}\" soci soci-snapshotter-grpc && \\\n  mkdir -p /etc/soci-snapshotter-grpc && \\\n  touch /etc/soci-snapshotter-grpc/config.toml && \\\n  echo \"\\n[pull_modes]\\n  [pull_modes.soci_v1]\\n    enable = true\" >> /etc/soci-snapshotter-grpc/config.toml\n# enable offline ipfs for integration test\nCOPY --from=build-kubo /out/${TARGETARCH:-amd64}/* /usr/local/bin/\nCOPY ./Dockerfile.d/test-integration-etc_containerd-stargz-grpc_config.toml /etc/containerd-stargz-grpc/config.toml\nCOPY ./Dockerfile.d/test-integration-ipfs-offline.service /usr/local/lib/systemd/system/\nCOPY ./Dockerfile.d/test-integration-buildkit-nerdctl-test.service /usr/local/lib/systemd/system/\nCOPY ./Dockerfile.d/test-integration-soci-snapshotter.service /usr/local/lib/systemd/system/\nRUN cp /usr/local/bin/tini /usr/local/bin/tini-custom\n# using test integration containerd config\nCOPY ./Dockerfile.d/test-integration-etc_containerd_config.toml /etc/containerd/config.toml\n# install ipfs service. avoid using 5001(api)/8080(gateway) which are reserved by tests.\nRUN systemctl enable test-integration-ipfs-offline test-integration-buildkit-nerdctl-test test-integration-soci-snapshotter && \\\n  ipfs init && \\\n  ipfs config Addresses.API \"/ip4/127.0.0.1/tcp/5888\" && \\\n  ipfs config Addresses.Gateway \"/ip4/127.0.0.1/tcp/5889\"\n# install nydus components\nARG NYDUS_VERSION\nRUN curl -o nydus-static.tgz -fsSL --retry 5 --retry-delay 5 --retry-max-time 120 --connect-timeout 20 --proto '=https' --tlsv1.2 \"https://github.com/dragonflyoss/image-service/releases/download/${NYDUS_VERSION}/nydus-static-${NYDUS_VERSION}-linux-${TARGETARCH}.tgz\" && \\\n  tar xzf nydus-static.tgz && \\\n  mv nydus-static/nydus-image nydus-static/nydusd nydus-static/nydusify /usr/bin/ && \\\n  rm nydus-static.tgz\nCMD [\"./hack/test-integration.sh\"]\n\nFROM test-integration AS test-integration-rootless\n# Install SSH for creating systemd user session.\n# (`sudo` does not work for this purpose,\n#  OTOH `machinectl shell` can create the session but does not propagate exit code)\nRUN apt-get update -qq && apt-get install -qq --no-install-recommends \\\n  uidmap \\\n  openssh-server \\\n  openssh-client\n# TODO: update containerized-systemd to enable sshd by default, or allow `systemctl wants <TARGET> ssh` here\nRUN ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N '' && \\\n  useradd -m -s /bin/bash rootless && \\\n  mkdir -p -m 0700 /home/rootless/.ssh && \\\n  cp -a /root/.ssh/id_rsa.pub /home/rootless/.ssh/authorized_keys && \\\n  mkdir -p /home/rootless/.local/share && \\\n  chown -R rootless:rootless /home/rootless\nCOPY ./Dockerfile.d/etc_systemd_system_user@.service.d_delegate.conf /etc/systemd/system/user@.service.d/delegate.conf\n# ipfs daemon for rootless containerd will be enabled in /test-integration-rootless.sh\nRUN systemctl disable test-integration-ipfs-offline\nVOLUME /home/rootless/.local/share\nCOPY ./Dockerfile.d/test-integration-rootless.sh /\nRUN chmod a+rx /test-integration-rootless.sh\nCMD [\"/test-integration-rootless.sh\", \"./hack/test-integration.sh\"]\n\n# test for CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns\nFROM test-integration-rootless AS test-integration-rootless-port-slirp4netns\nCOPY ./Dockerfile.d/home_rootless_.config_systemd_user_containerd.service.d_port-slirp4netns.conf /home/rootless/.config/systemd/user/containerd.service.d/port-slirp4netns.conf\nRUN chown -R rootless:rootless /home/rootless/.config\n\nFROM base AS demo\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/SHA256SUMS",
    "content": "3edc52986c442576da856a66b59a61d16cf765359712c5ecf2d147c69f0df6e9  rootlesskit-aarch64.tar.gz\n6ce9eed50f9e12f18f3e5197cf93d226bc9290185880a626ab186244593d2eed  rootlesskit-armv7l.tar.gz\n730ef884439e2fe15551218b05d5c4f96d96d6945db8ad7e89b1d12946408a8d  rootlesskit-ppc64le.tar.gz\n05da5803d0f023ec51112bbdf8967a3e12ae19544f8c101a7f08f3bb9c6548fd  rootlesskit-riscv64.tar.gz\n199f6bfcd0495d0b944d95f70e6fa1177ace16d801e2693fdd86fdaafa69b01a  rootlesskit-s390x.tar.gz\nafc52e9fa2f7a2d4bb692f675cf3d2f70f3a184f02593e8b18cfbbbc34cbfd41  rootlesskit-x86_64.tar.gz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/buildg-v0.5.3",
    "content": "cf4c40c58ca795eeb6e75e2c6a0e5bb3a6a9c0623d51bc3b85163e5d483eeade  buildg-full-v0.5.3-linux-amd64.tar.gz\n47c479f2e5150c9c76294fa93a03ad20e5928f4315bf52ca8432bfb6707d4276  buildg-full-v0.5.3-linux-arm64.tar.gz\nc289a454ae8673ff99acf56dec9ba97274c20d2015e80f7ac3b8eb8e4f77888f  buildg-v0.5.3-linux-amd64.tar.gz\nb2e244250ce7ea5c090388f2025a9c546557861d25bba7b0666aa512f01fa6cd  buildg-v0.5.3-linux-arm64.tar.gz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/buildkit-v0.26.3",
    "content": "249ae16ba4be59fadb51a49ff4d632bbf37200e2b6e187fa8574f0f1bce8166b  buildkit-v0.26.3.linux-amd64.tar.gz\na98829f1b1b9ec596eb424dd03f03b9c7b596edac83e6700adf83ba0cb0d5f80  buildkit-v0.26.3.linux-arm64.tar.gz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/cni-plugins-v1.9.0",
    "content": "58c03705426e929658f45a851df15a86d06ef680cacbf3f2dc127731ca265c28  cni-plugins-linux-amd64-v1.9.0.tgz\n2596ef56329dd1269026f46b8df262f09ba43c92dbfb940e1e69fbccccd30a29  cni-plugins-linux-arm64-v1.9.0.tgz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/containerd-fuse-overlayfs-v2.1.7",
    "content": "d54148043c22381af89cec2a167431e40668716404a1eb682ca69dfb890376f3  containerd-fuse-overlayfs-2.1.7-linux-amd64.tar.gz\na301030391d51356065f628b5e6e5a5a8c55f1978289eb71d8f5284af7a81eda  containerd-fuse-overlayfs-2.1.7-linux-arm-v7.tar.gz\n94ed6c2c3bece42e0c789ea056565b64fe487de4644121ee0dfb8acd8ef9369c  containerd-fuse-overlayfs-2.1.7-linux-arm64.tar.gz\n1bfb1f86894b640781d837ec0f66997222b419532fae730579140dbc1c7ea858  containerd-fuse-overlayfs-2.1.7-linux-ppc64le.tar.gz\n9f2ef69b06229f5357f3fc23524922cea6616663ff220979a110a7742aaffee6  containerd-fuse-overlayfs-2.1.7-linux-riscv64.tar.gz\n03f61035cef5fff33c5084c55f133d0340597520d8d12112970609dff0bd1e7a  containerd-fuse-overlayfs-2.1.7-linux-s390x.tar.gz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/fuse-overlayfs-v1.16",
    "content": "6c9ee54166fe7d33ebbfb085812585441f22ebe2a24a868d0a878d3127bcb89e  fuse-overlayfs-aarch64\nfc2a73ace8eb6a0553204532de615d782cb98d86deeb0fa7b5d14347d0b95823  fuse-overlayfs-armv7l\n3c07b76b432a5b4e5e0ccd986919b478d096701178617175b0c71bcce7c6f6a0  fuse-overlayfs-ppc64le\n404fd7a762255d554e70849612fb6979639e1eb23a740487dbe3bac2bccc37c1  fuse-overlayfs-riscv64\n9e96cfe091b4342b8de3e239a96d5fecfb8692fbb4a201c256790c270526fd1b  fuse-overlayfs-s390x\n30c6b9e192600d6854e13397974c709d7cabd980b7d1a4d67defd8eb69677e91  fuse-overlayfs-x86_64\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/rootlesskit-v1.1.1",
    "content": "b74c577abd6ad721e0b7e10a74f4c5ac26cb3afe005ad3d28d4d7912c356079f  rootlesskit-aarch64.tar.gz\n95c27e6808c942c67ab93d94e37bada3a62cfc47de848101889f8e3ba5c9f7dd  rootlesskit-armv7l.tar.gz\ndf35c74cd030e1b3978f28d1cb7c909da2ab962fb0c9369463d43a89b9f16cc2  rootlesskit-ppc64le.tar.gz\n79af3e96e9d6deddc5faa4680de7e28120ae333386c48a30e79fe156f17bad9b  rootlesskit-riscv64.tar.gz\n32da9a11b67340ff498de8a3268673277a1e1d9e9d8d5f619bbf09305beaaa6c  rootlesskit-s390x.tar.gz\n3c83affbb405cafe2d32e2e24462af9b4dcfa19e3809030012ad0d4e3fd49e8f  rootlesskit-x86_64.tar.gz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/rootlesskit-v2.3.6",
    "content": "3edc52986c442576da856a66b59a61d16cf765359712c5ecf2d147c69f0df6e9  rootlesskit-aarch64.tar.gz\n6ce9eed50f9e12f18f3e5197cf93d226bc9290185880a626ab186244593d2eed  rootlesskit-armv7l.tar.gz\n730ef884439e2fe15551218b05d5c4f96d96d6945db8ad7e89b1d12946408a8d  rootlesskit-ppc64le.tar.gz\n05da5803d0f023ec51112bbdf8967a3e12ae19544f8c101a7f08f3bb9c6548fd  rootlesskit-riscv64.tar.gz\n199f6bfcd0495d0b944d95f70e6fa1177ace16d801e2693fdd86fdaafa69b01a  rootlesskit-s390x.tar.gz\nafc52e9fa2f7a2d4bb692f675cf3d2f70f3a184f02593e8b18cfbbbc34cbfd41  rootlesskit-x86_64.tar.gz\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/slirp4netns-v1.3.3",
    "content": "d0e6a13342efbedb8b7454629a0e9ce9b7a937c261034c85f46ed81af76307d8  SOURCE_DATE_EPOCH\n1ca9d2f5f1fb4beb91f354653e5dad35b95c049afb264268d99a96ff2a10d903  slirp4netns-aarch64\n3e209d1c56fccbe627a038d311b233c15e8d914b30f9b981b5ed78b98e836859  slirp4netns-armv7l\n4d1003a98103ee170c0fcd4aad8a5e0ba7aa2e70fbca883cbb6a39f40447c8da  slirp4netns-ppc64le\n06a13b398d88120097b20dace966d7dd5e2fbfd284b95a086347808df392200e  slirp4netns-riscv64\n23d4a206edd6d3fc9c86f8b05c0881ff77a607b8d471f20964ad9f9c3f3176b1  slirp4netns-s390x\n5618887b671a30a2f7548f2bdf7fba98a53981abc80cfd3183cd28b4dc8b2b97  slirp4netns-x86_64\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/stargz-snapshotter-v0.18.1",
    "content": "f8f106a61b9fc797a6336d6c06435cdbf8b896f3f49fdc5288e08e87dff6bbdf  stargz-snapshotter-v0.18.1-linux-amd64.tar.gz\n643d04f5e97e83606b9ee129c2c33513df13a091dbc1dc084256d13a1034b749  stargz-snapshotter-v0.18.1-linux-arm64.tar.gz\nf1cf855870af16a653d8acb9daa3edf84687c2c05323cb958f078fb148af3eec  stargz-snapshotter.service\n"
  },
  {
    "path": "Dockerfile.d/SHA256SUMS.d/tini-v0.19.0",
    "content": "c5b0666b4cb676901f90dfcb37106783c5fe2077b04590973b885950611b30ee  tini-static-amd64\neae1d3aa50c48fb23b8cbdf4e369d0910dfc538566bfd09df89a774aa84a48b9  tini-static-arm64\n"
  },
  {
    "path": "Dockerfile.d/etc_buildkit_buildkitd.toml",
    "content": "[worker.oci]\n  enabled = false\n\n[worker.containerd]\n  enabled = true\n  namespace = \"default\"\n"
  },
  {
    "path": "Dockerfile.d/etc_containerd_config.toml",
    "content": "version = 2\n\n# Enable stargz snapshotter\n[proxy_plugins]\n  [proxy_plugins.stargz]\n    type = \"snapshot\"\n    address = \"/run/containerd-stargz-grpc/containerd-stargz-grpc.sock\"\n  [proxy_plugins.stargz.exports]\n    root = \"/var/lib/containerd-stargz-grpc/\"\n    enable_remote_snapshot_annotations = \"true\"\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"overlayfs\"\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"stargz\"\n"
  },
  {
    "path": "Dockerfile.d/etc_systemd_system_user@.service.d_delegate.conf",
    "content": "[Service]\nDelegate=yes\n"
  },
  {
    "path": "Dockerfile.d/home_rootless_.config_systemd_user_containerd.service.d_port-slirp4netns.conf",
    "content": "[Service]\n# Change the port driver from \"builtin\" to \"slirp4netns\". Only used in CI.\nEnvironment=\"CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns\"\n"
  },
  {
    "path": "Dockerfile.d/test-integration-buildkit-nerdctl-test.service",
    "content": "# Copyright The containerd Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Copied from released buildkit.service\n\n[Unit]\nAfter=network.target local-fs.target\nDescription=buildkit daemon for integration test (namespace: nerdctl-test)\n\n[Service]\nExecStartPre=-/sbin/modprobe overlay\nExecStart=/usr/local/bin/buildkitd --oci-worker=false --containerd-worker=true --addr=\"unix:///run/buildkit-nerdctl-test/buildkitd.sock\" --root=/var/lib/buildkit-nerdctl-test --containerd-worker-namespace=nerdctl-test\n\nType=notify\nDelegate=yes\nKillMode=process\nRestart=always\nRestartSec=5\n# Having non-zero Limit*s causes performance problems due to accounting overhead\n# in the kernel. We recommend using cgroups to do container-local accounting.\nLimitNPROC=infinity\nLimitCORE=infinity\nLimitNOFILE=infinity\n# Comment TasksMax if your systemd version does not supports it.\n# Only systemd 226 and above support this version.\nTasksMax=infinity\nOOMScoreAdjust=-999\n\n[Install]\nWantedBy=docker-entrypoint.target\n"
  },
  {
    "path": "Dockerfile.d/test-integration-etc_containerd-stargz-grpc_config.toml",
    "content": "version = 2\n\n# Enable IPFS\nipfs = true"
  },
  {
    "path": "Dockerfile.d/test-integration-etc_containerd_config.toml",
    "content": "version = 2\n\n# Enable stargz snapshotter\n[proxy_plugins]\n  [proxy_plugins.stargz]\n    type = \"snapshot\"\n    address = \"/run/containerd-stargz-grpc/containerd-stargz-grpc.sock\"\n  [proxy_plugins.stargz.exports]\n    root = \"/var/lib/containerd-stargz-grpc/\"\n    enable_remote_snapshot_annotations = \"true\"\n\n# Enable soci snapshotter\n  [proxy_plugins.soci]\n    type = \"snapshot\"\n    address = \"/run/soci-snapshotter-grpc/soci-snapshotter-grpc.sock\"\n  [proxy_plugins.soci.exports]\n    root = \"/var/lib/soci-snapshotter-grpc\"\n    enable_remote_snapshot_annotations = \"true\"\n\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"overlayfs\"\n\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"soci\"\n\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"stargz\"\n"
  },
  {
    "path": "Dockerfile.d/test-integration-ipfs-offline.service",
    "content": "[Unit]\nDescription=ipfs daemon for integration test (offline)\n\n[Service]\nExecStart=ipfs daemon --init --offline\nEnvironment=IPFS_PATH=\"%h/.ipfs\"\n\n[Install]\nWantedBy=docker-entrypoint.target\n"
  },
  {
    "path": "Dockerfile.d/test-integration-rootless.sh",
    "content": "#!/bin/bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -eux -o pipefail\nif [[ \"$(id -u)\" = \"0\" ]]; then\n  # Ensure securityfs is mounted for apparmor to work\n  if ! mountpoint -q /sys/kernel/security; then\n    mount -tsecurityfs securityfs /sys/kernel/security\n  fi\n\tif [ -e /sys/kernel/security/apparmor/profiles ]; then\n\t\t# Load the \"nerdctl-default\" profile for TestRunApparmor\n\t\tnerdctl apparmor load\n\tfi\n\n\t: \"${WORKAROUND_ISSUE_622:=}\"\n\tif [[ \"$WORKAROUND_ISSUE_622\" != \"\" ]]; then\n\t\ttouch /workaround-issue-622\n\tfi\n\n\t# Switch to the rootless user via SSH\n\tsystemctl start ssh\n\texec ssh -o StrictHostKeyChecking=no rootless@localhost \"$0\" \"$@\"\nelse\n\tcontainerd-rootless-setuptool.sh install\n\tif grep -q \"options use-vc\" /etc/resolv.conf; then\n\t\tcontainerd-rootless-setuptool.sh nsenter -- sh -euc 'echo \"options use-vc\" >>/etc/resolv.conf'\n\tfi\n\n\tif [[ -e /workaround-issue-622 ]]; then\n\t\techo \"WORKAROUND_ISSUE_622: Not enabling BuildKit (https://github.com/containerd/nerdctl/issues/622)\" >&2\n\telse\n\t\tCONTAINERD_NAMESPACE=\"nerdctl-test\" containerd-rootless-setuptool.sh install-buildkit-containerd\n\tfi\n\tcontainerd-rootless-setuptool.sh install-stargz\n\tif [ ! -f \"/home/rootless/.config/containerd/config.toml\" ] ; then\n\t\techo \"version = 2\" > /home/rootless/.config/containerd/config.toml\n\tfi\n\tcat <<EOF >>/home/rootless/.config/containerd/config.toml\n[proxy_plugins]\n  [proxy_plugins.\"stargz\"]\n    type = \"snapshot\"\n    address = \"/run/user/$(id -u)/containerd-stargz-grpc/containerd-stargz-grpc.sock\"\n  [proxy_plugins.stargz.exports]\n    root = \"/home/rootless/.local/share/containerd-stargz-grpc/\"\n    enable_remote_snapshot_annotations = \"true\"\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"overlayfs\"\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"stargz\"\nEOF\n\tsystemctl --user restart containerd.service\n\tcontainerd-rootless-setuptool.sh -- install-ipfs --init --offline # offline ipfs daemon for testing\n\techo \"ipfs = true\" >>/home/rootless/.config/containerd-stargz-grpc/config.toml\n\tsystemctl --user restart stargz-snapshotter.service\n\texport IPFS_PATH=\"/home/rootless/.local/share/ipfs\"\n\tcontainerd-rootless-setuptool.sh install-bypass4netnsd\n\t# Once ssh-ed, we lost the Dockerfile working dir, so, get back in the nerdctl checkout\n\tcd /go/src/github.com/containerd/nerdctl\n\t# We also lose the PATH (and SendEnv=PATH would require sshd config changes)\n\texec env PATH=\"/usr/local/go/bin:$PATH\" \"$@\"\nfi\n"
  },
  {
    "path": "Dockerfile.d/test-integration-soci-snapshotter.service",
    "content": "[Unit]\nDescription=soci snapshotter containerd plugin for integration test\nDocumentation=https://github.com/awslabs/soci-snapshotter\nAfter=network.target\nBefore=containerd.service\n\n[Service]\nType=notify\nExecStartPre=/bin/bash -c 'mkdir -p /var/lib/soci-snapshotter-grpc && mount -t tmpfs none /var/lib/soci-snapshotter-grpc'\nExecStart=/usr/local/bin/soci-snapshotter-grpc\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=docker-entrypoint.target\n"
  },
  {
    "path": "EMERITUS.md",
    "content": "See [`MAINTAINERS`](./MAINTAINERS) for the current active maintainers.\n- - -\n# nerdctl Emeritus Maintainers\n\n## Committers\n### Ye Sijun ([@junnplus](https://github.com/junnplus))\nYe Sijun (GitHub ID [@junnplus](https://github.com/junnplus)) served as\na Committer of nerdctl from November 2022 to June 2024.\nPrior to his role as a Committer, Sijun served as a Reviewer since February 2022.\n\nSijun has made [significant improvements](https://github.com/containerd/nerdctl/pulls?q=author%3Ajunnplus+)\nespecially to `nerdctl compose`, IPAM, and cosign integration.\n\n## Reviewers\n### Hanchin Hsieh ([@yuchanns](https://github.com/yuchanns))\nHanchin Hsieh (GitHub ID [@yuchanns](https://github.com/yuchanns)) served as\na Reviewer of nerdctl from November 2022 to June 2024.\n\nHanchin has made significant contributions such as the addition of\n[syslog driver](https://github.com/containerd/nerdctl/pull/1377) and\n[IPv6 networking](https://github.com/containerd/nerdctl/pull/1558).\n\n### Manu Gupta ([@manugupt1](https://github.com/manugupt1))\nManu Gupta (GitHub ID [@manugupt1](https://github.com/manugupt1)) served as\na Reviewer of nerdctl from 2022 to August 2025.\n\nManu has made [significant improvements](https://github.com/containerd/nerdctl/pulls?q=author%3Amanugupt1+)\nespecially to image and volume management, container runtime features, build system enhancements,\nand CI/CD infrastructure. Notable contributions include image filtering capabilities, volume size\ninspection, Docker Compose enhancements, and multi-architecture build support.\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS",
    "content": "# nerdctl maintainers\n#\n# As a containerd sub-project, containerd maintainers are also included from https://github.com/containerd/project/blob/main/MAINTAINERS.\n# See https://github.com/containerd/project/blob/main/GOVERNANCE.md for description of maintainer role\n#\n# See also MAINTAINERS_GUIDE.md\n\n# CORE COMMITTERS who regularly contribute to nerdctl\n# (Extracted from https://github.com/containerd/project/blob/main/MAINTAINERS for ease of reference)\n# GitHub ID, Name, Email address, GPG fingerprint\n\"AkihiroSuda\",\"Akihiro Suda\",\"akihiro.suda.cz@hco.ntt.co.jp\",\"C020 EA87 6CE4 E06C 7AB9 5AEF 4952 4C6F 9F63 8F1A\"\n\n# COMMITTERS\n# GitHub ID, Name, Email address, GPG fingerprint\n\"ktock\",\"Kohei Tokunaga\",\"ktokunaga.mail@gmail.com\",\"\"\n\"fahedouch\",\"Fahed Dorgaa\",\"fahed.dorgaa@gmail.com\",\"EE7A 5503 CE0D 38AC 5B95  A500 F35F F497 60A8 65FA\"\n\"Zheaoli\", \"Zheao Li\", \"me@manjusaka.me\",\"6E0D D9FA BAD5 AF61 D884 01EE 878F 445D 9C6C E65E\"\n\"djdongjin\", \"Jin Dong\", \"djdongjin95@gmail.com\",\"\"\n\"yankay\", \"Kay Yan\", \"kay.yan@daocloud.io\", \"\"\n\"ChengyuZhu6\",\"Chengyu Zhu\",\"hudson@cyzhu.com\",\"\"\n\n# REVIEWERS\n# GitHub ID, Name, Email address, GPG fingerprint\n\"jsturtevant\",\"James Sturtevant\",\"jstur@microsoft.com\",\"\"\n\"Shubhranshu153\",\"Shubharanshu Mahapatra\",\"shubhum@amazon.com\",\"\"\n\"haytok\",\"Hayato Kiwata\",\"haytok@amazon.co.jp\",\"B485 C5AA 6220 0A06 78FD 294D FA4F 2421 1D65 269F\"\n\n# EMERITUS\n# See EMERITUS.md\n"
  },
  {
    "path": "MAINTAINERS_GUIDE.md",
    "content": "# Maintainers' guide\n\n## Maintainer list\n\n- Core: https://github.com/containerd/project/blob/main/MAINTAINERS\n- Non-core: [`MAINTAINERS`](./MAINTAINERS)\n\n## Governance\n\nSee https://github.com/containerd/project/blob/main/GOVERNANCE.md\n\n## Creating a release\n\nEligibility to be a release manager:\n- MUST be an active Commiter (Core or Non-core)\n- MUST have the GPG fingerprint listed in [`MAINTAINERS`](./MAINTAINERS)\n- MUST upload the GPG public key to `https://github.com/USERNAME.gpg`\n- MUST protect the GPG key with a passphrase or a hardware token.\n\nRelease steps:\n- Open a PR to keep the dependencies up-to-date.\n  Update `go.mod` for Go dependencies (usually Dependabot automatically updates them).\n  Update `Dockerfile` and relevant files under `Dockerfile.d` for `nerdctl-full` dependencies.\n- Open an issue to propose making a new release.\n  The proposal should be public, with an exception for vulnerability fixes.\n  If this is the first time for you to take a role of release management,\n  you SHOULD make a beta (or alpha, RC) release as an exercise before releasing GA.\n- Make sure that all the merged PRs are associated with the correct [Milestone](https://github.com/containerd/nerdctl/milestones).\n- Run `git tag --sign vX.Y.Z-beta.W` .\n- Run `git push UPSTREAM vX.Y.Z-beta.W` .\n- Wait for the `Release` action on GitHub Actions to complete. A draft release will appear in https://github.com/containerd/nerdctl/releases .\n- Download `SHA256SUMS` from the draft release, and confirm that it corresponds to the hashes printed in the build logs on the `Release` action.\n- Sign `SHA256SUMS` with `gpg --detach-sign -a SHA256SUMS` to produce `SHA256SUMS.asc`, and upload it to the draft release.\n- Add release notes in the draft release, to explain the changes and show appreciation to the contributors.\n  Make sure to fulfill the `Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE])` line with your name.\n  e.g., `Release manager: Akihiro Suda (@AkihiroSuda)` .\n- Click the `Set as a pre-release` checkbox if this release is a beta (or alpha, RC).\n- Click the `Publish release` button.\n- Close the [Milestone](https://github.com/containerd/nerdctl/milestones).\n"
  },
  {
    "path": "Makefile",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# -----------------------------------------------------------------------------\n# Portions from https://github.com/kubernetes-sigs/cri-tools/blob/v1.19.0/Makefile\n# Copyright The Kubernetes Authors.\n# Licensed under the Apache License, Version 2.0\n# -----------------------------------------------------------------------------\n\n##########################\n# Configuration\n##########################\nPACKAGE := \"github.com/containerd/nerdctl/v2\"\n\nDOCKER ?= docker\nGO ?= go\nGOOS ?= $(shell $(GO) env GOOS)\nGOARCH ?= $(shell $(GO) env GOARCH)\nifeq ($(GOOS),windows)\n\tBIN_EXT := .exe\nendif\n\n# distro builders might want to override these\nPREFIX  ?= /usr/local\nBINDIR  ?= $(PREFIX)/bin\nDATADIR ?= $(PREFIX)/share\nDOCDIR  ?= $(DATADIR)/doc\n\nBINARY ?= \"nerdctl\"\nMAKEFILE_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))\nVERSION ?= $(shell git -C $(MAKEFILE_DIR) describe --match 'v[0-9]*' --dirty='.m' --always --tags 2>/dev/null || echo no_git_information)\nVERSION_TRIMMED := $(VERSION:v%=%)\nREVISION ?= $(shell git -C $(MAKEFILE_DIR) rev-parse HEAD 2>/dev/null || echo no_git_information)$(shell if ! git -C $(MAKEFILE_DIR) diff --no-ext-diff --quiet --exit-code 2>/dev/null; then echo .m; fi)\nLINT_COMMIT_RANGE ?= main..HEAD\nGO_BUILD_LDFLAGS ?= -s -w\nGO_BUILD_FLAGS ?=\n\nBUILDTAGS ?=\nGO_TAGS=$(if $(BUILDTAGS),-tags \"$(strip $(BUILDTAGS))\",)\n\n##########################\n# Helpers\n##########################\nifdef VERBOSE\n\tVERBOSE_FLAG := -v\n\tVERBOSE_FLAG_LONG := --verbose\nendif\n\nexport GO_BUILD=CGO_ENABLED=0 GOOS=$(GOOS) $(GO) -C $(MAKEFILE_DIR) build $(GO_TAGS) -ldflags \"$(GO_BUILD_LDFLAGS) $(VERBOSE_FLAG) -X $(PACKAGE)/pkg/version.Version=$(VERSION) -X $(PACKAGE)/pkg/version.Revision=$(REVISION)\"\n\nifndef NO_COLOR\n    NC := \\033[0m\n    GREEN := \\033[1;32m\n    ORANGE := \\033[1;33m\nendif\n\nrecursive_wildcard=$(wildcard $1$2) $(foreach e,$(wildcard $1*),$(call recursive_wildcard,$e/,$2))\n\ndefine title\n\t@printf \"$(GREEN)____________________________________________________________________________________________________\\n\"\n\t@printf \"$(GREEN)%*s\\n\" $$(( ( $(shell echo \"🤓$(1) 🤓\" | wc -c ) + 100 ) / 2 )) \"🤓$(1) 🤓\"\n\t@printf \"$(GREEN)____________________________________________________________________________________________________\\n$(ORANGE)\"\nendef\n\ndefine footer\n\t@printf \"$(GREEN)> %s: done!\\n\" \"$(1)\"\n\t@printf \"$(GREEN)____________________________________________________________________________________________________\\n$(NC)\"\nendef\n\n##########################\n# High-level tasks definitions\n##########################\nall: binaries\n\nlint: lint-go-all lint-yaml lint-shell lint-commits lint-mod lint-licenses-all\n\nfix: fix-mod fix-go-all\n\n# TODO: fix race task and add it\ntest: test-unit # test-unit-race test-unit-bench\n\nhelp:\n\t@echo \"Usage: make <target>\"\n\t@echo\n\t@echo \" * 'lint' - Run linters against codebase.\"\n\t@echo \" * 'fix' - Automatically fixes imports, modules, and simple formatting.\"\n\t@echo \" * 'test' - Run basic unit testing.\"\n\t@echo \" * 'binaries' - Build nerdctl.\"\n\t@echo \" * 'install' - Install binaries to system locations.\"\n\t@echo \" * 'clean' - Clean artifacts.\"\n\n##########################\n# Building and installation tasks\n##########################\nbinaries: $(CURDIR)/_output/$(BINARY)$(BIN_EXT)\n\n$(CURDIR)/_output/$(BINARY)$(BIN_EXT):\n\t$(call title, $@: $(GOOS)/$(GOARCH))\n\t$(GO_BUILD) $(GO_BUILD_FLAGS) $(VERBOSE_FLAG) -o $(CURDIR)/_output/$(BINARY)$(BIN_EXT) ./cmd/nerdctl\n\t$(call footer, $@)\n\ninstall:\n\t$(call title, $@)\n\tinstall -D -m 755 $(CURDIR)/_output/$(BINARY) $(DESTDIR)$(BINDIR)/$(BINARY)\n\tinstall -D -m 755 $(MAKEFILE_DIR)/extras/rootless/containerd-rootless.sh $(DESTDIR)$(BINDIR)/containerd-rootless.sh\n\tinstall -D -m 755 $(MAKEFILE_DIR)/extras/rootless/containerd-rootless-setuptool.sh $(DESTDIR)$(BINDIR)/containerd-rootless-setuptool.sh\n\tinstall -D -m 644 -t $(DESTDIR)$(DOCDIR)/nerdctl $(MAKEFILE_DIR)/docs/*.md\n\t$(call footer, $@)\n\nclean:\n\t$(call title, $@)\n\tfind . -name \\*~ -delete\n\tfind . -name \\#\\* -delete\n\trm -rf $(CURDIR)/_output/* $(MAKEFILE_DIR)/vendor\n\t$(call footer, $@)\n\n##########################\n# Linting tasks\n##########################\nlint-go:\n\t$(call title, $@: $(GOOS))\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& golangci-lint run $(VERBOSE_FLAG_LONG) ./...\n\t$(call footer, $@)\n\nlint-go-all:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& GOOS=linux make lint-go \\\n\t\t&& GOOS=windows make lint-go \\\n\t\t&& GOOS=freebsd make lint-go \\\n\t\t&& GOOS=darwin make lint-go\n\t$(call footer, $@)\n\nlint-yaml:\n\t$(call title, $@)\n\tcd $(MAKEFILE_DIR) \\\n\t\t&& yamllint .\n\t$(call footer, $@)\n\nlint-shell: $(call recursive_wildcard,$(MAKEFILE_DIR)/,*.sh)\n\t$(call title, $@)\n\tshellcheck -a -x $^\n\t$(call footer, $@)\n\nlint-commits:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& git-validation $(VERBOSE_FLAG) -run DCO,short-subject,dangling-whitespace -range \"$(LINT_COMMIT_RANGE)\"\n\t$(call footer, $@)\n\nlint-mod:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go mod tidy --diff\n\t$(call footer, $@)\n\n# FIXME: go-licenses cannot find LICENSE from root of repo when submodule is imported:\n# https://github.com/google/go-licenses/issues/186\n# This is impacting gotest.tools\n# FIXME: go-base36 is multi-license (MIT/Apache), using a custom boilerplate file that go-licenses fails to understand\nlint-licenses:\n\t$(call title, $@: $(GOOS))\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go-licenses check --include_tests --allowed_licenses=Apache-2.0,BSD-2-Clause,BSD-2-Clause-FreeBSD,BSD-3-Clause,MIT,ISC,Python-2.0,PostgreSQL,X11,Zlib \\\n\t\t  --ignore gotest.tools \\\n\t\t  --ignore github.com/multiformats/go-base36 \\\n\t\t  ./...\n\t$(call footer, $@)\n\nlint-licenses-all:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& GOOS=linux make lint-licenses \\\n\t\t&& GOOS=windows make lint-licenses \\\n\t\t&& GOOS=freebsd make lint-licenses \\\n\t\t&& GOOS=darwin make lint-licenses\n\t$(call footer, $@)\n\n##########################\n# Automated fixing tasks\n##########################\nfix-go:\n\t$(call title, $@: $(GOOS))\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& golangci-lint run --fix\n\t$(call footer, $@)\n\nfix-go-all:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& GOOS=linux make fix-go \\\n\t\t&& GOOS=windows make fix-go \\\n\t\t&& GOOS=freebsd make fix-go \\\n\t\t&& GOOS=darwin make fix-go\n\t$(call footer, $@)\n\nfix-mod:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go mod tidy\n\t$(call footer, $@)\n\n##########################\n# Development tools installation\n##########################\ninstall-dev-tools:\n\t$(call title, $@)\n\t# golangci: v2.4.0 (2025-08-14)\n\t# git-validation: main (2025-02-25)\n\t# ltag: main (2025-03-04)\n\t# go-licenses: v2.0.0-alpha.1 (2024-06-27)\n\t# stubbing go-licenses with dependency upgrade due to non-compatibility with golang 1.25rc1\n\t# Issue: https://github.com/google/go-licenses/issues/312\n\t@cd $(MAKEFILE_DIR) \\\n\t        && go install github.com/Shubhranshu153/go-licenses/v2@f8c503d1357dffb6c97ed3b94e912ab294dde24a \\\n\t\t&& go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@43d03392d7dc3746fa776dbddd66dfcccff70651 \\\n\t\t&& go install github.com/vbatts/git-validation@7b60e35b055dd2eab5844202ffffad51d9c93922 \\\n\t\t&& go install github.com/containerd/ltag@66e6a514664ee2d11a470735519fa22b1a9eaabd \\\n\t\t&& go install gotest.tools/gotestsum@0d9599e513d70e5792bb9334869f82f6e8b53d4d\n\t@echo \"Remember to add \\$$HOME/go/bin to your path\"\n\t$(call footer, $@)\n\n##########################\n# Testing tasks\n##########################\ntest-unit:\n\t$(call title, $@)\n\t@go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/pkg/...\n\t$(call footer, $@)\n\ntest-unit-bench:\n\t$(call title, $@)\n\t@go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/pkg/... -bench=.\n\t$(call footer, $@)\n\ntest-unit-race:\n\t$(call title, $@)\n\t@go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/pkg/... -race\n\t$(call footer, $@)\n\n##########################\n# Release tasks\n##########################\n# Note that these options will not work on macOS - unless you use gnu-tar instead of tar\nTAR_OWNER0_FLAGS=--owner=0 --group=0\nTAR_FLATTEN_FLAGS=--transform 's/.*\\///g'\n\ndefine make_artifact_full_linux\n\t$(DOCKER) build --secret id=github_token,env=GITHUB_TOKEN --output type=tar,dest=$(CURDIR)/_output/nerdctl-full-$(VERSION_TRIMMED)-linux-$(1).tar --target out-full --platform $(1) --build-arg GO_VERSION -f $(MAKEFILE_DIR)/Dockerfile $(MAKEFILE_DIR)\n\tgzip -9 $(CURDIR)/_output/nerdctl-full-$(VERSION_TRIMMED)-linux-$(1).tar\nendef\n\nartifacts: clean\n\t$(call title, $@)\n\tGOOS=linux GOARCH=amd64       make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-amd64.tar.gz   $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=linux GOARCH=arm64       make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-arm64.tar.gz   $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=linux GOARCH=arm GOARM=7 make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-arm-v7.tar.gz  $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=linux GOARCH=loong64     make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-loong64.tar.gz   $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=linux GOARCH=ppc64le     make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-ppc64le.tar.gz $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=linux GOARCH=riscv64     make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-riscv64.tar.gz $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=linux GOARCH=s390x       make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-linux-s390x.tar.gz   $(CURDIR)/_output/nerdctl $(MAKEFILE_DIR)/extras/rootless/*\n\n\tGOOS=windows GOARCH=amd64     make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-windows-amd64.tar.gz $(CURDIR)/_output/nerdctl.exe\n\n\tGOOS=freebsd GOARCH=amd64     make -C $(CURDIR) -f $(MAKEFILE_DIR)/Makefile binaries\n\ttar $(TAR_OWNER0_FLAGS) $(TAR_FLATTEN_FLAGS) -czvf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-freebsd-amd64.tar.gz $(CURDIR)/_output/nerdctl\n\n\trm -f $(CURDIR)/_output/nerdctl $(CURDIR)/_output/nerdctl.exe\n\n\t$(call make_artifact_full_linux,amd64)\n\t$(call make_artifact_full_linux,arm64)\n\n\t$(GO) -C $(MAKEFILE_DIR) mod vendor\n\ttar $(TAR_OWNER0_FLAGS) -czf $(CURDIR)/_output/nerdctl-$(VERSION_TRIMMED)-go-mod-vendor.tar.gz $(MAKEFILE_DIR)/go.mod $(MAKEFILE_DIR)/go.sum $(MAKEFILE_DIR)/vendor\n\t$(call footer, $@)\n\n.PHONY: \\\n\tall \\\n\tlint \\\n\tfix \\\n\ttest \\\n\thelp \\\n\tbinaries \\\n\tinstall \\\n\tclean \\\n\tlint-go lint-go-all lint-yaml lint-shell lint-commits lint-mod lint-licenses lint-licenses-all \\\n\tfix-go fix-go-all fix-mod \\\n\tinstall-dev-tools \\\n\ttest-unit test-unit-race test-unit-bench \\\n\tartifacts\n"
  },
  {
    "path": "NOTICE",
    "content": "nerdctl\nCopyright The containerd Authors.\n\nThis project contains portions of other projects that are licensed under the terms of Apache License 2.0.\nThe NOTICE files of those projects are replicated here.\n\n=== https://github.com/moby/moby , https://github.com/docker/cli ===\nhttps://github.com/moby/moby/blob/v20.10.14/LICENSE , https://github.com/docker/cli/blob/v20.10.14/LICENSE\nhttps://github.com/moby/moby/blob/v20.10.14/NOTICE , https://github.com/docker/cli/blob/v20.10.14/NOTICE\n\n> Docker\n> Copyright 2012-2017 Docker, Inc.\n>\n> This product includes software developed at Docker, Inc. (https://www.docker.com).\n>\n> This product contains software (https://github.com/creack/pty) developed\n> by Keith Rarick, licensed under the MIT License.\n>\n> The following is courtesy of our legal counsel:\n>\n>\n> Use and transfer of Docker may be subject to certain restrictions by the\n> United States and other governments.\n> It is your responsibility to ensure that your use and/or transfer does not\n> violate applicable laws.\n>\n> For more information, please see https://www.bis.doc.gov\n>\n> See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.\n\n=== https://github.com/docker/compose ===\nhttps://github.com/docker/compose/blob/v2.4.1/LICENSE\nhttps://github.com/docker/compose/blob/v2.4.1/NOTICE\n\n> Docker Compose V2\n> Copyright 2020 Docker Compose authors\n>\n> This product includes software developed at Docker, Inc. (https://www.docker.com).\n"
  },
  {
    "path": "README.md",
    "content": "[[⬇️ **Download]**](https://github.com/containerd/nerdctl/releases)\n[[📖 **Command reference]**](./docs/command-reference.md)\n[[❓**FAQs & Troubleshooting]**](./docs/faq.md)\n[[📚 **Additional documents]**](#additional-documents)\n\n# nerdctl: Docker-compatible CLI for containerd\n\n<picture>\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"docs/images/nerdctl.svg\">\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/images/nerdctl-white.svg\">\n  <img alt=\"logo\" src=\"docs/images/nerdctl.svg\">\n</picture>\n\n`nerdctl` is a Docker-compatible CLI for [contai**nerd**](https://containerd.io).\n\n ✅ Same UI/UX as `docker`\n\n ✅ Supports Docker Compose (`nerdctl compose up`)\n\n ✅ [Optional] Supports [rootless mode, without slirp overhead (bypass4netns)](./docs/rootless.md)\n\n ✅ [Optional] Supports lazy-pulling ([Stargz](./docs/stargz.md), [Nydus](./docs/nydus.md), [OverlayBD](./docs/overlaybd.md))\n\n ✅ [Optional] Supports [encrypted images (ocicrypt)](./docs/ocicrypt.md)\n\n ✅ [Optional] Supports [P2P image distribution (IPFS)](./docs/ipfs.md) (\\*1)\n\n ✅ [Optional] Supports [container image signing and verifying (cosign)](./docs/cosign.md)\n\nnerdctl is a **non-core** sub-project of containerd.\n\n\\*1: P2P image distribution (IPFS) is completely optional. Your host is NOT connected to any P2P network, unless you opt in to [install and run IPFS daemon](https://docs.ipfs.io/install/).\n\n## Examples\n\n### Basic usage\n\nTo run a container with the default `bridge` CNI network (10.4.0.0/24):\n\n```console\n# nerdctl run -it --rm alpine\n```\n\nTo build an image using BuildKit:\n\n```console\n# nerdctl build -t foo /some-dockerfile-directory\n# nerdctl run -it --rm foo\n```\n\nTo build and send output to a local directory using BuildKit:\n\n```console\n# nerdctl build -o type=local,dest=. /some-dockerfile-directory\n```\n\nTo run containers from `docker-compose.yaml`:\n\n```console\n# nerdctl compose -f ./examples/compose-wordpress/docker-compose.yaml up\n```\n\nSee also [`./examples/compose-wordpress`](./examples/compose-wordpress).\n\n### Debugging Kubernetes\n\nTo list local Kubernetes containers:\n\n```console\n# nerdctl --namespace k8s.io ps -a\n```\n\nTo build an image for local Kubernetes without using registry:\n\n```console\n# nerdctl --namespace k8s.io build -t foo /some-dockerfile-directory\n# kubectl apply -f - <<EOF\napiVersion: v1\nkind: Pod\nmetadata:\n  name: foo\nspec:\n  containers:\n    - name: foo\n      image: foo\n      imagePullPolicy: Never\nEOF\n```\n\nTo load an image archive (`docker save` format or OCI format) into local Kubernetes:\n\n```console\n# nerdctl --namespace k8s.io load < /path/to/image.tar\n```\n\nTo read logs (experimental):\n```console\n# nerdctl --namespace=k8s.io ps -a\nCONTAINER ID    IMAGE                                                      COMMAND                   CREATED          STATUS    PORTS    NAMES\n...\ne8793b8cca8b    registry.k8s.io/coredns/coredns:v1.9.3                     \"/coredns -conf /etc…\"    2 minutes ago    Up                 k8s://kube-system/coredns-787d4945fb-mfx6b/coredns\n...\n\n# nerdctl --namespace=k8s.io logs -f e8793b8cca8b\n[INFO] plugin/reload: Running configuration SHA512 = 591cf328cccc12bc490481273e738df59329c62c0b729d94e8b61db9961c2fa5f046dd37f1cf888b953814040d180f52594972691cd6ff41be96639138a43908\nCoreDNS-1.9.3\nlinux/amd64, go1.18.2, 45b0a11\n...\n```\n\n### Rootless mode\n\nTo launch rootless containerd:\n\n```console\n$ containerd-rootless-setuptool.sh install\n```\n\nTo run a container with rootless containerd:\n\n```console\n$ nerdctl run -d -p 8080:80 --name nginx nginx:alpine\n```\n\nSee [`./docs/rootless.md`](./docs/rootless.md).\n\n## Install\n\nBinaries are available here: <https://github.com/containerd/nerdctl/releases>\n\nIn addition to containerd, the following components should be installed:\n\n- [CNI plugins](https://github.com/containernetworking/plugins): for using `nerdctl run`.\n  - v1.1.0 or later is highly recommended.\n- [BuildKit](https://github.com/moby/buildkit) (OPTIONAL): for using `nerdctl build`. BuildKit daemon (`buildkitd`) needs to be running. See also [the document about setting up BuildKit](./docs/build.md).\n  - v0.11.0 or later is highly recommended. Some features, such as pruning caches with `nerdctl system prune`, do not work with older versions.\n- [RootlessKit](https://github.com/rootless-containers/rootlesskit) and [slirp4netns](https://github.com/rootless-containers/slirp4netns) (OPTIONAL): for [Rootless mode](./docs/rootless.md)\n  - RootlessKit needs to be v0.10.0 or later. v2.0.0 or later is recommended.\n  - slirp4netns needs to be v0.4.0 or later. v1.1.7 or later is recommended.\n\nThese dependencies are included in `nerdctl-full-<VERSION>-<OS>-<ARCH>.tar.gz`, but not included in `nerdctl-<VERSION>-<OS>-<ARCH>.tar.gz`.\n\n### Brew\n\nOn Linux systems you can install nerdctl via [brew](https://brew.sh):\n\n```bash\nbrew install nerdctl\n```\n\nThis is currently not supported for macOS. The section below shows how to install on macOS using brew.\n\n### macOS\n\n[Lima](https://github.com/lima-vm/lima) project provides Linux virtual machines for macOS, with built-in integration for nerdctl.\n\n```console\n$ brew install lima\n$ limactl start\n$ lima nerdctl run -d --name nginx -p 127.0.0.1:8080:80 nginx:alpine\n```\n\n### FreeBSD\n\nSee [`./docs/freebsd.md`](docs/freebsd.md).\n\n### Windows\n\n- Linux containers: Known to work on WSL2\n- Windows containers: experimental support for Windows (see below for features that are currently known to work)\n\n### Docker\n\nTo run containerd and nerdctl inside Docker:\n\n```bash\ndocker build -t nerdctl .\ndocker run -it --rm --privileged nerdctl\n```\n\n## Motivation\n\nThe goal of `nerdctl` is to facilitate experimenting the cutting-edge features of containerd that are not present in Docker (see below).\n\nNote that competing with Docker is _not_ the goal of `nerdctl`. Those cutting-edge features are expected to be eventually available in Docker as well.\n\nAlso, `nerdctl` might be potentially useful for debugging Kubernetes clusters, but it is not the primary goal.\n\n## Features present in `nerdctl` but not present in Docker\n\nMajor:\n\n- On-demand image pulling (lazy-pulling) using [Stargz](./docs/stargz.md)/[Nydus](./docs/nydus.md)/[OverlayBD](./docs/overlaybd.md)/[SOCI](./docs/soci.md) Snapshotter: `nerdctl --snapshotter=stargz|nydus|overlaybd|soci run IMAGE` .\n- [Image encryption and decryption using ocicrypt (imgcrypt)](./docs/ocicrypt.md): `nerdctl image (encrypt|decrypt) SRC DST`\n- [P2P image distribution using IPFS](./docs/ipfs.md): `nerdctl run ipfs://CID` .\n  P2P image distribution (IPFS) is completely optional. Your host is NOT connected to any P2P network, unless you opt in to [install and run IPFS daemon](https://docs.ipfs.io/install/).\n- [Cosign integration](./docs/cosign.md): `nerdctl pull --verify=cosign` and `nerdctl push --sign=cosign`, and [in Compose](./docs/cosign.md#cosign-in-compose)\n- [Accelerated rootless containers using bypass4netns](./docs/rootless.md): `nerdctl run --annotation nerdctl/bypass4netns=true`\n\nMinor:\n\n- Namespacing: `nerdctl --namespace=<NS> ps` .\n  (NOTE: All Kubernetes containers are in the `k8s.io` containerd namespace regardless to Kubernetes namespaces)\n- Exporting Docker/OCI dual-format archives: `nerdctl save` .\n- Importing OCI archives as well as Docker archives: `nerdctl load` .\n- Specifying a non-image rootfs: `nerdctl run -it --rootfs <ROOTFS> /bin/sh` . The CLI syntax conforms to Podman convention.\n- Connecting a container to multiple networks at once: `nerdctl run --net foo --net bar`\n- Running [FreeBSD jails](./docs/freebsd.md).\n- Better multi-platform support, e.g., `nerdctl pull --all-platforms IMAGE`\n- Applying an (existing) AppArmor profile to rootless containers: `nerdctl run --security-opt apparmor=<PROFILE>`.\n  Use `sudo nerdctl apparmor load` to load the `nerdctl-default` profile.\n- Systemd compatibility support: `nerdctl run --systemd=always`\n\nTrivial:\n\n- Inspecting raw OCI config: `nerdctl container inspect --mode=native` .\n\n## Features implemented in `nerdctl` ahead of Docker\n\n- Recursive read-only (RRO) bind-mount: `nerdctl run -v /mnt:/mnt:rro` (make children such as `/mnt/usb` to be read-only, too).\n  Requires kernel >= 5.12.\nThe same feature was later introduced in Docker v25 with a different syntax. nerdctl will support Docker v25 syntax too in the future.\n## Similar tools\n\n- [`ctr`](https://github.com/containerd/containerd/tree/main/cmd/ctr): incompatible with Docker CLI, and not friendly to users.\n  Notably, `ctr` lacks the equivalents of the following nerdctl commands:\n  - `nerdctl run -p <PORT>`\n  - `nerdctl run --restart=always --net=bridge`\n  - `nerdctl pull` with `~/.docker/config.json` and credential helper binaries such as `docker-credential-ecr-login`\n  - `nerdctl logs`\n  - `nerdctl build`\n  - `nerdctl compose up`\n\n- [`crictl`](https://github.com/kubernetes-sigs/cri-tools): incompatible with Docker CLI, not friendly to users, and does not support non-CRI features\n- [k3c v0.2 (abandoned)](https://github.com/rancher/k3c/tree/v0.2.1): needs an extra daemon, and does not support non-CRI features\n- [Rancher Kim (nee k3c v0.3)](https://github.com/rancher/kim): needs Kubernetes, and only focuses on image management commands such as `kim build` and `kim push`\n- [PouchContainer (abandoned?)](https://github.com/alibaba/pouch): needs an extra daemon\n\n## Developer guide\n\nnerdctl is a containerd **non-core** sub-project, licensed under the [Apache 2.0 license](./LICENSE).\nAs a containerd non-core sub-project, you will find the:\n\n- [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md),\n- [Maintainers](./MAINTAINERS),\n- and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md)\n\ninformation in our [`containerd/project`](https://github.com/containerd/project) repository.\n\n### Compiling nerdctl from source\n\nRun `make && sudo make install`.\n\nSee the header of [`go.mod`](./go.mod) for the minimum supported version of Go.\n\nUsing `go install github.com/containerd/nerdctl/v2/cmd/nerdctl` is possible, but unrecommended because it does not fill version strings printed in `nerdctl version`\n\n### Testing\n\nSee [testing nerdctl](docs/testing/README.md).\n\n### Contributing to nerdctl\n\nLots of commands and flags are currently missing. Pull requests are highly welcome.\n\nPlease certify your [Developer Certificate of Origin (DCO)](https://developercertificate.org/), by signing off your commit with `git commit -s` and with your real name.\n\n# Command reference\n\nMoved to [`./docs/command-reference.md`](./docs/command-reference.md)\n\n# Additional documents\n\nConfiguration guide:\n\n- [`./docs/config.md`](./docs/config.md): Configuration (`/etc/nerdctl/nerdctl.toml`, `~/.config/nerdctl/nerdctl.toml`)\n- [`./docs/registry.md`](./docs/registry.md): Registry authentication (`~/.docker/config.json`)\n\nBasic features:\n\n- [`./docs/compose.md`](./docs/compose.md):   Compose\n- [`./docs/rootless.md`](./docs/rootless.md): Rootless mode\n- [`./docs/cni.md`](./docs/cni.md): CNI for containers network\n- [`./docs/build.md`](./docs/build.md): `nerdctl build` with BuildKit\n\nAdvanced features:\n\n- [`./docs/stargz.md`](./docs/stargz.md):     Lazy-pulling using Stargz Snapshotter\n- [`./docs/nydus.md`](./docs/nydus.md):       Lazy-pulling using Nydus Snapshotter\n- [`./docs/soci.md`](./docs/soci.md):         Lazy-pulling using SOCI Snapshotter\n- [`./docs/overlaybd.md`](./docs/overlaybd.md):       Lazy-pulling using OverlayBD Snapshotter\n- [`./docs/ocicrypt.md`](./docs/ocicrypt.md): Running encrypted images\n- [`./docs/gpu.md`](./docs/gpu.md):           Using GPUs inside containers\n- [`./docs/multi-platform.md`](./docs/multi-platform.md):  Multi-platform mode\n\nExperimental features:\n\n- [`./docs/experimental.md`](./docs/experimental.md):  Experimental features\n- [`./docs/freebsd.md`](./docs/freebsd.md):  Running FreeBSD jails\n- [`./docs/ipfs.md`](./docs/ipfs.md): Distributing images on IPFS\n- [`./docs/builder-debug.md`](./docs/builder-debug.md): Interactive debugging of Dockerfile\n\nImplementation details:\n\n- [`./docs/dir.md`](./docs/dir.md):           Directory layout (`/var/lib/nerdctl`)\n\nMisc:\n\n- [`./docs/faq.md`](./docs/faq.md): FAQs and Troubleshooting\n"
  },
  {
    "path": "SECURITY.md",
    "content": "See https://github.com/containerd/project/blob/main/SECURITY.md for reporting a vulnerability.\n"
  },
  {
    "path": "Vagrantfile.freebsd",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n#   Copyright The containerd Authors.\n#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# Vagrantfile for FreeBSD\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"generic/freebsd14\"\n\n  memory = 2048\n  cpus = 1\n  config.vm.provider :virtualbox do |v, o|\n    v.memory = memory\n    v.cpus = cpus\n  end\n  config.vm.provider :libvirt do |v|\n    v.memory = memory\n    v.cpus = cpus\n  end\n\n  config.vm.synced_folder \".\", \"/vagrant\", type: \"rsync\"\n\n  config.vm.provision \"install\", type: \"shell\", run: \"once\" do |sh|\n    sh.inline = <<~SHELL\n        #!/usr/bin/env bash\n        set -eux -o pipefail\n        freebsd-version -kru\n        # switching to \"release_2\" ensures compatibility with the current Vagrant box\n        # https://github.com/moby/buildkit/pull/5893\n        sed -i '' 's/latest/release_2/' /usr/local/etc/pkg/repos/FreeBSD.conf\n        # `pkg install go` still installs Go 1.20 (March 2024)\n        pkg install -y go122 containerd runj\n        ln -s go122 /usr/local/bin/go\n        cd /vagrant\n        go install ./cmd/nerdctl\n    SHELL\n  end\n\n config.vm.provision \"test-unit\", type: \"shell\", run: \"never\" do |sh|\n    sh.inline = <<~SHELL\n        #!/usr/bin/env bash\n        set -eux -o pipefail\n        cd /vagrant\n        go test -v ./pkg/...\n    SHELL\n  end\n\n  config.vm.provision \"test-integration\", type: \"shell\", run: \"never\" do |sh|\n    sh.inline = <<~SHELL\n        #!/usr/bin/env bash\n        set -eux -o pipefail\n        daemon -o containerd.out containerd\n        sleep 3\n        CONTAINERD_ADDRESS=/run/containerd/containerd.sock /root/go/bin/nerdctl run --rm --quiet --net=none dougrabson/freebsd-minimal:13 echo \"Nerdctl is up and running.\"\n    SHELL\n  end\n\nend\n"
  },
  {
    "path": "cmd/nerdctl/apparmor/apparmor_inspect_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/apparmor\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"inspect\",\n\t\tShort:         fmt.Sprintf(\"Display the default AppArmor profile %q. Other profiles cannot be displayed with this command.\", defaults.AppArmorProfileName),\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          inspectAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\treturn cmd\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\treturn apparmor.Inspect(types.ApparmorInspectOptions{\n\t\tStdout: cmd.OutOrStdout(),\n\t})\n}\n"
  },
  {
    "path": "cmd/nerdctl/apparmor/apparmor_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"apparmor\",\n\t\tShort:         \"Manage AppArmor profiles\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tlistCommand(),\n\t\tinspectCommand(),\n\t\tloadCommand(),\n\t\tunloadCommand(),\n\t)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/apparmor/apparmor_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/apparmor/apparmor_list_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/apparmor\"\n)\n\nfunc listCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"ls\",\n\t\tAliases:       []string{\"list\"},\n\t\tShort:         \"List the loaded AppArmor profiles\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          listAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only display profile names\")\n\t// Alias \"-f\" is reserved for \"--filter\"\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given go template\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\", \"table\", \"wide\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc listOptions(cmd *cobra.Command) (types.ApparmorListOptions, error) {\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.ApparmorListOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.ApparmorListOptions{}, err\n\t}\n\treturn types.ApparmorListOptions{\n\t\tQuiet:  quiet,\n\t\tFormat: format,\n\t\tStdout: cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc listAction(cmd *cobra.Command, args []string) error {\n\toptions, err := listOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn apparmor.List(options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/apparmor/apparmor_load_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/apparmor\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\nfunc loadCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"load\",\n\t\tShort:         fmt.Sprintf(\"Load the default AppArmor profile %q. Requires root.\", defaults.AppArmorProfileName),\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          loadAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\treturn cmd\n}\n\nfunc loadAction(cmd *cobra.Command, args []string) error {\n\treturn apparmor.Load()\n}\n"
  },
  {
    "path": "cmd/nerdctl/apparmor/apparmor_unload_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/apparmor\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\nfunc unloadCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"unload [PROFILE]\",\n\t\tShort:             fmt.Sprintf(\"Unload an AppArmor profile. The target profile name defaults to %q. Requires root.\", defaults.AppArmorProfileName),\n\t\tArgs:              cobra.MaximumNArgs(1),\n\t\tRunE:              unloadAction,\n\t\tValidArgsFunction: unloadShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc unloadAction(cmd *cobra.Command, args []string) error {\n\ttarget := defaults.AppArmorProfileName\n\tif len(args) > 0 {\n\t\ttarget = args[0]\n\t}\n\treturn apparmor.Unload(target)\n}\n\nfunc unloadShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ApparmorProfiles(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/builder/builder.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/builder\"\n)\n\nfunc Command() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"builder\",\n\t\tShort:         \"Manage builds\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tBuildCommand(),\n\t\tpruneCommand(),\n\t\tdebugCommand(),\n\t)\n\treturn cmd\n}\n\nfunc pruneCommand() *cobra.Command {\n\tshortHelp := `Clean up BuildKit build cache`\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"prune\",\n\t\tArgs:          cobra.NoArgs,\n\t\tShort:         shortHelp,\n\t\tRunE:          pruneAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.Flags().String(\"buildkit-host\", \"\", \"BuildKit address\")\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Remove all unused build cache, not just dangling ones\")\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\treturn cmd\n}\n\nfunc pruneAction(cmd *cobra.Command, _ []string) error {\n\toptions, err := pruneOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !options.Force {\n\t\tvar msg string\n\n\t\tif options.All {\n\t\t\tmsg = \"This will remove all build cache.\"\n\t\t} else {\n\t\t\tmsg = \"This will remove any dangling build cache.\"\n\t\t}\n\n\t\tif confirmed, err := helpers.Confirm(cmd, fmt.Sprintf(\"WARNING! %s.\", msg)); err != nil || !confirmed {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tprunedObjects, err := builder.Prune(cmd.Context(), options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar totalReclaimedSpace int64\n\n\tfor _, prunedObject := range prunedObjects {\n\t\ttotalReclaimedSpace += prunedObject.Size\n\t}\n\n\tfmt.Fprintf(cmd.OutOrStdout(), \"Total:  %s\\n\", units.BytesSize(float64(totalReclaimedSpace)))\n\n\treturn nil\n}\n\nfunc pruneOptions(cmd *cobra.Command) (types.BuilderPruneOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.BuilderPruneOptions{}, err\n\t}\n\n\tbuildkitHost, err := GetBuildkitHost(cmd, globalOptions.Namespace)\n\tif err != nil {\n\t\treturn types.BuilderPruneOptions{}, err\n\t}\n\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn types.BuilderPruneOptions{}, err\n\t}\n\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn types.BuilderPruneOptions{}, err\n\t}\n\n\treturn types.BuilderPruneOptions{\n\t\tStderr:       cmd.OutOrStderr(),\n\t\tGOptions:     globalOptions,\n\t\tBuildKitHost: buildkitHost,\n\t\tAll:          all,\n\t\tForce:        force,\n\t}, nil\n}\n\nfunc debugCommand() *cobra.Command {\n\tshortHelp := `Debug Dockerfile`\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"debug\",\n\t\tShort:         shortHelp,\n\t\tPreRunE:       helpers.CheckExperimental(\"`nerdctl builder debug`\"),\n\t\tRunE:          debugAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringP(\"file\", \"f\", \"\", \"Name of the Dockerfile\")\n\tcmd.Flags().String(\"target\", \"\", \"Set the target build stage to build\")\n\tcmd.Flags().StringArray(\"build-arg\", nil, \"Set build-time variables\")\n\tcmd.Flags().String(\"image\", \"\", \"Image to use for debugging stage\")\n\tcmd.Flags().StringArray(\"ssh\", nil, \"Allow forwarding SSH agent to the build. Format: default|<id>[=<socket>|<key>[,<key>]]\")\n\tcmd.Flags().StringArray(\"secret\", nil, \"Expose secret value to the build. Format: id=secretname,src=filepath\")\n\thelpers.AddDurationFlag(cmd, \"buildg-startup-timeout\", nil, 1*time.Minute, \"\", \"Timeout for starting up buildg\")\n\treturn cmd\n}\n\nfunc debugAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(args) < 1 {\n\t\treturn fmt.Errorf(\"context needs to be specified\")\n\t}\n\n\tbuildgBinary, err := exec.LookPath(\"buildg\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuildgArgs := []string{\"debug\"}\n\tif globalOptions.Debug {\n\t\tbuildgArgs = append([]string{\"--debug\"}, buildgArgs...)\n\t}\n\n\tstartupTimeout, err := cmd.Flags().GetDuration(\"buildg-startup-timeout\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuildgArgs = append(buildgArgs, \"--startup-timeout=\"+startupTimeout.String())\n\n\tif file, err := cmd.Flags().GetString(\"file\"); err != nil {\n\t\treturn err\n\t} else if file != \"\" {\n\t\tbuildgArgs = append(buildgArgs, \"--file=\"+file)\n\t}\n\n\tif target, err := cmd.Flags().GetString(\"target\"); err != nil {\n\t\treturn err\n\t} else if target != \"\" {\n\t\tbuildgArgs = append(buildgArgs, \"--target=\"+target)\n\t}\n\n\tif buildArgsValue, err := cmd.Flags().GetStringArray(\"build-arg\"); err != nil {\n\t\treturn err\n\t} else if len(buildArgsValue) > 0 {\n\t\tfor _, v := range buildArgsValue {\n\t\t\tarr := strings.Split(v, \"=\")\n\t\t\tif len(arr) == 1 && len(arr[0]) > 0 {\n\t\t\t\t// Avoid masking default build arg value from Dockerfile if environment variable is not set\n\t\t\t\t// https://github.com/moby/moby/issues/24101\n\t\t\t\tval, ok := os.LookupEnv(arr[0])\n\t\t\t\tif ok {\n\t\t\t\t\tbuildgArgs = append(buildgArgs, fmt.Sprintf(\"--build-arg=%s=%s\", v, val))\n\t\t\t\t}\n\t\t\t} else if len(arr) > 1 && len(arr[0]) > 0 {\n\t\t\t\tbuildgArgs = append(buildgArgs, \"--build-arg=\"+v)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"invalid build arg %q\", v)\n\t\t\t}\n\t\t}\n\t}\n\n\tif imageValue, err := cmd.Flags().GetString(\"image\"); err != nil {\n\t\treturn err\n\t} else if imageValue != \"\" {\n\t\tbuildgArgs = append(buildgArgs, \"--image=\"+imageValue)\n\t}\n\n\tif sshValue, err := cmd.Flags().GetStringArray(\"ssh\"); err != nil {\n\t\treturn err\n\t} else if len(sshValue) > 0 {\n\t\tfor _, v := range sshValue {\n\t\t\tbuildgArgs = append(buildgArgs, \"--ssh=\"+v)\n\t\t}\n\t}\n\n\tif secretValue, err := cmd.Flags().GetStringArray(\"secret\"); err != nil {\n\t\treturn err\n\t} else if len(secretValue) > 0 {\n\t\tfor _, v := range secretValue {\n\t\t\tbuildgArgs = append(buildgArgs, \"--secret=\"+v)\n\t\t}\n\t}\n\n\tbuildgCmd := exec.Command(buildgBinary, append(buildgArgs, args[0])...)\n\tbuildgCmd.Env = os.Environ()\n\tbuildgCmd.Stdin = cmd.InOrStdin()\n\tbuildgCmd.Stdout = cmd.OutOrStdout()\n\tbuildgCmd.Stderr = cmd.ErrOrStderr()\n\tif err := buildgCmd.Start(); err != nil {\n\t\treturn err\n\t}\n\n\treturn buildgCmd.Wait()\n}\n"
  },
  {
    "path": "cmd/nerdctl/builder/builder_build.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/builder\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc BuildCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:   \"build [flags] PATH\",\n\t\tShort: \"Build an image from a Dockerfile. Needs buildkitd to be running.\",\n\t\tLong: `Build an image from a Dockerfile. Needs buildkitd to be running.\nIf Dockerfile is not present and -f is not specified, it will look for Containerfile and build with it. `,\n\t\tRunE:          buildAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().String(\"buildkit-host\", \"\", \"BuildKit address\")\n\tcmd.Flags().StringArray(\"add-host\", nil, \"Add a custom host-to-IP mapping (format: \\\"host:ip\\\")\")\n\tcmd.Flags().StringArrayP(\"tag\", \"t\", nil, \"Name and optionally a tag in the 'name:tag' format\")\n\tcmd.Flags().StringP(\"file\", \"f\", \"\", \"Name of the Dockerfile\")\n\tcmd.Flags().String(\"target\", \"\", \"Set the target build stage to build\")\n\tcmd.Flags().StringArray(\"build-arg\", nil, \"Set build-time variables\")\n\tcmd.Flags().Bool(\"no-cache\", false, \"Do not use cache when building the image\")\n\tcmd.Flags().StringP(\"output\", \"o\", \"\", \"Output destination (format: type=local,dest=path)\")\n\tcmd.Flags().String(\"progress\", \"auto\", \"Set type of progress output (auto, plain, tty). Use plain to show container output\")\n\tcmd.Flags().String(\"provenance\", \"\", \"Shorthand for \\\"--attest=type=provenance\\\"\")\n\tcmd.Flags().Bool(\"pull\", false, \"On true, always attempt to pull latest image version from remote. Default uses buildkit's default.\")\n\tcmd.Flags().StringArray(\"secret\", nil, \"Secret file to expose to the build: id=mysecret,src=/local/secret\")\n\tcmd.Flags().StringArray(\"allow\", nil, \"Allow extra privileged entitlement, e.g. network.host, security.insecure\")\n\tcmd.RegisterFlagCompletionFunc(\"allow\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"network.host\", \"security.insecure\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringArray(\"attest\", nil, \"Attestation parameters (format: \\\"type=sbom,generator=image\\\")\")\n\tcmd.Flags().StringArray(\"ssh\", nil, \"SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])\")\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Suppress the build output and print image ID on success\")\n\tcmd.Flags().String(\"sbom\", \"\", \"Shorthand for \\\"--attest=type=sbom\\\"\")\n\tcmd.Flags().StringArray(\"cache-from\", nil, \"External cache sources (eg. user/app:cache, type=local,src=path/to/dir)\")\n\tcmd.Flags().StringArray(\"cache-to\", nil, \"Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)\")\n\tcmd.Flags().Bool(\"rm\", true, \"Remove intermediate containers after a successful build\")\n\tcmd.Flags().String(\"network\", \"default\", \"Set type of network for build (format:network=default|none|host)\")\n\tcmd.RegisterFlagCompletionFunc(\"network\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"default\", \"host\", \"none\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tcmd.Flags().StringSlice(\"platform\", []string{}, \"Set target platform for build (e.g., \\\"amd64\\\", \\\"arm64\\\")\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tcmd.Flags().StringArray(\"build-context\", []string{}, \"Additional build contexts (e.g., name=path)\")\n\t// #endregion\n\n\tcmd.Flags().String(\"iidfile\", \"\", \"Write the image ID to the file\")\n\tcmd.Flags().StringArray(\"label\", nil, \"Set metadata for an image\")\n\tcmd.Flags().String(\"source-policy-file\", \"\", \"BuildKit source policy file (see https://github.com/moby/buildkit/blob/master/docs/build-repro.md)\")\n\n\treturn cmd\n}\n\nfunc processBuildCommandFlag(cmd *cobra.Command, args []string) (types.BuilderBuildOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tbuildKitHost, err := GetBuildkitHost(cmd, globalOptions.Namespace)\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\textraHosts, err := cmd.Flags().GetStringArray(\"add-host\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tplatform, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tplatform = strutil.DedupeStrSlice(platform)\n\tif len(args) < 1 {\n\t\treturn types.BuilderBuildOptions{}, errors.New(\"context needs to be specified\")\n\t}\n\tbuildContext := args[0]\n\tif buildContext == \"-\" || strings.Contains(buildContext, \"://\") {\n\t\treturn types.BuilderBuildOptions{}, fmt.Errorf(\"unsupported build context: %q\", buildContext)\n\t}\n\toutput, err := cmd.Flags().GetString(\"output\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\ttagValue, err := cmd.Flags().GetStringArray(\"tag\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tprogress, err := cmd.Flags().GetString(\"progress\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tfilename, err := cmd.Flags().GetString(\"file\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\ttarget, err := cmd.Flags().GetString(\"target\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tbuildArgs, err := cmd.Flags().GetStringArray(\"build-arg\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tlabel, err := cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tnoCache, err := cmd.Flags().GetBool(\"no-cache\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tvar pull *bool\n\tif cmd.Flags().Changed(\"pull\") {\n\t\tpullFlag, err := cmd.Flags().GetBool(\"pull\")\n\t\tif err != nil {\n\t\t\treturn types.BuilderBuildOptions{}, err\n\t\t}\n\t\tpull = &pullFlag\n\t}\n\tsecret, err := cmd.Flags().GetStringArray(\"secret\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tallow, err := cmd.Flags().GetStringArray(\"allow\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tssh, err := cmd.Flags().GetStringArray(\"ssh\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tcacheFrom, err := cmd.Flags().GetStringArray(\"cache-from\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tcacheTo, err := cmd.Flags().GetStringArray(\"cache-to\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\trm, err := cmd.Flags().GetBool(\"rm\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tiidfile, err := cmd.Flags().GetString(\"iidfile\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tnetwork, err := cmd.Flags().GetString(\"network\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\n\tattest, err := cmd.Flags().GetStringArray(\"attest\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tsbom, err := cmd.Flags().GetString(\"sbom\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tif sbom != \"\" {\n\t\tattest = append(attest, canonicalizeAttest(\"sbom\", sbom))\n\t}\n\tprovenance, err := cmd.Flags().GetString(\"provenance\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tif provenance != \"\" {\n\t\tattest = append(attest, canonicalizeAttest(\"provenance\", provenance))\n\t}\n\textendedBuildCtx, err := cmd.Flags().GetStringArray(\"build-context\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\tsourcePolicyFile, err := cmd.Flags().GetString(\"source-policy-file\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t}\n\n\tusernsRemap, err := cmd.Flags().GetString(\"userns-remap\")\n\tif err != nil {\n\t\treturn types.BuilderBuildOptions{}, err\n\t} else if usernsRemap != \"\" {\n\t\tlog.L.Warn(\"userns remap is not supported with nerdctl build. dropping the config.\")\n\t}\n\n\treturn types.BuilderBuildOptions{\n\t\tGOptions:             globalOptions,\n\t\tBuildKitHost:         buildKitHost,\n\t\tBuildContext:         buildContext,\n\t\tOutput:               output,\n\t\tTag:                  tagValue,\n\t\tProgress:             progress,\n\t\tFile:                 filename,\n\t\tTarget:               target,\n\t\tBuildArgs:            buildArgs,\n\t\tLabel:                label,\n\t\tNoCache:              noCache,\n\t\tPull:                 pull,\n\t\tSecret:               secret,\n\t\tAllow:                allow,\n\t\tAttest:               attest,\n\t\tSSH:                  ssh,\n\t\tCacheFrom:            cacheFrom,\n\t\tCacheTo:              cacheTo,\n\t\tRm:                   rm,\n\t\tIidFile:              iidfile,\n\t\tQuiet:                quiet,\n\t\tPlatform:             platform,\n\t\tStdout:               cmd.OutOrStdout(),\n\t\tStderr:               cmd.OutOrStderr(),\n\t\tStdin:                cmd.InOrStdin(),\n\t\tNetworkMode:          network,\n\t\tExtendedBuildContext: extendedBuildCtx,\n\t\tExtraHosts:           extraHosts,\n\t\tSourcePolicyFile:     sourcePolicyFile,\n\t}, nil\n}\n\nfunc GetBuildkitHost(cmd *cobra.Command, namespace string) (string, error) {\n\tif cmd.Flags().Changed(\"buildkit-host\") {\n\t\t// If address is explicitly specified, use it.\n\t\tbuildkitHost, err := cmd.Flags().GetString(\"buildkit-host\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif err := buildkitutil.PingBKDaemon(buildkitHost); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn buildkitHost, nil\n\t}\n\n\treturn buildkitutil.GetBuildkitHost(namespace)\n}\n\nfunc buildAction(cmd *cobra.Command, args []string) error {\n\toptions, err := processBuildCommandFlag(cmd, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn builder.Build(ctx, client, options)\n}\n\n// canonicalizeAttest is from https://github.com/docker/buildx/blob/v0.12/util/buildflags/attests.go##L13-L21\nfunc canonicalizeAttest(attestType string, in string) string {\n\tif in == \"\" {\n\t\treturn \"\"\n\t}\n\tif b, err := strconv.ParseBool(in); err == nil {\n\t\treturn fmt.Sprintf(\"type=%s,disabled=%t\", attestType, !b)\n\t}\n\treturn fmt.Sprintf(\"type=%s,%s\", attestType, in)\n}\n"
  },
  {
    "path": "cmd/nerdctl/builder/builder_build_oci_layout_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestBuildContextWithOCILayout(t *testing.T) {\n\tnerdtest.Setup()\n\n\tvar dockerBuilderArgs []string\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(require.Windows),\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\thelpers.Anyhow(\"buildx\", \"stop\", data.Identifier(\"container\"))\n\t\t\t\thelpers.Anyhow(\"buildx\", \"rm\", \"--force\", data.Identifier(\"container\"))\n\t\t\t}\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"parent\"))\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"child\"))\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t// Default docker driver does not support OCI exporter.\n\t\t\t// Reference: https://docs.docker.com/build/exporters/oci-docker/\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\tname := data.Identifier(\"container\")\n\t\t\t\thelpers.Ensure(\"buildx\", \"create\", \"--name\", name, \"--driver=docker-container\")\n\t\t\t\tdockerBuilderArgs = []string{\"buildx\", \"--builder\", name}\n\t\t\t}\n\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nLABEL layer=oci-layout-parent\nCMD [\"echo\", \"test-nerdctl-build-context-oci-layout-parent\"]`, testutil.CommonImage)\n\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdest := data.Temp().Dir(\"parent\")\n\t\t\ttarPath := data.Temp().Path(\"parent.tar\")\n\n\t\t\thelpers.Ensure(\"build\", data.Temp().Path(), \"--tag\", data.Identifier(\"parent\"))\n\t\t\thelpers.Ensure(\"image\", \"save\", \"--output\", tarPath, data.Identifier(\"parent\"))\n\t\t\thelpers.Custom(\"tar\", \"Cxf\", dest, tarPath).Run(&test.Expected{\n\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t})\n\t\t},\n\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tdockerfile := `FROM parent\nCMD [\"echo\", \"test-nerdctl-build-context-oci-layout\"]`\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\t\tvar cmd test.TestableCommand\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\tcmd = helpers.Command(dockerBuilderArgs...)\n\t\t\t} else {\n\t\t\t\tcmd = helpers.Command()\n\t\t\t}\n\t\t\tcmd.WithArgs(\n\t\t\t\t\"build\",\n\t\t\t\tdata.Temp().Path(),\n\t\t\t\tfmt.Sprintf(\"--build-context=parent=oci-layout://%s\", filepath.Join(data.Temp().Path(), \"parent\")),\n\t\t\t\t\"--tag\",\n\t\t\t\tdata.Identifier(\"child\"),\n\t\t\t)\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t// Need to load the container image from the builder to be able to run it.\n\t\t\t\tcmd.WithArgs(\"--load\")\n\t\t\t}\n\t\t\treturn cmd\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(\n\t\t\t\t\t\tt,\n\t\t\t\t\t\tstrings.Contains(\n\t\t\t\t\t\t\thelpers.Capture(\"run\", \"--rm\", data.Identifier(\"child\")),\n\t\t\t\t\t\t\t\"test-nerdctl-build-context-oci-layout\",\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/builder/builder_build_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestBuildBasics(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Successfully build with 'tag first', 'buildctx second'\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(), data.Labels().Get(\"buildCtx\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Successfully build with 'buildctx first', 'tag second'\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Successfully build with output docker, main tag still works\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\n\t\t\t\t\t\t\"build\",\n\t\t\t\t\t\tdata.Labels().Get(\"buildCtx\"),\n\t\t\t\t\t\t\"-t\",\n\t\t\t\t\t\tdata.Identifier(),\n\t\t\t\t\t\t\"--output=type=docker,name=\"+data.Identifier(\"ignored\"),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Successfully build with output docker, name cannot be used\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\n\t\t\t\t\t\t\"build\",\n\t\t\t\t\t\tdata.Labels().Get(\"buildCtx\"),\n\t\t\t\t\t\t\"-t\",\n\t\t\t\t\t\tdata.Identifier(),\n\t\t\t\t\t\t\"--output=type=docker,name=\"+data.Identifier(\"ignored\"),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier(\"ignored\"))\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"ignored\"))\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestCanBuildOnOtherPlatform(t *testing.T) {\n\tnerdtest.Setup()\n\n\trequireEmulation := &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\tcandidateArch := \"arm64\"\n\t\t\tif runtime.GOARCH == \"arm64\" {\n\t\t\t\tcandidateArch = \"amd64\"\n\t\t\t}\n\t\t\tcan, err := platformutil.CanExecProbably(\"linux/\" + candidateArch)\n\t\t\tassert.NilError(helpers.T(), err)\n\n\t\t\tdata.Labels().Set(\"OS\", \"linux\")\n\t\t\tdata.Labels().Set(\"Architecture\", candidateArch)\n\t\t\treturn can, \"Current environment does not support emulation\"\n\t\t},\n\t}\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN echo hello > /hello\nCMD [\"echo\", \"nerdctl-build-test-string\"]`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequireEmulation,\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\n\t\t\t\t\"build\",\n\t\t\t\tdata.Labels().Get(\"buildCtx\"),\n\t\t\t\t\"--platform\",\n\t\t\t\tfmt.Sprintf(\"%s/%s\", data.Labels().Get(\"OS\"), data.Labels().Get(\"Architecture\")),\n\t\t\t\t\"-t\",\n\t\t\t\tdata.Identifier(),\n\t\t\t)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestBuildBaseImage tests if an image can be built on the previously built image.\n// This isn't currently supported by nerdctl with BuildKit OCI worker.\nfunc TestBuildBaseImage(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"first\"))\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"second\"))\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nRUN echo hello > /hello\nCMD [\"echo\", \"nerdctl-build-test-string\"]`, testutil.CommonImage)\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(\"first\"), data.Temp().Path())\n\n\t\t\tdockerfileSecond := fmt.Sprintf(`FROM %s\nRUN echo hello2 > /hello2\nCMD [\"cat\", \"/hello2\"]`, data.Identifier(\"first\"))\n\t\t\tdata.Temp().Save(dockerfileSecond, \"Dockerfile\")\n\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(\"second\"), data.Temp().Path())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier(\"second\"))\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"hello2\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestBuildFromContainerd tests if an image can be built on an image pulled by nerdctl.\n// This isn't currently supported by nerdctl with BuildKit OCI worker.\nfunc TestBuildFromContainerd(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"first\"))\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"second\"))\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, data.Identifier(\"first\"))\n\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nRUN echo hello2 > /hello2\nCMD [\"cat\", \"/hello2\"]`, data.Identifier(\"first\"))\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(\"second\"), data.Temp().Path())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier(\"second\"))\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"hello2\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildFromStdin(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-stdin\"]`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tcmd := helpers.Command(\"build\", \"-t\", data.Identifier(), \"-f\", \"-\", \".\")\n\t\t\tcmd.Feed(strings.NewReader(dockerfile))\n\t\t\treturn cmd\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tErrors: []error{errors.New(data.Identifier())},\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithDockerfile(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-dockerfile\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"test\", \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path(\"test\"))\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Dockerfile ..\",\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tcmd := helpers.Command(\"build\", \"-t\", data.Identifier(), \"-f\", \"Dockerfile\", \"..\")\n\t\t\t\t\tcmd.WithCwd(data.Labels().Get(\"buildCtx\"))\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Dockerfile .\",\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tcmd := helpers.Command(\"build\", \"-t\", data.Identifier(), \"-f\", \"Dockerfile\", \".\")\n\t\t\t\t\tcmd.WithCwd(data.Labels().Get(\"buildCtx\"))\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"../Dockerfile .\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tcmd := helpers.Command(\"build\", \"-t\", data.Identifier(), \"-f\", \"../Dockerfile\", \".\")\n\t\t\t\t\tcmd.WithCwd(data.Labels().Get(\"buildCtx\"))\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildLocal(t *testing.T) {\n\tnerdtest.Setup()\n\n\tconst testFileName = \"nerdctl-build-test\"\n\tconst testContent = \"nerdctl\"\n\n\tdockerfile := fmt.Sprintf(`FROM scratch\nCOPY %s /`, testFileName)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Temp().Save(testContent, testFileName)\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\t// GOTCHA: avoid comma and = in the test name, or buildctl will misparse the destination direction\n\t\t\t\tDescription: \"-o type local destination DIR: verify the file copied from context is in the output directory\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"build\", \"-o\", fmt.Sprintf(\"type=local,dest=%s\", data.Temp().Path()), data.Labels().Get(\"buildCtx\"))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t// Expecting testFileName to exist inside the output target directory\n\t\t\t\t\t\t\tassert.Equal(t, data.Temp().Load(testFileName), testContent, \"file content is identical\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"-o DIR: verify the file copied from context is in the output directory\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"build\", \"-o\", data.Temp().Path(), data.Labels().Get(\"buildCtx\"))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tassert.Equal(t, data.Temp().Load(testFileName), testContent, \"file content is identical\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithBuildArg(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nARG TEST_STRING=1\nENV TEST_STRING=$TEST_STRING\nCMD echo $TEST_STRING\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"No args\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"1\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"ArgValueOverridesDefault\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"--build-arg\", \"TEST_STRING=2\", \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"2\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"EmptyArgValueOverridesDefault\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"--build-arg\", \"TEST_STRING=\", \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"UnsetArgKeyPreservesDefault\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"--build-arg\", \"TEST_STRING\", \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"1\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"EnvValueOverridesDefault\",\n\t\t\t\tEnv: map[string]string{\n\t\t\t\t\t\"TEST_STRING\": \"3\",\n\t\t\t\t},\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"--build-arg\", \"TEST_STRING\", \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"3\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"EmptyEnvValueOverridesDefault\",\n\t\t\t\tEnv: map[string]string{\n\t\t\t\t\t\"TEST_STRING\": \"\",\n\t\t\t\t},\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"--build-arg\", \"TEST_STRING\", \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"\\n\")),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithIIDFile(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\thelpers.Ensure(\"build\", data.Temp().Path(), \"--iidfile\", data.Temp().Path(\"id.txt\"), \"-t\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Temp().Load(\"id.txt\"))\n\t\t},\n\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithLabels(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nLABEL name=nerdctl-build-test-label\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\thelpers.Ensure(\"build\", data.Temp().Path(), \"--label\", \"label=test\", \"-t\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"inspect\", data.Identifier(), \"--format\", \"{{json .Config.Labels }}\")\n\t\t},\n\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"{\\\"label\\\":\\\"test\\\",\\\"name\\\":\\\"nerdctl-build-test-label\\\"}\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildMultipleTags(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"i1\"))\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"i2\"))\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"i3\"))\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Labels().Set(\"i1\", data.Identifier(\"image\"))\n\t\t\tdata.Labels().Set(\"i2\", data.Identifier(\"image2\"))\n\t\t\tdata.Labels().Set(\"i3\", data.Identifier(\"image3\")+\":hello\")\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\thelpers.Ensure(\n\t\t\t\t\"build\",\n\t\t\t\tdata.Temp().Path(),\n\t\t\t\t\"-t\", data.Labels().Get(\"i1\"),\n\t\t\t\t\"-t\", data.Labels().Get(\"i2\"),\n\t\t\t\t\"-t\", data.Labels().Get(\"i3\"),\n\t\t\t)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"i1\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"i1\"))\n\t\t\t\t},\n\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"i2\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"i2\"))\n\t\t\t\t},\n\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"i3\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"i3\"))\n\t\t\t\t},\n\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithContainerfile(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\thelpers.Ensure(\"build\", data.Temp().Path(), \"-t\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithDockerFileAndContainerfile(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"dockerfile\"]\n\t`, testutil.CommonImage)\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\t\tdockerfile = fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"containerfile\"]\n\t`, testutil.CommonImage)\n\t\t\tdata.Temp().Save(dockerfile, \"Containerfile\")\n\n\t\t\thelpers.Ensure(\"build\", data.Temp().Path(), \"-t\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"dockerfile\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildNoTag(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\t// FIXME: this test should be rewritten and instead get the image id from the build, then query the image explicitly - instead of pruning / noparallel\n\ttestCase := &test.Case{\n\t\tNoParallel: true,\n\t\tRequire:    nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"image\", \"prune\", \"--force\", \"--all\")\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\t\t// XXX FIXME\n\t\t\thelpers.Capture(\"build\", data.Temp().Path())\n\t\t},\n\t\tCommand:  test.Command(\"images\"),\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"<none>\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildContextDockerImageAlias(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := `FROM myorg/myapp\nCMD [\"echo\", \"nerdctl-build-myorg/myapp\"]`\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\n\t\t\t\t\"build\",\n\t\t\t\t\"-t\",\n\t\t\t\tdata.Identifier(),\n\t\t\t\tdata.Temp().Path(),\n\t\t\t\tfmt.Sprintf(\"--build-context=myorg/myapp=docker-image://%s\", testutil.CommonImage),\n\t\t\t)\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildContextWithCopyFromDir(t *testing.T) {\n\tnerdtest.Setup()\n\n\tcontent := \"hello_from_dir_2\"\n\tfilename := \"hello.txt\"\n\tdockerfile := fmt.Sprintf(`FROM %s\nCOPY --from=dir2 /%s /hello_from_dir2.txt\nRUN [\"cat\", \"/hello_from_dir2.txt\"]`, testutil.CommonImage, filename)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"context\", \"Dockerfile\")\n\t\t\tdata.Temp().Save(content, \"other-directory\", filename)\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\n\t\t\t\t\"build\",\n\t\t\t\t\"-t\",\n\t\t\t\tdata.Identifier(),\n\t\t\t\tdata.Temp().Path(\"context\"),\n\t\t\t\tfmt.Sprintf(\"--build-context=dir2=%s\", data.Temp().Path(\"other-directory\")),\n\t\t\t)\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestBuildSourceDateEpoch tests that $SOURCE_DATE_EPOCH is propagated from the client env\n// https://github.com/docker/buildx/pull/1482\nfunc TestBuildSourceDateEpoch(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nARG SOURCE_DATE_EPOCH\nRUN echo $SOURCE_DATE_EPOCH >/source-date-epoch\nCMD [\"cat\", \"/source-date-epoch\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"1111111111\",\n\t\t\t\tEnv: map[string]string{\n\t\t\t\t\t\"SOURCE_DATE_EPOCH\": \"1111111111\",\n\t\t\t\t},\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"1111111111\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"2222222222\",\n\t\t\t\tEnv: map[string]string{\n\t\t\t\t\t\"SOURCE_DATE_EPOCH\": \"1111111111\",\n\t\t\t\t},\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"build\", data.Labels().Get(\"buildCtx\"), \"--build-arg\", \"SOURCE_DATE_EPOCH=2222222222\", \"-t\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"2222222222\\n\")),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildNetwork(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN apk add --no-cache curl\nRUN curl -I http://google.com\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"none\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"buildCtx\"), \"-t\", data.Identifier(), \"--no-cache\", \"--network\", \"none\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"empty\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"buildCtx\"), \"-t\", data.Identifier(), \"--no-cache\", \"--network\", \"\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"default\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"buildCtx\"), \"-t\", data.Identifier(), \"--no-cache\", \"--network\", \"default\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildAttestation(t *testing.T) {\n\tnerdtest.Setup()\n\n\t// Using regex patterns to match SBOM and provenance files with optional platform suffix\n\tconst testSBOMFilePattern = `sbom\\.spdx(?:\\.[a-z0-9_]+)?\\.json`\n\tconst testProvenanceFilePattern = `provenance(?:\\.[a-z0-9_]+)?\\.json`\n\n\tdockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\thelpers.Anyhow(\"buildx\", \"rm\", data.Identifier(\"builder\"))\n\t\t\t}\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\thelpers.Anyhow(\"buildx\", \"create\", \"--name\", data.Identifier(\"builder\"), \"--bootstrap\", \"--use\")\n\t\t\t}\n\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"SBOM\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tcmd := helpers.Command(\"build\")\n\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\tcmd.WithArgs(\"--builder\", data.Identifier(\"builder\"))\n\t\t\t\t\t}\n\t\t\t\t\tcmd.WithArgs(\n\t\t\t\t\t\t\"--sbom=true\",\n\t\t\t\t\t\t\"-o\", fmt.Sprintf(\"type=local,dest=%s\", data.Temp().Path(\"dir-for-bom\")),\n\t\t\t\t\t\tdata.Labels().Get(\"buildCtx\"),\n\t\t\t\t\t)\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tfiles, err := os.ReadDir(data.Temp().Path(\"dir-for-bom\"))\n\t\t\t\t\t\t\tassert.NilError(t, err, \"failed to read directory\")\n\n\t\t\t\t\t\t\tfound := false\n\t\t\t\t\t\t\tfor _, file := range files {\n\t\t\t\t\t\t\t\tif !file.IsDir() && regexp.MustCompile(testSBOMFilePattern).MatchString(file.Name()) {\n\t\t\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, found, \"no SBOM file matching pattern %s found\", testSBOMFilePattern)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Provenance\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tcmd := helpers.Command(\"build\")\n\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\tcmd.WithArgs(\"--builder\", data.Identifier(\"builder\"))\n\t\t\t\t\t}\n\t\t\t\t\tcmd.WithArgs(\n\t\t\t\t\t\t\"--provenance=mode=min\",\n\t\t\t\t\t\t\"-o\", fmt.Sprintf(\"type=local,dest=%s\", data.Temp().Path(\"dir-for-prov\")),\n\t\t\t\t\t\tdata.Labels().Get(\"buildCtx\"),\n\t\t\t\t\t)\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tfiles, err := os.ReadDir(data.Temp().Path(\"dir-for-prov\"))\n\t\t\t\t\t\t\tassert.NilError(t, err, \"failed to read directory\")\n\n\t\t\t\t\t\t\tfound := false\n\t\t\t\t\t\t\tfor _, file := range files {\n\t\t\t\t\t\t\t\tif !file.IsDir() && regexp.MustCompile(testProvenanceFilePattern).MatchString(file.Name()) {\n\t\t\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, found, \"no provenance file matching pattern %s found\", testProvenanceFilePattern)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Attestation\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tcmd := helpers.Command(\"build\")\n\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\tcmd.WithArgs(\"--builder\", data.Identifier(\"builder\"))\n\t\t\t\t\t}\n\t\t\t\t\tcmd.WithArgs(\n\t\t\t\t\t\t\"--attest=type=provenance,mode=min\",\n\t\t\t\t\t\t\"--attest=type=sbom\",\n\t\t\t\t\t\t\"-o\", fmt.Sprintf(\"type=local,dest=%s\", data.Temp().Path(\"dir-for-attest\")),\n\t\t\t\t\t\tdata.Labels().Get(\"buildCtx\"),\n\t\t\t\t\t)\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t// Check if any file in the directory matches the SBOM file pattern\n\t\t\t\t\t\t\tfiles, err := os.ReadDir(data.Temp().Path(\"dir-for-attest\"))\n\t\t\t\t\t\t\tassert.NilError(t, err, \"failed to read directory\")\n\n\t\t\t\t\t\t\tsbomFound := false\n\t\t\t\t\t\t\tfor _, file := range files {\n\t\t\t\t\t\t\t\tif !file.IsDir() && regexp.MustCompile(testSBOMFilePattern).MatchString(file.Name()) {\n\t\t\t\t\t\t\t\t\tsbomFound = true\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, sbomFound, \"no SBOM file matching pattern %s found\", testSBOMFilePattern)\n\n\t\t\t\t\t\t\t// Check if any file in the directory matches the provenance file pattern\n\t\t\t\t\t\t\tprovenanceFound := false\n\t\t\t\t\t\t\tfor _, file := range files {\n\t\t\t\t\t\t\t\tif !file.IsDir() && regexp.MustCompile(testProvenanceFilePattern).MatchString(file.Name()) {\n\t\t\t\t\t\t\t\t\tprovenanceFound = true\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, provenanceFound, \"no provenance file matching pattern %s found\", testProvenanceFilePattern)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildAddHost(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN ping -c 5 alpha\nRUN ping -c 5 beta\n`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\n\t\t\t\t\"build\", data.Temp().Path(),\n\t\t\t\t\"-t\", data.Identifier(),\n\t\t\t\t\"--add-host\", \"alpha:127.0.0.1\",\n\t\t\t\t\"--add-host\", \"beta:127.0.0.1\",\n\t\t\t)\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestBuildWithBuildkitConfig(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]`, testutil.CommonImage)\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", data.Temp().Path())\n\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"build with buildkit-host\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t// Get BuildkitAddr\n\t\t\t\t\tbuildkitAddr, err := buildkitutil.GetBuildkitHost(testutil.Namespace)\n\t\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\t\tbuildkitAddr = strings.TrimPrefix(buildkitAddr, \"unix://\")\n\n\t\t\t\t\t// Symlink the buildkit Socket for testing\n\t\t\t\t\tsymlinkedBuildkitAddr := filepath.Join(data.Temp().Path(), \"buildkit.sock\")\n\n\t\t\t\t\t// Do a negative test to check the setup\n\t\t\t\t\thelpers.Fail(\"build\", \"-t\", data.Identifier(), \"--buildkit-host\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr), data.Labels().Get(\"buildCtx\"))\n\n\t\t\t\t\t// Test build with the symlinked socket\n\t\t\t\t\tcmd := helpers.Custom(\"ln\", \"-s\", buildkitAddr, symlinkedBuildkitAddr)\n\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t})\n\t\t\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(), \"--buildkit-host\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr), data.Labels().Get(\"buildCtx\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"build with env specified\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t// Get BuildkitAddr\n\t\t\t\t\tbuildkitAddr, err := buildkitutil.GetBuildkitHost(testutil.Namespace)\n\t\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\t\tbuildkitAddr = strings.TrimPrefix(buildkitAddr, \"unix://\")\n\n\t\t\t\t\t// Symlink the buildkit Socket for testing\n\t\t\t\t\tsymlinkedBuildkitAddr := filepath.Join(data.Temp().Path(), \"buildkit-env.sock\")\n\n\t\t\t\t\t// Do a negative test to ensure setting up the env variable is effective\n\t\t\t\t\tcmd := helpers.Command(\"build\", \"-t\", data.Identifier(), data.Labels().Get(\"buildCtx\"))\n\t\t\t\t\tcmd.Setenv(\"BUILDKIT_HOST\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr))\n\t\t\t\t\tcmd.Run(&test.Expected{ExitCode: expect.ExitCodeGenericFail})\n\n\t\t\t\t\t// Symlink the buildkit socket for testing\n\t\t\t\t\tcmd = helpers.Custom(\"ln\", \"-s\", buildkitAddr, symlinkedBuildkitAddr)\n\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t})\n\n\t\t\t\t\tcmd = helpers.Command(\"build\", \"-t\", data.Identifier(), data.Labels().Get(\"buildCtx\"))\n\t\t\t\t\tcmd.Setenv(\"BUILDKIT_HOST\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr))\n\t\t\t\t\tcmd.Run(&test.Expected{ExitCode: expect.ExitCodeSuccess})\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/builder/builder_builder_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestBuilder(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tNoParallel: true,\n\t\tRequire: require.All(\n\t\t\tnerdtest.Build,\n\t\t\trequire.Not(require.Windows),\n\t\t),\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"PruneForce\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-test-builder-prune\"]`, testutil.CommonImage)\n\t\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\t\thelpers.Ensure(\"build\", data.Temp().Path())\n\t\t\t\t},\n\t\t\t\tCommand:  test.Command(\"builder\", \"prune\", \"--force\"),\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"PruneForceAll\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-test-builder-prune\"]`, testutil.CommonImage)\n\t\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\t\thelpers.Ensure(\"build\", data.Temp().Path())\n\t\t\t\t},\n\t\t\t\tCommand:  test.Command(\"builder\", \"prune\", \"--force\", \"--all\"),\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"builder with buildkit-host\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t// Get BuildkitAddr\n\t\t\t\t\tbuildkitAddr, err := buildkitutil.GetBuildkitHost(testutil.Namespace)\n\t\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\t\tbuildkitAddr = strings.TrimPrefix(buildkitAddr, \"unix://\")\n\n\t\t\t\t\t// Symlink the buildkit Socket for testing\n\t\t\t\t\tsymlinkedBuildkitAddr := filepath.Join(data.Temp().Path(), \"buildkit.sock\")\n\t\t\t\t\tdata.Labels().Set(\"symlinkedBuildkitAddr\", symlinkedBuildkitAddr)\n\n\t\t\t\t\t// Do a negative test to check the setup\n\t\t\t\t\thelpers.Fail(\"builder\", \"prune\", \"--force\", \"--buildkit-host\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr))\n\n\t\t\t\t\t// Test build with the symlinked socket\n\t\t\t\t\tcmd := helpers.Custom(\"ln\", \"-s\", buildkitAddr, symlinkedBuildkitAddr)\n\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t})\n\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"builder\", \"prune\", \"--force\", \"--buildkit-host\", fmt.Sprintf(\"unix://%s\", data.Labels().Get(\"symlinkedBuildkitAddr\")))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"builder with env\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t// Get BuildkitAddr\n\t\t\t\t\tbuildkitAddr, err := buildkitutil.GetBuildkitHost(testutil.Namespace)\n\t\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\t\tbuildkitAddr = strings.TrimPrefix(buildkitAddr, \"unix://\")\n\n\t\t\t\t\t// Symlink the buildkit Socket for testing\n\t\t\t\t\tsymlinkedBuildkitAddr := filepath.Join(data.Temp().Path(), \"buildkit-env.sock\")\n\t\t\t\t\tdata.Labels().Set(\"symlinkedBuildkitAddr\", symlinkedBuildkitAddr)\n\n\t\t\t\t\t// Do a negative test to ensure setting up the env variable is effective\n\t\t\t\t\tcmd := helpers.Command(\"builder\", \"prune\", \"--force\")\n\t\t\t\t\tcmd.Setenv(\"BUILDKIT_HOST\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr))\n\t\t\t\t\tcmd.Run(&test.Expected{ExitCode: expect.ExitCodeGenericFail})\n\n\t\t\t\t\t// Symlink the buildkit socket for testing\n\t\t\t\t\tcmd = helpers.Custom(\"ln\", \"-s\", buildkitAddr, symlinkedBuildkitAddr)\n\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tsymlinkedBuildkitAddr := data.Labels().Get(\"symlinkedBuildkitAddr\")\n\t\t\t\t\tcmd := helpers.Command(\"builder\", \"prune\", \"--force\")\n\t\t\t\t\tcmd.Setenv(\"BUILDKIT_HOST\", fmt.Sprintf(\"unix://%s\", symlinkedBuildkitAddr))\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Debug\",\n\t\t\t\t// `nerdctl builder debug` is currently incompatible with `docker buildx debug`.\n\t\t\t\tRequire:    require.All(require.Not(nerdtest.Docker)),\n\t\t\t\tNoParallel: true,\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-builder-debug-test-string\"]`, testutil.CommonImage)\n\t\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\t\tcmd := helpers.Command(\"builder\", \"debug\", data.Temp().Path())\n\t\t\t\t\tcmd.Feed(strings.NewReader(\"c\\n\"))\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"WithPull\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t// FIXME: this test should be rewritten to dynamically retrieve the ids, and use images\n\t\t\t\t\t// available on all platforms\n\t\t\t\t\toldImage := testutil.BusyboxImage\n\t\t\t\t\tparsedOldImage, err := referenceutil.Parse(oldImage)\n\t\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\t\toldImageSha := parsedOldImage.Digest.String()\n\n\t\t\t\t\tnewImage := testutil.AlpineImage\n\t\t\t\t\tparsedNewImage, err := referenceutil.Parse(newImage)\n\t\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\t\tnewImageSha := parsedNewImage.Digest.String()\n\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", oldImage)\n\t\t\t\t\thelpers.Ensure(\"tag\", oldImage, parsedNewImage.Domain+\"/\"+parsedNewImage.Path+\":\"+parsedNewImage.Tag)\n\n\t\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s`, parsedNewImage.Domain+\"/\"+parsedNewImage.Path+\":\"+parsedNewImage.Tag)\n\t\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\t\tdata.Labels().Set(\"oldImageSha\", oldImageSha)\n\t\t\t\t\tdata.Labels().Set(\"newImageSha\", newImageSha)\n\t\t\t\t\tdata.Labels().Set(\"base\", data.Temp().Dir())\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", testutil.AlpineImage)\n\t\t\t\t},\n\t\t\t\tSubTests: []*test.Case{\n\t\t\t\t\t{\n\t\t\t\t\t\tDescription: \"pull false\",\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"base\"), \"--pull=false\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\t\t\tErrors: []error{errors.New(data.Labels().Get(\"oldImageSha\"))},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tDescription: \"pull true\",\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"base\"), \"--pull=true\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\t\t\tErrors: []error{errors.New(data.Labels().Get(\"newImageSha\"))},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tDescription: \"no pull\",\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"base\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\t\t\tErrors: []error{errors.New(data.Labels().Get(\"newImageSha\"))},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/builder/builder_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"checkpoint\",\n\t\tShort:         \"Manage checkpoints.\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.AddCommand(\n\t\tcreateCommand(),\n\t\tlsCommand(),\n\t\trmCommand(),\n\t)\n\n\treturn cmd\n}\n\nfunc lsCommand() *cobra.Command {\n\tx := listCommand()\n\tx.Use = \"ls\"\n\tx.Aliases = []string{\"list\"}\n\treturn x\n}\nfunc rmCommand() *cobra.Command {\n\tx := removeCommand()\n\tx.Use = \"rm\"\n\tx.Aliases = []string{\"remove\"}\n\treturn x\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"path/filepath\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint\"\n)\n\nfunc createCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"create [OPTIONS] CONTAINER CHECKPOINT\",\n\t\tShort:             \"Create a checkpoint from a running container\",\n\t\tArgs:              cobra.ExactArgs(2),\n\t\tRunE:              createAction,\n\t\tValidArgsFunction: createShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().Bool(\"leave-running\", false, \"Leave the container running after checkpointing\")\n\tcmd.Flags().String(\"checkpoint-dir\", \"\", \"Checkpoint directory\")\n\treturn cmd\n}\n\nfunc processCreateFlags(cmd *cobra.Command) (types.CheckpointCreateOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.CheckpointCreateOptions{}, err\n\t}\n\n\tleaveRunning, err := cmd.Flags().GetBool(\"leave-running\")\n\tif err != nil {\n\t\treturn types.CheckpointCreateOptions{}, err\n\t}\n\tcheckpointDir, err := cmd.Flags().GetString(\"checkpoint-dir\")\n\tif err != nil {\n\t\treturn types.CheckpointCreateOptions{}, err\n\t}\n\tif checkpointDir == \"\" {\n\t\tcheckpointDir = filepath.Join(globalOptions.DataRoot, \"checkpoints\")\n\t}\n\n\treturn types.CheckpointCreateOptions{\n\t\tStdout:        cmd.OutOrStdout(),\n\t\tGOptions:      globalOptions,\n\t\tLeaveRunning:  leaveRunning,\n\t\tCheckpointDir: checkpointDir,\n\t}, nil\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\tcreateOptions, err := processCreateFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), createOptions.GOptions.Namespace, createOptions.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\terr = checkpoint.Create(ctx, client, args[0], args[1], createOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc createShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_create_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestCheckpointCreateErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"too-few-arguments\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"create\", \"too-few-arguments\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"too-many-arguments\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"create\", \"too\", \"many\", \"arguments\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-container-id\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"create\", \"foo\", \"bar\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"error creating checkpoint for container: foo\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestCheckpointCreate(t *testing.T) {\n\tconst (\n\t\tcheckpointName = \"checkpoint-bar\"\n\t\tcheckpointDir  = \"/dir/foo\"\n\t)\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\ttestCase.NoParallel = true\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"leave-running=true\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container-running\"), testutil.CommonImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container-running\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"checkpoint\", \"create\", \"--leave-running\", \"--checkpoint-dir\", checkpointDir, data.Identifier(\"container-running\"), checkpointName+\"running\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   expect.Equals(checkpointName + \"running\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"leave-running=false\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container-exit\"), testutil.CommonImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container-exit\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"checkpoint\", \"create\", \"--checkpoint-dir\", checkpointDir, data.Identifier(\"container-exit\"), checkpointName+\"exit\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   expect.Equals(checkpointName + \"exit\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"fmt\"\n\t\"text/tabwriter\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint\"\n)\n\nfunc listCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"list [OPTIONS] CONTAINER\",\n\t\tShort:             \"List checkpoints for a container\",\n\t\tArgs:              cobra.ExactArgs(1),\n\t\tRunE:              listAction,\n\t\tValidArgsFunction: listShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"checkpoint-dir\", \"\", \"Checkpoint directory\")\n\treturn cmd\n}\n\nfunc processListFlags(cmd *cobra.Command) (types.CheckpointListOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.CheckpointListOptions{}, err\n\t}\n\n\tcheckpointDir, err := cmd.Flags().GetString(\"checkpoint-dir\")\n\tif err != nil {\n\t\treturn types.CheckpointListOptions{}, err\n\t}\n\tif checkpointDir == \"\" {\n\t\tcheckpointDir = globalOptions.DataRoot + \"/checkpoints\"\n\t}\n\n\treturn types.CheckpointListOptions{\n\t\tStdout:        cmd.OutOrStdout(),\n\t\tGOptions:      globalOptions,\n\t\tCheckpointDir: checkpointDir,\n\t}, nil\n}\n\nfunc listAction(cmd *cobra.Command, args []string) error {\n\tlistOptions, err := processListFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), listOptions.GOptions.Namespace, listOptions.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tcheckpoints, err := checkpoint.List(ctx, client, args[0], listOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw := tabwriter.NewWriter(listOptions.Stdout, 4, 8, 4, ' ', 0)\n\tfmt.Fprintln(w, \"CHECKPOINT NAME\")\n\n\tfor _, cp := range checkpoints {\n\t\tfmt.Fprintln(w, cp.Name)\n\t}\n\n\treturn w.Flush()\n}\n\nfunc listShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_list_linux_test.go",
    "content": "//go:build linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestCheckpointListErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"too-few-arguments\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"list\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{ExitCode: 1}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"too-many-arguments\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"list\", \"too\", \"many\", \"arguments\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{ExitCode: 1}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-container-id\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"list\", \"no-such-container\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"error list checkpoint for container: no-such-container\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestCheckpointList(t *testing.T) {\n\tconst checkpointName = \"checkpoint-list\"\n\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\ttestCase.NoParallel = true\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"infinity\")\n\t\thelpers.Ensure(\"checkpoint\", \"create\", data.Identifier(), checkpointName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"checkpoint\", \"list\", data.Identifier())\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\t// First line is header, second should include the checkpoint name\n\t\t\tOutput: expect.Contains(\"CHECKPOINT NAME\\n\" + checkpointName + \"\\n\"),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"path/filepath\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint\"\n)\n\nfunc removeCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"rm [OPTIONS] CONTAINER CHECKPOINT\",\n\t\tShort:             \"Remove a checkpoint\",\n\t\tArgs:              cobra.ExactArgs(2),\n\t\tRunE:              removeAction,\n\t\tValidArgsFunction: removeShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"checkpoint-dir\", \"\", \"Checkpoint directory\")\n\treturn cmd\n}\n\nfunc processRemoveFlags(cmd *cobra.Command) (types.CheckpointRemoveOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.CheckpointRemoveOptions{}, err\n\t}\n\n\tcheckpointDir, err := cmd.Flags().GetString(\"checkpoint-dir\")\n\tif err != nil {\n\t\treturn types.CheckpointRemoveOptions{}, err\n\t}\n\tif checkpointDir == \"\" {\n\t\tcheckpointDir = filepath.Join(globalOptions.DataRoot, \"checkpoints\")\n\t}\n\n\treturn types.CheckpointRemoveOptions{\n\t\tStdout:        cmd.OutOrStdout(),\n\t\tGOptions:      globalOptions,\n\t\tCheckpointDir: checkpointDir,\n\t}, nil\n}\n\nfunc removeAction(cmd *cobra.Command, args []string) error {\n\tremoveOptions, err := processRemoveFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), removeOptions.GOptions.Namespace, removeOptions.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\terr = checkpoint.Remove(ctx, client, args[0], args[1], removeOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc removeShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_remove_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestCheckpointRemoveErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"too-few-arguments\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"rm\", \"too-few-arguments\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"too-many-arguments\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"rm\", \"too\", \"many\", \"arguments\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-container-id\",\n\t\t\tCommand:     test.Command(\"checkpoint\", \"rm\", \"foo\", \"bar\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"error removing checkpoint for container: foo\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestCheckpointRemove(t *testing.T) {\n\tconst (\n\t\tcheckpointName = \"checkpoint-remove\"\n\t\tcheckpointDir  = \"/dir/remove\"\n\t)\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\ttestCase.NoParallel = true\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"remove-existing\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container-running-remove\"), testutil.CommonImage, \"sleep\", \"infinity\")\n\t\t\t\thelpers.Ensure(\"checkpoint\", \"create\", \"--checkpoint-dir\", checkpointDir, data.Identifier(\"container-running-remove\"), checkpointName)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container-running-remove\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"checkpoint\", \"rm\", \"--checkpoint-dir\", checkpointDir, data.Identifier(\"container-running-remove\"), checkpointName)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   expect.Equals(\"\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"remove-nonexistent-checkpoint\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container-clean-remove\"), testutil.CommonImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container-clean-remove\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"checkpoint\", \"rm\", \"--checkpoint-dir\", checkpointDir, data.Identifier(\"container-clean-remove\"), checkpointName)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"checkpoint \" + checkpointName + \" does not exist for container\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/checkpoint/checkpoint_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/completion/completion.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage completion\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\nfunc ImageNames(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tdefer cancel()\n\n\timageList, err := client.ImageService().List(ctx, \"\")\n\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tcandidates := []string{}\n\tfor _, img := range imageList {\n\t\tcandidates = append(candidates, img.Name)\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc ContainerNames(cmd *cobra.Command, filterFunc func(containerd.ProcessStatus) bool) ([]string, cobra.ShellCompDirective) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tdefer cancel()\n\tcontainers, err := client.Containers(ctx)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tgetStatus := func(c containerd.Container) containerd.ProcessStatus {\n\t\tctx2, cancel2 := context.WithTimeout(ctx, 100*time.Millisecond)\n\t\tdefer cancel2()\n\t\ttask, err := c.Task(ctx2, nil)\n\t\tif err != nil {\n\t\t\treturn containerd.Unknown\n\t\t}\n\t\tst, err := task.Status(ctx2)\n\t\tif err != nil {\n\t\t\treturn containerd.Unknown\n\t\t}\n\t\treturn st.Status\n\t}\n\tcandidates := []string{}\n\tfor _, c := range containers {\n\t\tif filterFunc != nil {\n\t\t\tif !filterFunc(getStatus(c)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tlab, err := c.Labels(ctx)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tname := lab[labels.Name]\n\t\tif name != \"\" {\n\t\t\tcandidates = append(candidates, name)\n\t\t\tcontinue\n\t\t}\n\t\tcandidates = append(candidates, c.ID())\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\n// NetworkNames includes {\"bridge\",\"host\",\"none\"}\nfunc NetworkNames(cmd *cobra.Command, exclude []string) ([]string, cobra.ShellCompDirective) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\texcludeMap := make(map[string]struct{}, len(exclude))\n\tfor _, ex := range exclude {\n\t\texcludeMap[ex] = struct{}{}\n\t}\n\n\te, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace))\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tcandidates := []string{}\n\tnetConfigs, err := e.NetworkMap()\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tfor netName, network := range netConfigs {\n\t\tif _, ok := excludeMap[netName]; !ok {\n\t\t\tcandidates = append(candidates, netName)\n\t\t\tif network.NerdctlID != nil {\n\t\t\t\tcandidates = append(candidates, *network.NerdctlID)\n\t\t\t\tcandidates = append(candidates, (*network.NerdctlID)[0:12])\n\t\t\t}\n\t\t}\n\t}\n\tfor _, s := range []string{\"host\", \"none\"} {\n\t\tif _, ok := excludeMap[s]; !ok {\n\t\t\tcandidates = append(candidates, s)\n\t\t}\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc VolumeNames(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tvols, err := getVolumes(cmd, globalOptions)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tcandidates := []string{}\n\tfor _, v := range vols {\n\t\tcandidates = append(candidates, v.Name)\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc Platforms(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates := []string{\n\t\t\"amd64\",\n\t\t\"arm64\",\n\t\t\"riscv64\",\n\t\t\"ppc64le\",\n\t\t\"s390x\",\n\t\t\"loong64\",\n\t\t\"386\",\n\t\t\"arm\",          // alias of \"linux/arm/v7\"\n\t\t\"linux/arm/v6\", // \"arm/v6\" is invalid (interpreted as OS=\"arm\", Arch=\"v7\")\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc getVolumes(cmd *cobra.Command, globalOptions types.GlobalCommandOptions) (map[string]native.Volume, error) {\n\tvolumeSize, err := cmd.Flags().GetBool(\"size\")\n\tif err != nil {\n\t\t// The `nerdctl volume rm` does not have the flag `size`, so set it to false as the default value.\n\t\tvolumeSize = false\n\t}\n\treturn volume.Volumes(globalOptions.Namespace, globalOptions.DataRoot, globalOptions.Address, volumeSize, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/completion/completion_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage completion\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n\tncdefaults \"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc ApparmorProfiles(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) {\n\tprofiles, err := apparmorutil.Profiles()\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tvar names []string // nolint: prealloc\n\tfor _, f := range profiles {\n\t\tnames = append(names, f.Name)\n\t}\n\treturn names, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc CgroupManagerNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates := []string{\"cgroupfs\"}\n\tif ncdefaults.IsSystemdAvailable() {\n\t\tcandidates = append(candidates, \"systemd\")\n\t}\n\tif rootlessutil.IsRootless() {\n\t\tcandidates = append(candidates, \"none\")\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/completion/completion_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage completion\n\nimport (\n\t\"os/exec\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n\nfunc TestCompletion(t *testing.T) {\n\tnerdtest.Setup()\n\n\t// Note: some functions need to be tested without the automatic --namespace nerdctl-test argument, so we need\n\t// to retrieve the binary name.\n\t// Note that we know this works already, so no need to assert err.\n\tbin, _ := exec.LookPath(testutil.GetTarget())\n\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(nerdtest.Docker),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tidentifier := data.Identifier()\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\thelpers.Ensure(\"network\", \"create\", identifier)\n\t\t\thelpers.Ensure(\"volume\", \"create\", identifier)\n\t\t\tdata.Labels().Set(\"identifier\", identifier)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tidentifier := data.Identifier()\n\t\t\thelpers.Anyhow(\"network\", \"rm\", identifier)\n\t\t\thelpers.Anyhow(\"volume\", \"rm\", identifier)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"--cgroup-manager\",\n\t\t\t\tRequire:     require.Not(require.Windows),\n\t\t\t\tCommand:     test.Command(\"__complete\", \"--cgroup-manager\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"cgroupfs\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"--snapshotter\",\n\t\t\t\tRequire:     require.Not(require.Windows),\n\t\t\t\tCommand:     test.Command(\"__complete\", \"--snapshotter\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"native\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"empty\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"run\\t\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"build --network\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"build\", \"--network\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"default\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run -\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"-\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"--network\\t\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run --n\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"--n\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"--network\\t\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run --ne\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"--ne\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"--network\\t\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run --net\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"--net\", \"\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(\"host\\n\", data.Labels().Get(\"identifier\")+\"\\n\"),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run -it --net\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"-it\", \"--net\", \"\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(\"host\\n\", data.Labels().Get(\"identifier\")+\"\\n\"),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run -ti --rm --net\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"-it\", \"--rm\", \"--net\", \"\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(\"host\\n\", data.Labels().Get(\"identifier\")+\"\\n\"),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run --restart\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"--restart\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"always\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"network --rm\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"network\", \"rm\", \"\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\texpect.DoesNotContain(\"host\\n\"),\n\t\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"identifier\")+\"\\n\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run --cap-add\",\n\t\t\t\tRequire:     require.Not(require.Windows),\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"--cap-add\", \"\"),\n\t\t\t\tExpected: test.Expects(0, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"sys_admin\\n\"),\n\t\t\t\t\texpect.DoesNotContain(\"CAP_SYS_ADMIN\\n\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"volume inspect\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"volume\", \"inspect\", \"\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"identifier\") + \"\\n\"),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"volume rm\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"volume\", \"rm\", \"\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"identifier\") + \"\\n\"),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"--cgroup-manager\",\n\t\t\t\tRequire:     require.Not(require.Windows),\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"__complete\", \"--cgroup-manager\", \"\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"cgroupfs\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"empty\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"__complete\", \"\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"run\\t\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"namespace space empty\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t// mind {\"--namespace=nerdctl-test\"} vs {\"--namespace\", \"nerdctl-test\"}\n\t\t\t\t\treturn helpers.Custom(bin, \"__complete\", \"--namespace\", string(helpers.Read(nerdtest.Namespace)), \"\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"run\\t\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run -i\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"-i\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(testutil.CommonImage)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run -it\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"-it\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(testutil.CommonImage)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"run -it --rm\",\n\t\t\t\tCommand:     test.Command(\"__complete\", \"run\", \"-it\", \"--rm\", \"\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(testutil.CommonImage)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"namespace run -i\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t// mind {\"--namespace=nerdctl-test\"} vs {\"--namespace\", \"nerdctl-test\"}\n\t\t\t\t\treturn helpers.Custom(bin, \"__complete\", \"--namespace\", string(helpers.Read(nerdtest.Namespace)), \"run\", \"-i\", \"\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(testutil.CommonImage+\"\\n\")),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/completion/completion_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage completion\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc NetworkDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates := []string{\"bridge\", \"macvlan\", \"ipvlan\"}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc IPAMDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn []string{\"default\", \"host-local\", \"dhcp\"}, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc NetworkOptions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tdriver, _ := cmd.Flags().GetString(\"driver\")\n\tif driver == \"\" {\n\t\tdriver = \"bridge\"\n\t}\n\n\tvar candidates []string\n\tswitch driver {\n\tcase \"bridge\":\n\t\tcandidates = []string{\n\t\t\t\"mtu=\",\n\t\t\t\"com.docker.network.driver.mtu=\",\n\t\t\t\"ip-masq=\",\n\t\t\t\"com.docker.network.bridge.enable_ip_masquerade=\",\n\t\t}\n\tcase \"macvlan\":\n\t\tcandidates = []string{\n\t\t\t\"mtu=\",\n\t\t\t\"com.docker.network.driver.mtu=\",\n\t\t\t\"mode=bridge\",\n\t\t\t\"macvlan_mode=bridge\",\n\t\t\t\"parent=\",\n\t\t}\n\tcase \"ipvlan\":\n\t\tcandidates = []string{\n\t\t\t\"mtu=\",\n\t\t\t\"com.docker.network.driver.mtu=\",\n\t\t\t\"mode=l2\",\n\t\t\t\"mode=l3\",\n\t\t\t\"ipvlan_mode=l2\",\n\t\t\t\"ipvlan_mode=l3\",\n\t\t\t\"parent=\",\n\t\t}\n\tdefault:\n\t\tcandidates = []string{\n\t\t\t\"mtu=\",\n\t\t\t\"com.docker.network.driver.mtu=\",\n\t\t\t\"parent=\",\n\t\t}\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoSpace\n}\n\nfunc NamespaceNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tif rootlessutil.IsRootlessParent() {\n\t\t_ = rootlessutil.ParentMain(globalOptions.HostGatewayIP)\n\t\treturn nil, cobra.ShellCompDirectiveNoFileComp\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tdefer cancel()\n\tnsService := client.NamespaceService()\n\tnsList, err := nsService.List(ctx)\n\tif err != nil {\n\t\tlog.L.Warn(err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tvar candidates []string\n\tcandidates = append(candidates, nsList...)\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc SnapshotterNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tif rootlessutil.IsRootlessParent() {\n\t\t_ = rootlessutil.ParentMain(globalOptions.HostGatewayIP)\n\t\treturn nil, cobra.ShellCompDirectiveNoFileComp\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tdefer cancel()\n\tsnapshotterPlugins, err := infoutil.GetSnapshotterNames(ctx, client.IntrospectionService())\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tvar candidates []string\n\tcandidates = append(candidates, snapshotterPlugins...)\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/completion/completion_unix_nolinux.go",
    "content": "//go:build unix && !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage completion\n\nimport \"github.com/spf13/cobra\"\n\nfunc CgroupManagerNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/completion/completion_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage completion\n\nimport \"github.com/spf13/cobra\"\n\nfunc NamespaceNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc SnapshotterNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc CgroupManagerNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc NetworkDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates := []string{\"nat\"}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc IPAMDrivers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn []string{\"default\"}, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc NetworkOptions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tdriver, _ := cmd.Flags().GetString(\"driver\")\n\tif driver == \"\" {\n\t\tdriver = \"nat\"\n\t}\n\n\tvar candidates []string\n\tswitch driver {\n\tcase \"nat\":\n\t\tcandidates = []string{\n\t\t\t\"mtu=\",\n\t\t\t\"com.docker.network.driver.mtu=\",\n\t\t}\n\tdefault:\n\t\tcandidates = []string{\n\t\t\t\"mtu=\",\n\t\t\t\"com.docker.network.driver.mtu=\",\n\t\t}\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoSpace\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc Command() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:              \"compose [flags] COMMAND\",\n\t\tShort:            \"Compose\",\n\t\tRunE:             helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:     true,\n\t\tSilenceErrors:    true,\n\t\tTraverseChildren: true, // required for global short hands like -f\n\t}\n\t// `-f` is a nonPersistentAlias, as it conflicts with `nerdctl compose logs --follow`\n\thelpers.AddPersistentStringArrayFlag(cmd, \"file\", nil, []string{\"f\"}, nil, \"\", \"Specify an alternate compose file\")\n\tcmd.PersistentFlags().String(\"project-directory\", \"\", \"Specify an alternate working directory\")\n\tcmd.PersistentFlags().StringP(\"project-name\", \"p\", \"\", \"Specify an alternate project name\")\n\tcmd.PersistentFlags().String(\"env-file\", \"\", \"Specify an alternate environment file\")\n\tcmd.PersistentFlags().String(\"ipfs-address\", \"\", \"multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\")\n\tcmd.PersistentFlags().StringArray(\"profile\", []string{}, \"Specify a profile to enable\")\n\n\tcmd.AddCommand(\n\t\tupCommand(),\n\t\tlogsCommand(),\n\t\tconfigCommand(),\n\t\tcopyCommand(),\n\t\tbuildCommand(),\n\t\texecCommand(),\n\t\timagesCommand(),\n\t\tportCommand(),\n\t\tpushCommand(),\n\t\tpullCommand(),\n\t\tdownCommand(),\n\t\tpsCommand(),\n\t\tkillCommand(),\n\t\trestartCommand(),\n\t\tremoveCommand(),\n\t\trunCommand(),\n\t\tversionCommand(),\n\t\tstartCommand(),\n\t\tstopCommand(),\n\t\tpauseCommand(),\n\t\tunpauseCommand(),\n\t\ttopCommand(),\n\t\tcreateCommand(),\n\t)\n\n\treturn cmd\n}\n\nfunc getComposeOptions(cmd *cobra.Command, debugFull, experimental bool) (composer.Options, error) {\n\tnerdctlCmd, nerdctlArgs := helpers.GlobalFlags(cmd)\n\tprojectDirectory, err := cmd.Flags().GetString(\"project-directory\")\n\tif err != nil {\n\t\treturn composer.Options{}, err\n\t}\n\tenvFile, err := cmd.Flags().GetString(\"env-file\")\n\tif err != nil {\n\t\treturn composer.Options{}, err\n\t}\n\tprojectName, err := cmd.Flags().GetString(\"project-name\")\n\tif err != nil {\n\t\treturn composer.Options{}, err\n\t}\n\tfiles, err := cmd.Flags().GetStringArray(\"file\")\n\tif err != nil {\n\t\treturn composer.Options{}, err\n\t}\n\tipfsAddressStr, err := cmd.Flags().GetString(\"ipfs-address\")\n\tif err != nil {\n\t\treturn composer.Options{}, err\n\t}\n\tprofiles, err := cmd.Flags().GetStringArray(\"profile\")\n\tif err != nil {\n\t\treturn composer.Options{}, err\n\t}\n\n\treturn composer.Options{\n\t\tProject:          projectName,\n\t\tProjectDirectory: projectDirectory,\n\t\tConfigPaths:      files,\n\t\tProfiles:         profiles,\n\t\tEnvFile:          envFile,\n\t\tNerdctlCmd:       nerdctlCmd,\n\t\tNerdctlArgs:      nerdctlArgs,\n\t\tDebugPrintFull:   debugFull,\n\t\tExperimental:     experimental,\n\t\tIPFSAddress:      ipfsAddressStr,\n\t}, nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_build.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc buildCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"build [flags] [SERVICE...]\",\n\t\tShort:         \"Build or rebuild services\",\n\t\tRunE:          buildAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringArray(\"build-arg\", nil, \"Set build-time variables for services.\")\n\tcmd.Flags().Bool(\"no-cache\", false, \"Do not use cache when building the image.\")\n\tcmd.Flags().String(\"progress\", \"\", \"Set type of progress output (auto, plain, tty). Use plain to show container output\")\n\n\treturn cmd\n}\n\nfunc buildAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuildArg, err := cmd.Flags().GetStringArray(\"build-arg\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoCache, err := cmd.Flags().GetBool(\"no-cache\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tprogress, err := cmd.Flags().GetString(\"progress\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tbo := composer.BuildOptions{\n\t\tArgs:     buildArg,\n\t\tNoCache:  noCache,\n\t\tProgress: progress,\n\t}\n\treturn c.Build(ctx, bo, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_build_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeBuild(t *testing.T) {\n\tdockerfile := \"FROM \" + testutil.CommonImage\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Build\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// Make sure we shard the image name to something unique to the test to avoid conflicts with other tests\n\t\timageSvc0 := data.Identifier(\"svc0\")\n\t\timageSvc1 := data.Identifier(\"svc1\")\n\t\timageSvc2 := data.Identifier(\"svc2\")\n\n\t\t// We are not going to run them, so, ports conflicts should not matter here\n\t\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    build: .\n    image: %s\n    depends_on:\n    - svc1\n  svc1:\n    build: .\n    image: %s\n  svc2:\n    image: %s\n    build:\n      context: .\n      dockerfile_inline: |\n        FROM %s\n`, imageSvc0, imageSvc1, imageSvc2, testutil.CommonImage)\n\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\tdata.Labels().Set(\"composeYaml\", data.Temp().Path(\"compose.yaml\"))\n\t\tdata.Labels().Set(\"imageSvc0\", imageSvc0)\n\t\tdata.Labels().Set(\"imageSvc1\", imageSvc1)\n\t\tdata.Labels().Set(\"imageSvc2\", imageSvc2)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"build svc0\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"build\", \"svc0\")\n\t\t\t},\n\n\t\t\tCommand: test.Command(\"images\"),\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"imageSvc0\")),\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"imageSvc1\")),\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"imageSvc2\")),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"build svc2\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"build\", \"svc2\")\n\t\t\t},\n\n\t\t\tCommand: test.Command(\"images\"),\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"imageSvc2\")),\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"imageSvc1\")),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"build svc0, svc1, svc2\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"build\", \"svc0\", \"svc1\", \"svc2\")\n\t\t\t},\n\n\t\t\tCommand: test.Command(\"images\"),\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"imageSvc0\"), data.Labels().Get(\"imageSvc1\"), data.Labels().Get(\"imageSvc2\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"build no arg\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"build\")\n\t\t\t},\n\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"build bogus\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Labels().Get(\"composeYaml\"),\n\t\t\t\t\t\"build\",\n\t\t\t\t\t\"svc0\",\n\t\t\t\t\t\"svc100\",\n\t\t\t\t)\n\t\t\t},\n\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"no such service: svc100\")}, nil),\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"imageSvc0\") != \"\" {\n\t\t\thelpers.Anyhow(\"rmi\", data.Labels().Get(\"imageSvc0\"), data.Labels().Get(\"imageSvc1\"), data.Labels().Get(\"imageSvc2\"))\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_config.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc configCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"config\",\n\t\tShort:         \"Validate and view the Compose file\",\n\t\tRunE:          configAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only validate the configuration, don't print anything.\")\n\tcmd.Flags().Bool(\"services\", false, \"Print the service names, one per line.\")\n\tcmd.Flags().Bool(\"volumes\", false, \"Print the volume names, one per line.\")\n\tcmd.Flags().String(\"hash\", \"\", \"Print the service config hash, one per line.\")\n\tcmd.RegisterFlagCompletionFunc(\"hash\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"\\\"*\\\"\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc configAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(args) != 0 {\n\t\t// TODO: support specifying service names as args\n\t\treturn fmt.Errorf(\"arguments %v not supported\", args)\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tservices, err := cmd.Flags().GetBool(\"services\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tvolumes, err := cmd.Flags().GetBool(\"volumes\")\n\tif err != nil {\n\t\treturn err\n\t}\n\thash, err := cmd.Flags().GetString(\"hash\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif quiet {\n\t\treturn nil\n\t}\n\tco := composer.ConfigOptions{\n\t\tServices: services,\n\t\tVolumes:  volumes,\n\t\tHash:     hash,\n\t}\n\treturn c.Config(ctx, cmd.OutOrStdout(), co)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_config_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeConfig(t *testing.T) {\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  hello:\n    image: %s\n`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", data.Temp().Path(\"compose.yaml\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"config contains service name\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"config\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"hello:\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"config --services is exactly service name\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Labels().Get(\"composeYaml\"),\n\t\t\t\t\t\"config\",\n\t\t\t\t\t\"--services\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"hello\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"config --hash=* contains service name\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"config\", \"--hash=*\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"hello\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeConfigWithPrintServiceHash(t *testing.T) {\n\tconst dockerComposeYAML = `\nservices:\n  hello:\n    image: alpine:%s\n`\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(fmt.Sprintf(dockerComposeYAML, \"3.13\"), \"compose.yaml\")\n\n\t\thash := helpers.Capture(\n\t\t\t\"compose\",\n\t\t\t\"-f\",\n\t\t\tdata.Temp().Path(\"compose.yaml\"),\n\t\t\t\"config\",\n\t\t\t\"--hash=hello\",\n\t\t)\n\n\t\tdata.Labels().Set(\"hash\", hash)\n\n\t\tdata.Temp().Save(fmt.Sprintf(dockerComposeYAML, \"3.14\"), \"compose.yaml\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\n\t\t\t\"compose\",\n\t\t\t\"-f\",\n\t\t\tdata.Temp().Path(\"compose.yaml\"),\n\t\t\t\"config\",\n\t\t\t\"--hash=hello\",\n\t\t)\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t, data.Labels().Get(\"hash\") != stdout, \"hash should be different\")\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeConfigWithMultipleFile(t *testing.T) {\n\tconst dockerComposeBase = `\nservices:\n  hello1:\n    image: alpine:3.13\n`\n\n\tconst dockerComposeTest = `\nservices:\n  hello2:\n    image: alpine:3.14\n`\n\n\tconst dockerComposeOverride = `\nservices:\n  hello1:\n    image: alpine:3.14\n`\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeBase, \"compose.yaml\")\n\t\tdata.Temp().Save(dockerComposeTest, \"docker-compose.test.yml\")\n\t\tdata.Temp().Save(dockerComposeOverride, \"docker-compose.override.yml\")\n\n\t\tdata.Labels().Set(\"composeDir\", data.Temp().Path())\n\t\tdata.Labels().Set(\"composeYaml\", data.Temp().Path(\"compose.yaml\"))\n\t\tdata.Labels().Set(\"composeYamlTest\", data.Temp().Path(\"docker-compose.test.yml\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"config override\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\", data.Labels().Get(\"composeYaml\"),\n\t\t\t\t\t\"-f\", data.Labels().Get(\"composeYamlTest\"),\n\t\t\t\t\t\"config\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(\n\t\t\t\texpect.ExitCodeSuccess,\n\t\t\t\tnil,\n\t\t\t\texpect.Contains(\"alpine:3.13\", \"alpine:3.14\", \"hello1\", \"hello2\"),\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tDescription: \"project dir\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"--project-directory\", data.Labels().Get(\"composeDir\"), \"config\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"alpine:3.14\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"project dir services\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"--project-directory\", data.Labels().Get(\"composeDir\"), \"config\", \"--services\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"hello1\\n\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeConfigWithComposeFileEnv(t *testing.T) {\n\tconst dockerComposeBase = `\nservices:\n  hello1:\n    image: alpine:3.13\n`\n\n\tconst dockerComposeTest = `\nservices:\n  hello2:\n    image: alpine:3.14\n`\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeBase, \"compose.yaml\")\n\t\tdata.Temp().Save(dockerComposeTest, \"docker-compose.test.yml\")\n\n\t\tdata.Labels().Set(\"composeDir\", data.Temp().Path())\n\t\tdata.Labels().Set(\"composeYaml\", data.Temp().Path(\"compose.yaml\"))\n\t\tdata.Labels().Set(\"composeYamlTest\", data.Temp().Path(\"docker-compose.test.yml\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"env config\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"config\",\n\t\t\t\t)\n\t\t\t\tcmd.Setenv(\"COMPOSE_FILE\", data.Labels().Get(\"composeYaml\")+\",\"+data.Labels().Get(\"composeYamlTest\"))\n\t\t\t\tcmd.Setenv(\"COMPOSE_PATH_SEPARATOR\", \",\")\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(\n\t\t\t\texpect.ExitCodeSuccess,\n\t\t\t\tnil,\n\t\t\t\texpect.Contains(\"alpine:3.13\", \"alpine:3.14\", \"hello1\", \"hello2\"),\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tDescription: \"env with project dir\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"--project-directory\", data.Labels().Get(\"composeDir\"),\n\t\t\t\t\t\"config\",\n\t\t\t\t)\n\t\t\t\tcmd.Setenv(\"COMPOSE_FILE\", data.Labels().Get(\"composeYaml\")+\",\"+data.Labels().Get(\"composeYamlTest\"))\n\t\t\t\tcmd.Setenv(\"COMPOSE_PATH_SEPARATOR\", \",\")\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(\n\t\t\t\texpect.ExitCodeSuccess,\n\t\t\t\tnil,\n\t\t\t\texpect.Contains(\"alpine:3.13\", \"alpine:3.14\", \"hello1\", \"hello2\"),\n\t\t\t),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeConfigWithEnvFile(t *testing.T) {\n\tconst dockerComposeYAML = `\nservices:\n  hello:\n    image: ${image}\n`\n\tconst envFileContent = `\nimage: hello-world\n`\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Temp().Save(envFileContent, \"env\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\",\n\t\t\t\"-f\", data.Temp().Path(\"compose.yaml\"),\n\t\t\t\"--env-file\", data.Temp().Path(\"env\"),\n\t\t\t\"config\",\n\t\t)\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"image: hello-world\"))\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_cp.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc copyCommand() *cobra.Command {\n\tusage := `cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-\n       nerdctl compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH`\n\tvar cmd = &cobra.Command{\n\t\tUse:           usage,\n\t\tShort:         \"Copy files/folders between a service container and the local filesystem\",\n\t\tArgs:          cobra.ExactArgs(2),\n\t\tRunE:          copyAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().Bool(\"dry-run\", false, \"Execute command in dry run mode\")\n\tcmd.Flags().BoolP(\"follow-link\", \"L\", false, \"Always follow symbol link in SRC_PATH\")\n\tcmd.Flags().Int(\"index\", 0, \"index of the container if service has multiple replicas\")\n\treturn cmd\n}\n\nfunc copyAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsource := args[0]\n\tif source == \"\" {\n\t\treturn errors.New(\"source can not be empty\")\n\t}\n\tdestination := args[1]\n\tif destination == \"\" {\n\t\treturn errors.New(\"destination can not be empty\")\n\t}\n\n\tdryRun, err := cmd.Flags().GetBool(\"dry-run\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfollowLink, err := cmd.Flags().GetBool(\"follow-link\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tindex, err := cmd.Flags().GetInt(\"index\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// rootless cp runs in the host namespaces, so the address is different\n\tif rootlessutil.IsRootless() {\n\t\tglobalOptions.Address, err = rootlessutil.RootlessContainredSockAddress()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tco := composer.CopyOptions{\n\t\tSource:      source,\n\t\tDestination: destination,\n\t\tIndex:       index,\n\t\tFollowLink:  followLink,\n\t\tDryRun:      dryRun,\n\t}\n\treturn c.Copy(ctx, co)\n\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_cp_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeCopy(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\tconst testFileContent = \"test-file-content\"\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcompYamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\thelpers.Ensure(\"compose\", \"-f\", compYamlPath, \"up\", \"-d\")\n\n\t\tsrcFilePath := data.Temp().Save(testFileContent, \"test-file\")\n\n\t\tdata.Labels().Set(\"composeYaml\", compYamlPath)\n\t\tdata.Labels().Set(\"srcFile\", srcFilePath)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"test copy to service /dest-no-exist-no-slash\",\n\t\t\t// These are expected to run in sequence\n\t\t\tNoParallel: true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\",\n\t\t\t\t\t\"-f\", data.Labels().Get(\"composeYaml\"),\n\t\t\t\t\t\"cp\", data.Labels().Get(\"srcFile\"), \"svc0:/dest-no-exist-no-slash\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"test copy from service test-file2\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\",\n\t\t\t\t\t\"-f\", data.Labels().Get(\"composeYaml\"),\n\t\t\t\t\t\"cp\", \"svc0:/dest-no-exist-no-slash\", data.Temp().Path(\"test-file2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tcopied := data.Temp().Load(\"test-file2\")\n\t\t\t\t\t\tassert.Equal(t, copied, testFileContent)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc createCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"create [flags] [SERVICE...]\",\n\t\tShort:         \"Creates containers for one or more services\",\n\t\tRunE:          createAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().Bool(\"build\", false, \"Build images before starting containers.\")\n\tcmd.Flags().Bool(\"no-build\", false, \"Don't build an image even if it's missing, conflict with --build.\")\n\tcmd.Flags().Bool(\"force-recreate\", false, \"Recreate containers even if their configuration and image haven't changed.\")\n\tcmd.Flags().Bool(\"no-recreate\", false, \"Don't recreate containers if they exist, conflict with --force-recreate.\")\n\tcmd.Flags().String(\"pull\", \"missing\", \"Pull images before running. (support always|missing|never)\")\n\treturn cmd\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuild, err := cmd.Flags().GetBool(\"build\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoBuild, err := cmd.Flags().GetBool(\"no-build\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif build && noBuild {\n\t\treturn errors.New(\"flag --build and --no-build cannot be specified together\")\n\t}\n\tforceRecreate, err := cmd.Flags().GetBool(\"force-recreate\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoRecreate, err := cmd.Flags().GetBool(\"no-recreate\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif forceRecreate && noRecreate {\n\t\treturn errors.New(\"flag --force-recreate and --no-recreate cannot be specified together\")\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topt := composer.CreateOptions{\n\t\tBuild:         build,\n\t\tNoBuild:       noBuild,\n\t\tForceRecreate: forceRecreate,\n\t\tNoRecreate:    noRecreate,\n\t}\n\n\tif cmd.Flags().Changed(\"pull\") {\n\t\tpull, err := cmd.Flags().GetString(\"pull\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topt.Pull = &pull\n\t}\n\n\treturn c.Create(ctx, opt, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_create_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeCreate(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcompYamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", compYamlPath)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"`compose create` should work\",\n\t\t\t// These are sequential\n\t\t\tNoParallel: true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"create\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`compose create` should have created service container (in `created` status)\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"ps\", \"svc0\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t,\n\t\t\t\t\tstrings.Contains(stdout, \"created\") || strings.Contains(stdout, \"Created\"),\n\t\t\t\t\t\"stdout should contain `created`\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`created container can be started by `compose start`\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"start\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeCreateDependency(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    depends_on:\n    - \"svc1\"\n  svc1:\n    image: %s\n`, testutil.CommonImage, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcompYamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", compYamlPath)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"`compose create` should work\",\n\t\t\t// These are sequential\n\t\t\tNoParallel: true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"create\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`compose create` should have created svc0 (in `created` status)\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"ps\", \"svc0\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t,\n\t\t\t\t\tstrings.Contains(stdout, \"created\") || strings.Contains(stdout, \"Created\"),\n\t\t\t\t\t\"stdout should contain `created`\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`compose create` should have created svc1 (in `created` status)\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"ps\", \"svc1\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t,\n\t\t\t\t\tstrings.Contains(stdout, \"created\") || strings.Contains(stdout, \"Created\"),\n\t\t\t\t\t\"stdout should contain `created`\")\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeCreatePull(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.NoParallel = true\n\ttestCase.Require = nerdtest.Private\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"compose create --pull never fails when image missing\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"create\", \"--pull\", \"never\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"compose create --pull missing (default) pulls and creates a container\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"create\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"svc0\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(`Created|created`))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"compose create --pull always pulls and creates a container\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"create\", \"--pull\", \"always\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"svc0\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(`Created|created`))),\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeCreatePullInvalidOption(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// nerver isn't never.\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"create\", \"--pull\", \"nerver\")\n\t}\n\n\ttestCase.Expected = test.Expects(1, []error{errors.New(`invalid --pull option \\\"nerver\\\"`)}, nil)\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif path := data.Labels().Get(\"composeYAML\"); path != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", path, \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeCreateBuild(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.NoParallel = true\n\ttestCase.Require = require.All(\n\t\tnerdtest.Private,\n\t\tnerdtest.Build,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\timageSvc0 := data.Identifier(\"composebuild_svc0\")\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    build: .\n    image: %s\n`, imageSvc0)\n\t\tdockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"imageName\", imageSvc0)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"compose create --no-build fails when image needs to be built\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"create\", \"--no-build\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"compose create --build builds image and creates container\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"create\", \"--build\")\n\t\t\t\thelpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"images\", \"svc0\").Run(\n\t\t\t\t\t&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, data.Labels().Get(\"imageName\")))\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"svc0\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(`Created|created`))),\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"imageName\"))\n\t\thelpers.Anyhow(\"builder\", \"prune\", \"--all\", \"--force\")\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeCreateWritesConfigHashLabel(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n`, testutil.CommonImage)\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"containerName\", serviceparser.DefaultContainerName(projectName, \"svc0\", \"1\"))\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"create\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"inspect\", \"--format\", \"{{json .Config.Labels}}\", data.Labels().Get(\"containerName\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(\"com.docker.compose.config-hash\"))\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif path := data.Labels().Get(\"composeYAML\"); path != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", path, \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_down.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc downCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"down\",\n\t\tShort:         \"Remove containers and associated resources\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          downAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"volumes\", \"v\", false, \"Remove named volumes declared in the `volumes` section of the Compose file and anonymous volumes attached to containers.\")\n\tcmd.Flags().Bool(\"remove-orphans\", false, \"Remove containers for services not defined in the Compose file.\")\n\treturn cmd\n}\n\nfunc downAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvolumes, err := cmd.Flags().GetBool(\"volumes\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tremoveOrphans, err := cmd.Flags().GetBool(\"remove-orphans\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdownOpts := composer.DownOptions{\n\t\tRemoveVolumes: volumes,\n\t\tRemoveOrphans: removeOrphans,\n\t}\n\treturn c.Down(ctx, downOpts)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_down_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeDownRemoveUsedNetwork(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdockerComposeYAMLOrphan := fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\t\tdockerComposeYAMLFull := fmt.Sprintf(`\n%s\n  orphan:\n    image: %s\n    command: \"sleep infinity\"\n`, dockerComposeYAMLOrphan, testutil.CommonImage)\n\n\t\tcomposeOrphanPath := data.Temp().Save(dockerComposeYAMLOrphan, \"compose-orphan.yaml\")\n\t\tcomposeFullPath := data.Temp().Save(dockerComposeYAMLFull, \"compose-full.yaml\")\n\n\t\tprojectName := data.Identifier(\"project\")\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\ttestContainer := serviceparser.DefaultContainerName(projectName, \"test\", \"1\")\n\t\torphanContainer := serviceparser.DefaultContainerName(projectName, \"orphan\", \"1\")\n\n\t\tdata.Labels().Set(\"composeOrphan\", composeOrphanPath)\n\t\tdata.Labels().Set(\"composeFull\", composeFullPath)\n\t\tdata.Labels().Set(\"projectName\", projectName)\n\n\t\thelpers.Ensure(\"compose\", \"-p\", projectName, \"-f\", composeFullPath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainer)\n\t\tnerdtest.EnsureContainerStarted(helpers, orphanContainer)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", data.Labels().Get(\"composeOrphan\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors: []error{\n\t\t\t\tfmt.Errorf(\"in use\"),\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif composeFull := data.Labels().Get(\"composeFull\"); composeFull != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", composeFull, \"down\", \"--remove-orphans\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeDownRemoveOrphans(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdockerComposeYAMLOrphan := fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\t\tdockerComposeYAMLFull := fmt.Sprintf(`\n%s\n  orphan:\n    image: %s\n    command: \"sleep infinity\"\n`, dockerComposeYAMLOrphan, testutil.CommonImage)\n\n\t\tcomposeOrphanPath := data.Temp().Save(dockerComposeYAMLOrphan, \"compose-orphan.yaml\")\n\t\tcomposeFullPath := data.Temp().Save(dockerComposeYAMLFull, \"compose-full.yaml\")\n\n\t\tprojectName := data.Identifier(\"project\")\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\ttestContainer := serviceparser.DefaultContainerName(projectName, \"test\", \"1\")\n\t\torphanContainer := serviceparser.DefaultContainerName(projectName, \"orphan\", \"1\")\n\n\t\tdata.Labels().Set(\"composeOrphan\", composeOrphanPath)\n\t\tdata.Labels().Set(\"composeFull\", composeFullPath)\n\t\tdata.Labels().Set(\"projectName\", projectName)\n\t\tdata.Labels().Set(\"orphanContainer\", orphanContainer)\n\n\t\thelpers.Ensure(\"compose\", \"-p\", projectName, \"-f\", composeFullPath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainer)\n\t\tnerdtest.EnsureContainerStarted(helpers, orphanContainer)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", data.Labels().Get(\"composeOrphan\"), \"down\", \"--remove-orphans\")\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"orphan container removed\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", data.Labels().Get(\"composeFull\"), \"ps\", \"-a\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   expect.DoesNotContain(data.Labels().Get(\"orphanContainer\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif composeFull := data.Labels().Get(\"composeFull\"); composeFull != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", composeFull, \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_exec.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/moby/term\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc execCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"exec [flags] SERVICE COMMAND [ARGS...]\",\n\t\tShort:         \"Execute a command in a running container of the service\",\n\t\tArgs:          cobra.MinimumNArgs(2),\n\t\tRunE:          execAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().SetInterspersed(false)\n\n\t_, isTerminal := term.GetFdInfo(os.Stdout)\n\tcmd.Flags().BoolP(\"no-TTY\", \"T\", !isTerminal, \"Disable pseudo-TTY allocation. By default nerdctl compose exec allocates a TTY.\")\n\tcmd.Flags().BoolP(\"detach\", \"d\", false, \"Detached mode: Run containers in the background\")\n\tcmd.Flags().StringP(\"workdir\", \"w\", \"\", \"Working directory inside the container\")\n\t// env needs to be StringArray, not StringSlice, to prevent \"FOO=foo1,foo2\" from being split to {\"FOO=foo1\", \"foo2\"}\n\tcmd.Flags().StringArrayP(\"env\", \"e\", nil, \"Set environment variables\")\n\tcmd.Flags().Bool(\"privileged\", false, \"Give extended privileges to the command\")\n\tcmd.Flags().StringP(\"user\", \"u\", \"\", \"Username or UID (format: <name|uid>[:<group|gid>])\")\n\tcmd.Flags().Int(\"index\", 1, \"index of the container if the service has multiple instances.\")\n\n\tcmd.Flags().BoolP(\"interactive\", \"i\", true, \"Keep STDIN open even if not attached\")\n\tcmd.Flags().MarkHidden(\"interactive\")\n\t// The -t does not has effect to keep the compatibility with docker.\n\t// The proposal of -t is to keep \"muscle memory\" with compose v1: https://github.com/docker/compose/issues/9207\n\t// FYI: https://github.com/docker/compose/blob/v2.23.1/cmd/compose/exec.go#L77\n\tcmd.Flags().BoolP(\"tty\", \"t\", true, \"Allocate a pseudo-TTY\")\n\tcmd.Flags().MarkHidden(\"tty\")\n\n\treturn cmd\n}\n\nfunc execAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinteractive, err := cmd.Flags().GetBool(\"interactive\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoTty, err := cmd.Flags().GetBool(\"no-TTY\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdetach, err := cmd.Flags().GetBool(\"detach\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tworkdir, err := cmd.Flags().GetString(\"workdir\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tenv, err := cmd.Flags().GetStringArray(\"env\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tprivileged, err := cmd.Flags().GetBool(\"privileged\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tuser, err := cmd.Flags().GetString(\"user\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tindex, err := cmd.Flags().GetInt(\"index\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif index < 1 {\n\t\treturn errors.New(\"index starts from 1 and should be equal or greater than 1\")\n\t}\n\t// https://github.com/containerd/nerdctl/blob/v1.0.0/cmd/nerdctl/exec.go#L116\n\tif interactive && detach {\n\t\treturn errors.New(\"currently flag -i and -d cannot be specified together (FIXME)\")\n\t}\n\t// https://github.com/containerd/nerdctl/blob/v1.0.0/cmd/nerdctl/exec.go#L122\n\tif !noTty && detach {\n\t\treturn errors.New(\"currently flag -d should be specified with --no-TTY (FIXME)\")\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\teo := composer.ExecOptions{\n\t\tServiceName: args[0],\n\t\tIndex:       index,\n\n\t\tInteractive: interactive,\n\t\tTty:         !noTty,\n\t\tDetach:      detach,\n\t\tWorkDir:     workdir,\n\t\tEnv:         env,\n\t\tPrivileged:  privileged,\n\t\tUser:        user,\n\t\tArgs:        args[1:],\n\t}\n\n\treturn c.Exec(ctx, eo)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_exec_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeExec(t *testing.T) {\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n  svc1:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tyamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"YAMLPath\", yamlPath)\n\t\thelpers.Ensure(\"compose\", \"-f\", yamlPath, \"up\", \"-d\", \"svc0\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"exec no tty\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Labels().Get(\"YAMLPath\"),\n\t\t\t\t\t\"exec\",\n\t\t\t\t\t\"-i=false\",\n\t\t\t\t\t\"--no-TTY\",\n\t\t\t\t\t\"svc0\",\n\t\t\t\t\t\"echo\",\n\t\t\t\t\t\"success\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"success\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"exec no tty with workdir\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Labels().Get(\"YAMLPath\"),\n\t\t\t\t\t\"exec\",\n\t\t\t\t\t\"-i=false\",\n\t\t\t\t\t\"--no-TTY\",\n\t\t\t\t\t\"--workdir\",\n\t\t\t\t\t\"/tmp\",\n\t\t\t\t\t\"svc0\",\n\t\t\t\t\t\"pwd\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"/tmp\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"cannot exec on non-running service\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"YAMLPath\"), \"exec\", \"svc1\", \"echo\", \"success\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with env\",\n\t\t\tEnv: map[string]string{\n\t\t\t\t\"CORGE\":  \"corge-value-in-host\",\n\t\t\t\t\"GARPLY\": \"garply-value-in-host\",\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Labels().Get(\"YAMLPath\"),\n\t\t\t\t\t\"exec\",\n\t\t\t\t\t\"-i=false\",\n\t\t\t\t\t\"--no-TTY\",\n\t\t\t\t\t\"--env\", \"FOO=foo1,foo2\",\n\t\t\t\t\t\"--env\", \"BAR=bar1 bar2\",\n\t\t\t\t\t\"--env\", \"BAZ=\",\n\t\t\t\t\t\"--env\", \"QUX\", // not exported in OS\n\t\t\t\t\t\"--env\", \"QUUX=quux1\",\n\t\t\t\t\t\"--env\", \"QUUX=quux2\",\n\t\t\t\t\t\"--env\", \"CORGE\", // OS exported\n\t\t\t\t\t\"--env\", \"GRAULT=grault_key=grault_value\", // value contains `=` char\n\t\t\t\t\t\"--env\", \"GARPLY=\", // OS exported\n\t\t\t\t\t\"--env\", \"WALDO=\", // not exported in OS\n\t\t\t\t\t\"svc0\",\n\t\t\t\t\t\"env\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\texpect.Contains(\n\t\t\t\t\t\"\\nFOO=foo1,foo2\\n\",\n\t\t\t\t\t\"\\nBAR=bar1 bar2\\n\",\n\t\t\t\t\t\"\\nBAZ=\\n\",\n\t\t\t\t\t\"\\nQUUX=quux2\\n\",\n\t\t\t\t\t\"\\nCORGE=corge-value-in-host\\n\",\n\t\t\t\t\t\"\\nGRAULT=grault_key=grault_value\\n\",\n\t\t\t\t\t\"\\nGARPLY=\\n\",\n\t\t\t\t\t\"\\nWALDO=\\n\"),\n\t\t\t\texpect.DoesNotContain(\"QUX\"),\n\t\t\t)),\n\t\t},\n\t}\n\n\tuserSubTest := &test.Case{\n\t\tDescription: \"with user\",\n\t\tSubTests:    []*test.Case{},\n\t}\n\n\tuserCasesMap := map[string]string{\n\t\t\"\":             \"uid=0(root) gid=0(root)\",\n\t\t\"1000\":         \"uid=1000 gid=0(root)\",\n\t\t\"1000:users\":   \"uid=1000 gid=100(users)\",\n\t\t\"guest\":        \"uid=405(guest) gid=100(users)\",\n\t\t\"nobody\":       \"uid=65534(nobody) gid=65534(nobody)\",\n\t\t\"nobody:users\": \"uid=65534(nobody) gid=100(users)\",\n\t}\n\n\tfor k, v := range userCasesMap {\n\t\tuserSubTest.SubTests = append(userSubTest.SubTests, &test.Case{\n\t\t\tDescription: k + \" \" + v,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\targs := []string{\"compose\", \"-f\", data.Labels().Get(\"YAMLPath\"), \"exec\", \"-i=false\", \"--no-TTY\"}\n\t\t\t\tif k != \"\" {\n\t\t\t\t\targs = append(args, \"--user\", k)\n\t\t\t\t}\n\t\t\t\targs = append(args, \"svc0\", \"id\")\n\t\t\t\treturn helpers.Command(args...)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(v)),\n\t\t})\n\t}\n\n\ttestCase.SubTests = append(testCase.SubTests, userSubTest)\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeExecTTY(t *testing.T) {\n\tconst expectedOutput = \"speed 38400 baud\"\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n  svc1:\n    image: %s\n`, testutil.CommonImage, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tyamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"YAMLPath\", yamlPath)\n\t\thelpers.Ensure(\n\t\t\t\"compose\",\n\t\t\t\"-f\",\n\t\t\tyamlPath,\n\t\t\t\"run\",\n\t\t\t\"-d\",\n\t\t\t\"-i=false\",\n\t\t\t\"--name\",\n\t\t\tdata.Identifier(),\n\t\t\t\"svc0\",\n\t\t\t\"sleep\",\n\t\t\t\"1h\",\n\t\t)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\t// FIXME?\n\t\t// similar, other test does *also* remove the container\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"YAMLPath\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"stty exec\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"YAMLPath\"), \"exec\", \"svc0\", \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(expectedOutput)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"-i=false stty exec\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"YAMLPath\"), \"exec\", \"-i=false\", \"svc0\", \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(expectedOutput)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"--no-TTY stty exec\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"YAMLPath\"), \"exec\", \"--no-TTY\", \"svc0\", \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"-i=false --no-TTY stty exec\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Labels().Get(\"YAMLPath\"),\n\t\t\t\t\t\"exec\",\n\t\t\t\t\t\"-i=false\",\n\t\t\t\t\t\"--no-TTY\",\n\t\t\t\t\t\"svc0\",\n\t\t\t\t\t\"stty\",\n\t\t\t\t)\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeExecWithIndex(t *testing.T) {\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n    deploy:\n      replicas: 3\n`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tyamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"YAMLPath\", yamlPath)\n\t\tdata.Labels().Set(\"projectName\", strings.ToLower(filepath.Base(data.Temp().Dir())))\n\n\t\thelpers.Ensure(\"compose\", \"-f\", yamlPath, \"up\", \"-d\", \"svc0\")\n\n\t\t// Make sure all containers are started so that /etc/hosts is consistent.\n\t\tfor _, index := range []string{\"1\", \"2\", \"3\"} {\n\t\t\tnerdtest.EnsureContainerStarted(helpers, fmt.Sprintf(\"%s-svc0-%s\", data.Labels().Get(\"projectName\"), index))\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t}\n\n\tfor _, index := range []string{\"1\", \"2\", \"3\"} {\n\t\ttestCase.SubTests = append(testCase.SubTests, &test.Case{\n\t\t\tDescription: index,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// try 5 times to ensure that results are stable\n\t\t\t\tfor range 5 {\n\t\t\t\t\tcmds := []string{\n\t\t\t\t\t\t\"compose\",\n\t\t\t\t\t\t\"-f\",\n\t\t\t\t\t\tdata.Labels().Get(\"YAMLPath\"),\n\t\t\t\t\t\t\"exec\",\n\t\t\t\t\t\t\"-i=false\",\n\t\t\t\t\t\t\"--no-TTY\",\n\t\t\t\t\t\t\"--index\",\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\"svc0\",\n\t\t\t\t\t}\n\n\t\t\t\t\thsts := helpers.Capture(append(cmds, \"cat\", \"/etc/hosts\")...)\n\t\t\t\t\tips := helpers.Capture(append(cmds, \"ip\", \"addr\", \"show\", \"dev\", \"eth0\")...)\n\n\t\t\t\t\tvar (\n\t\t\t\t\t\texpectIP string\n\t\t\t\t\t\trealIP   string\n\t\t\t\t\t)\n\t\t\t\t\tname := fmt.Sprintf(\"%s-svc0-%s\", data.Labels().Get(\"projectName\"), index)\n\t\t\t\t\thost := fmt.Sprintf(\"%s.%s_default\", name, data.Labels().Get(\"projectName\"))\n\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\thost = strings.TrimSpace(helpers.Capture(\"ps\", \"--filter\", \"name=\"+name, \"--format\", \"{{.ID}}\"))\n\t\t\t\t\t}\n\n\t\t\t\t\tlines := strings.Split(hsts, \"\\n\")\n\t\t\t\t\tfor _, line := range lines {\n\t\t\t\t\t\tif !strings.Contains(line, host) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfields := strings.Fields(line)\n\t\t\t\t\t\tif len(fields) == 0 {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\texpectIP = fields[0]\n\t\t\t\t\t}\n\n\t\t\t\t\tvar ip string\n\t\t\t\t\tlines = strings.Split(ips, \"\\n\")\n\t\t\t\t\tfor _, line := range lines {\n\t\t\t\t\t\tif !strings.Contains(line, \"inet \") {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfields := strings.Fields(line)\n\t\t\t\t\t\tif len(fields) <= 1 {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tip = strings.Split(fields[1], \"/\")[0]\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tpip := net.ParseIP(ip)\n\n\t\t\t\t\tassert.Assert(helpers.T(), pip != nil, \"fail to get the real ip address\")\n\t\t\t\t\trealIP = pip.String()\n\n\t\t\t\t\tassert.Equal(helpers.T(), realIP, expectIP)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_images.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/sync/errgroup\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/containerd/v2/pkg/progress\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc imagesCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"images [flags] [SERVICE...]\",\n\t\tShort:         \"List images used by created containers in services\",\n\t\tRunE:          imagesAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().String(\"format\", \"\", \"Format the output. Supported values: [json]\")\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only show numeric image IDs\")\n\treturn cmd\n}\n\nfunc imagesAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif format != \"json\" && format != \"\" {\n\t\treturn fmt.Errorf(\"unsupported format %s, supported formats are: [json]\", format)\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tserviceNames, err := c.ServiceNames(args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif quiet {\n\t\treturn printComposeImageIDs(ctx, containers)\n\t}\n\n\tsn := client.SnapshotService(globalOptions.Snapshotter)\n\n\treturn printComposeImages(ctx, cmd, containers, sn, format)\n}\n\nfunc printComposeImageIDs(ctx context.Context, containers []containerd.Container) error {\n\tids := []string{}\n\tfor _, c := range containers {\n\t\timage, err := c.Image(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmetaImage := image.Metadata()\n\t\tid := metaImage.Target.Digest.String()\n\t\tif !strutil.InStringSlice(ids, id) {\n\t\t\tids = append(ids, id)\n\t\t}\n\t}\n\n\tfor _, id := range ids {\n\t\t// always truncate image ids.\n\t\tfmt.Println(strings.Split(id, \":\")[1][:12])\n\t}\n\treturn nil\n}\n\nfunc printComposeImages(ctx context.Context, cmd *cobra.Command, containers []containerd.Container, sn snapshots.Snapshotter, format string) error {\n\ttype composeImagePrintable struct {\n\t\tContainerName string\n\t\tRepository    string\n\t\tTag           string\n\t\tImageID       string\n\t\tSize          string\n\t}\n\n\timagePrintables := make([]composeImagePrintable, len(containers))\n\teg, ctx := errgroup.WithContext(ctx)\n\tfor i, c := range containers {\n\t\ti, c := i, c\n\t\teg.Go(func() error {\n\t\t\tinfo, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontainerName := info.Labels[labels.Name]\n\n\t\t\timage, err := c.Image(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsize, err := imgutil.UnpackedImageSize(ctx, sn, image)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tmetaImage := image.Metadata()\n\t\t\trepository, tag := imgutil.ParseRepoTag(metaImage.Name)\n\t\t\timageID := metaImage.Target.Digest.String()\n\t\t\tif repository == \"\" {\n\t\t\t\trepository = \"<none>\"\n\t\t\t}\n\t\t\tif tag == \"\" {\n\t\t\t\ttag = \"<none>\"\n\t\t\t}\n\t\t\tif format != \"json\" {\n\t\t\t\timageID = strings.Split(imageID, \":\")[1][:12]\n\t\t\t}\n\n\t\t\t// no race condition since each goroutine accesses different `i`\n\t\t\timagePrintables[i] = composeImagePrintable{\n\t\t\t\tContainerName: containerName,\n\t\t\t\tRepository:    repository,\n\t\t\t\tTag:           tag,\n\t\t\t\tImageID:       imageID,\n\t\t\t\tSize:          progress.Bytes(size).String(),\n\t\t\t}\n\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif err := eg.Wait(); err != nil {\n\t\treturn err\n\t}\n\n\tif format == \"json\" {\n\t\toutJSON, err := formatter.ToJSON(imagePrintables, \"\", \"\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = fmt.Fprint(cmd.OutOrStdout(), outJSON)\n\t\treturn err\n\t}\n\n\tw := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)\n\tfmt.Fprintln(w, \"Container\\tRepository\\tTag\\tImage Id\\tSize\")\n\tfor _, p := range imagePrintables {\n\t\tif _, err := fmt.Fprintf(w, \"%s\\t%s\\t%s\\t%s\\t%s\\n\",\n\t\t\tp.ContainerName,\n\t\t\tp.Repository,\n\t\t\tp.Tag,\n\t\t\tp.ImageID,\n\t\t\tp.Size,\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn w.Flush()\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_images_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeImages(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  wordpress:\n    image: %s\n    container_name: wordpress\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n  db:\n    image: %s\n    container_name: db\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\twordpressImageName, _ := referenceutil.Parse(testutil.WordpressImage)\n\tdbImageName, _ := referenceutil.Parse(testutil.MariaDBImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", data.Temp().Path(\"compose.yaml\"))\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"images db\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"images\", \"db\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\texpect.Contains(dbImageName.Name()),\n\t\t\t\texpect.DoesNotContain(wordpressImageName.Name()),\n\t\t\t)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"images\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"images\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(dbImageName.Name(), wordpressImageName.Name())),\n\t\t},\n\t\t{\n\t\t\tDescription: \"images --format yaml\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"images\", \"--format\", \"yaml\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"images --format json\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"images\", \"--format\", \"json\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\texpect.JSON([]composeContainerPrintable{}, func(printables []composeContainerPrintable, t tig.T) {\n\t\t\t\t\tassert.Equal(t, len(printables), 2)\n\t\t\t\t}),\n\t\t\t\texpect.Contains(`\"ContainerName\":\"wordpress\"`, `\"ContainerName\":\"db\"`),\n\t\t\t)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"images --format json wordpress\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"images\", \"--format\", \"json\", \"wordpress\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\texpect.JSON([]composeContainerPrintable{}, func(printables []composeContainerPrintable, t tig.T) {\n\t\t\t\t\tassert.Equal(t, len(printables), 1)\n\t\t\t\t}),\n\t\t\t\texpect.Contains(`\"ContainerName\":\"wordpress\"`),\n\t\t\t)),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_kill.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc killCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"kill [flags] [SERVICE...]\",\n\t\tShort:         \"Force stop service containers\",\n\t\tRunE:          killAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringP(\"signal\", \"s\", \"SIGKILL\", \"SIGNAL to send to the container.\")\n\treturn cmd\n}\n\nfunc killAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsignal, err := cmd.Flags().GetString(\"signal\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkillOpts := composer.KillOptions{\n\t\tSignal: signal,\n\t}\n\treturn c.Kill(ctx, killOpts, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_kill_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeKill(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n\n  wordpress:\n    image: %s\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n\n  db:\n    image: %s\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\t\tcomposePath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\twordpressContainerName := serviceparser.DefaultContainerName(projectName, \"wordpress\", \"1\")\n\t\tdbContainerName := serviceparser.DefaultContainerName(projectName, \"db\", \"1\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"wordpressContainer\", wordpressContainerName)\n\t\tdata.Labels().Set(\"dbContainer\", dbContainerName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, wordpressContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, dbContainerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"kill db container and exit with 137\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"kill\", \"db\")\n\t\t\t\tnerdtest.EnsureContainerExited(helpers, data.Labels().Get(\"dbContainer\"), expect.ExitCodeSigkill)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"db\", \"-a\")\n\t\t\t},\n\t\t\t// Docker Compose v1: \"Exit 137\", v2: \"exited (137)\"\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Match(regexp.MustCompile(` 137|\\(137\\)`))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"wordpress container is still running\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"wordpress\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Match(regexp.MustCompile(\"Up|running\"))),\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_logs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc logsCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"logs [flags] [SERVICE...]\",\n\t\tShort:         \"Show logs of running containers\",\n\t\tRunE:          logsAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"follow\", \"f\", false, \"Follow log output.\")\n\tcmd.Flags().BoolP(\"timestamps\", \"t\", false, \"Show timestamps\")\n\tcmd.Flags().String(\"tail\", \"all\", \"Number of lines to show from the end of the logs\")\n\tcmd.Flags().Bool(\"no-color\", false, \"Produce monochrome output\")\n\tcmd.Flags().Bool(\"no-log-prefix\", false, \"Don't print prefix in logs\")\n\treturn cmd\n}\n\nfunc logsAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfollow, err := cmd.Flags().GetBool(\"follow\")\n\tif err != nil {\n\t\treturn err\n\t}\n\ttimestamps, err := cmd.Flags().GetBool(\"timestamps\")\n\tif err != nil {\n\t\treturn err\n\t}\n\ttail, err := cmd.Flags().GetString(\"tail\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoColor, err := cmd.Flags().GetBool(\"no-color\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoLogPrefix, err := cmd.Flags().GetBool(\"no-log-prefix\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlo := composer.LogsOptions{\n\t\tFollow:      follow,\n\t\tTimestamps:  timestamps,\n\t\tTail:        tail,\n\t\tNoColor:     noColor,\n\t\tNoLogPrefix: noLogPrefix,\n\t}\n\treturn c.Logs(ctx, lo, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_pause.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n)\n\nfunc pauseCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:                   \"pause [SERVICE...]\",\n\t\tShort:                 \"Pause all processes within containers of service(s). They can be unpaused with nerdctl compose unpause\",\n\t\tRunE:                  pauseAction,\n\t\tSilenceUsage:          true,\n\t\tSilenceErrors:         true,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\treturn cmd\n}\n\nfunc pauseAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn c.Pause(ctx, args, cmd.OutOrStdout())\n}\n\nfunc unpauseCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:                   \"unpause [SERVICE...]\",\n\t\tShort:                 \"Unpause all processes within containers of service(s).\",\n\t\tRunE:                  unpauseAction,\n\t\tSilenceUsage:          true,\n\t\tSilenceErrors:         true,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\treturn cmd\n}\n\nfunc unpauseAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn c.Unpause(ctx, args, cmd.OutOrStdout())\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_pause_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposePauseAndUnpause(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.CGroup\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n  svc1:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tsvc0Container := serviceparser.DefaultContainerName(projectName, \"svc0\", \"1\")\n\t\tsvc1Container := serviceparser.DefaultContainerName(projectName, \"svc1\", \"1\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, svc0Container)\n\t\tnerdtest.EnsureContainerStarted(helpers, svc1Container)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// pause a service should (only) pause its own container\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"pause\", \"svc0\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tsvc0Paused := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"svc0\", \"-a\")\n\t\t\t\texpect.Match(regexp.MustCompile(\"Paused|paused\"))(svc0Paused, t)\n\n\t\t\t\tsvc1Running := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"svc1\")\n\t\t\t\texpect.Match(regexp.MustCompile(\"Up|running\"))(svc1Running, t)\n\n\t\t\t\t// unpause should be able to recover the paused service container\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"unpause\", \"svc0\")\n\t\t\t\tsvc0Running := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\", \"svc0\")\n\t\t\t\texpect.Match(regexp.MustCompile(\"Up|running\"))(svc0Running, t)\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_port.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc portCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"port [flags] SERVICE PRIVATE_PORT\",\n\t\tShort:         \"Print the public port for a port binding\",\n\t\tArgs:          cobra.ExactArgs(2),\n\t\tRunE:          portAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().Int(\"index\", 1, \"index of the container if the service has multiple instances.\")\n\tcmd.Flags().String(\"protocol\", \"tcp\", \"protocol of the port (tcp|udp)\")\n\n\treturn cmd\n}\n\nfunc portAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tindex, err := cmd.Flags().GetInt(\"index\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif index < 1 {\n\t\treturn fmt.Errorf(\"index starts from 1 and should be equal or greater than 1, given index: %d\", index)\n\t}\n\n\tprotocol, err := cmd.Flags().GetString(\"protocol\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch protocol {\n\tcase \"tcp\", \"udp\":\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported protocol: %s (only tcp and udp are supported)\", protocol)\n\t}\n\n\tport, err := strconv.Atoi(args[1])\n\tif err != nil {\n\t\treturn err\n\t}\n\tif port <= 0 {\n\t\treturn fmt.Errorf(\"unexpected port: %d\", port)\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpo := composer.PortOptions{\n\t\tServiceName: args[0],\n\t\tIndex:       index,\n\t\tPort:        port,\n\t\tProtocol:    protocol,\n\t\tDataStore:   dataStore,\n\t\tNamespace:   globalOptions.Namespace,\n\t}\n\n\treturn c.Port(ctx, cmd.OutOrStdout(), po)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_port_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\nfunc TestComposePort(t *testing.T) {\n\tconst portCount = 2\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tfor i := 0; i < portCount; i++ {\n\t\t\tport, err := portlock.Acquire(0)\n\t\t\tif err != nil {\n\t\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\t\thelpers.T().FailNow()\n\t\t\t}\n\t\t\tdata.Labels().Set(fmt.Sprintf(\"hostPort%d\", i), strconv.Itoa(port))\n\t\t}\n\n\t\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n    ports:\n    - \"%s:10000\"\n    - \"%s:10001/udp\"\n`, testutil.CommonImage, data.Labels().Get(\"hostPort0\"), data.Labels().Get(\"hostPort1\"))\n\n\t\tcompYamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", compYamlPath)\n\t\tprojectName := filepath.Base(filepath.Dir(compYamlPath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", compYamlPath, \"up\", \"-d\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t\tfor i := 0; i < portCount; i++ {\n\t\t\tport, _ := strconv.Atoi(data.Labels().Get(fmt.Sprintf(\"hostPort%d\", i)))\n\t\t\t_ = portlock.Release(port)\n\t\t}\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"port should return host port for TCP\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"port\", \"svc0\", \"10000\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput:   expect.Equals(fmt.Sprintf(\"0.0.0.0:%s\\n\", data.Labels().Get(\"hostPort0\"))),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"port should return host port for UDP\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"port\", \"--protocol\", \"udp\", \"svc0\", \"10001\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput:   expect.Equals(fmt.Sprintf(\"0.0.0.0:%s\\n\", data.Labels().Get(\"hostPort1\"))),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposePortFailure(t *testing.T) {\n\tconst portCount = 2\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tfor i := 0; i < portCount; i++ {\n\t\t\tport, err := portlock.Acquire(0)\n\t\t\tif err != nil {\n\t\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\t\thelpers.T().FailNow()\n\t\t\t}\n\t\t\tdata.Labels().Set(fmt.Sprintf(\"hostPort%d\", i), strconv.Itoa(port))\n\t\t}\n\n\t\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n    ports:\n    - \"%s:10000\"\n    - \"%s:10001/udp\"\n`, testutil.CommonImage, data.Labels().Get(\"hostPort0\"), data.Labels().Get(\"hostPort1\"))\n\n\t\tcompYamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", compYamlPath)\n\t\tprojectName := filepath.Base(filepath.Dir(compYamlPath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", compYamlPath, \"up\", \"-d\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t\tfor i := 0; i < portCount; i++ {\n\t\t\tport, _ := strconv.Atoi(data.Labels().Get(fmt.Sprintf(\"hostPort%d\", i)))\n\t\t\t_ = portlock.Release(port)\n\t\t}\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"port should fail for non-existent port\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"port\", \"svc0\", \"9999\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"port should fail for wrong protocol (UDP on TCP port)\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"port\", \"--protocol\", \"udp\", \"svc0\", \"10000\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"port should fail for wrong protocol (TCP on UDP port)\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"port\", \"--protocol\", \"tcp\", \"svc0\", \"10001\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestComposeMultiplePorts tests whether it is possible to allocate a large\n// number of ports. (https://github.com/containerd/nerdctl/issues/4027)\nfunc TestComposeMultiplePorts(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.NoParallel = true\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n    ports:\n    - '32000-32060:32000-32060'\n`, testutil.AlpineImage)\n\n\t\tcompYamlPath := data.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\tdata.Labels().Set(\"composeYaml\", compYamlPath)\n\t\tprojectName := filepath.Base(filepath.Dir(compYamlPath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", compYamlPath, \"up\", \"-d\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Issue #4027 - Allocate a large number of ports.\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYaml\"), \"port\", \"svc0\", \"32000\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"0.0.0.0:32000\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_ps.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/sync/errgroup\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/runtime/restart\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n)\n\nfunc psCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"ps [flags] [SERVICE...]\",\n\t\tShort:         \"List containers of services\",\n\t\tRunE:          psAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().String(\"format\", \"table\", \"Format the output. Supported values: [table|json]\")\n\tcmd.Flags().String(\"filter\", \"\", \"Filter matches containers based on given conditions\")\n\tcmd.Flags().StringArray(\"status\", []string{}, \"Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]\")\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only display container IDs\")\n\tcmd.Flags().Bool(\"services\", false, \"Display services\")\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Show all containers (default shows just running)\")\n\treturn cmd\n}\n\ntype composeContainerPrintable struct {\n\tID       string\n\tName     string\n\tImage    string\n\tCommand  string\n\tProject  string\n\tService  string\n\tState    string\n\tHealth   string // placeholder, lack containerd support.\n\tExitCode uint32\n\t// `Publishers` stores docker-compatible ports and used for json output.\n\t// `Ports` stores formatted ports and only used for console output.\n\tPublishers []PortPublisher\n\tPorts      string `json:\"-\"`\n}\n\nfunc psAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif format != \"json\" && format != \"table\" {\n\t\treturn fmt.Errorf(\"unsupported format %s, supported formats are: [table|json]\", format)\n\t}\n\tstatus, err := cmd.Flags().GetStringArray(\"status\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdisplayServices, err := cmd.Flags().GetBool(\"services\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilter, err := cmd.Flags().GetString(\"filter\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif filter != \"\" {\n\t\tsplited := strings.SplitN(filter, \"=\", 2)\n\t\tif len(splited) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid argument \\\"%s\\\" for \\\"-f, --filter\\\": bad format of filter (expected name=value)\", filter)\n\t\t}\n\t\t// currently only the 'status' filter is supported\n\t\tif splited[0] != \"status\" {\n\t\t\treturn fmt.Errorf(\"invalid filter '%s'\", splited[0])\n\t\t}\n\t\tstatus = append(status, splited[1])\n\t}\n\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tserviceNames, err := c.ServiceNames(args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !all {\n\t\tvar upContainers []containerd.Container\n\t\tfor _, container := range containers {\n\t\t\t// cStatus := formatter.ContainerStatus(ctx, c)\n\t\t\tcStatus, err := containerutil.ContainerStatus(ctx, container)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif cStatus.Status == containerd.Running {\n\t\t\t\tupContainers = append(upContainers, container)\n\t\t\t}\n\t\t}\n\t\tcontainers = upContainers\n\t}\n\n\tif len(status) != 0 {\n\t\tvar filterdContainers []containerd.Container\n\t\tfor _, container := range containers {\n\t\t\tcStatus := statusForFilter(ctx, container)\n\t\t\tfor _, s := range status {\n\t\t\t\tif cStatus == s {\n\t\t\t\t\tfilterdContainers = append(filterdContainers, container)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcontainers = filterdContainers\n\t}\n\n\tif quiet {\n\t\tfor _, c := range containers {\n\t\t\tfmt.Fprintln(cmd.OutOrStdout(), c.ID())\n\t\t}\n\t\treturn nil\n\t}\n\n\tcontainersPrintable := make([]composeContainerPrintable, len(containers))\n\teg, ctx := errgroup.WithContext(ctx)\n\tfor i, container := range containers {\n\t\ti, container := i, container\n\t\teg.Go(func() error {\n\t\t\tvar p composeContainerPrintable\n\t\t\tvar err error\n\t\t\tif format == \"json\" {\n\t\t\t\tp, err = composeContainerPrintableJSON(ctx, container, globalOptions)\n\t\t\t} else {\n\t\t\t\tp, err = composeContainerPrintableTab(ctx, container, globalOptions)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontainersPrintable[i] = p\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif err := eg.Wait(); err != nil {\n\t\treturn err\n\t}\n\n\tif displayServices {\n\t\tfor _, p := range containersPrintable {\n\t\t\tfmt.Fprintln(cmd.OutOrStdout(), p.Service)\n\t\t}\n\t\treturn nil\n\t}\n\tif format == \"json\" {\n\t\toutJSON, err := formatter.ToJSON(containersPrintable, \"\", \"\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = fmt.Fprint(cmd.OutOrStdout(), outJSON)\n\t\treturn err\n\t}\n\n\tw := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)\n\tfmt.Fprintln(w, \"NAME\\tIMAGE\\tCOMMAND\\tSERVICE\\tSTATUS\\tPORTS\")\n\tfor _, p := range containersPrintable {\n\t\tif _, err := fmt.Fprintf(w, \"%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n\",\n\t\t\tp.Name,\n\t\t\tp.Image,\n\t\t\tp.Command,\n\t\t\tp.Service,\n\t\t\tp.State,\n\t\t\tp.Ports,\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn w.Flush()\n}\n\n// composeContainerPrintableTab constructs composeContainerPrintable with fields\n// only for console output.\nfunc composeContainerPrintableTab(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {\n\tinfo, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tstatus := formatter.ContainerStatus(ctx, container)\n\tif status == \"Up\" {\n\t\tstatus = \"running\" // corresponds to Docker Compose v2.0.1\n\t}\n\timage, err := container.Image(ctx)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tdataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tcontainerLabels, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tports, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID, containerLabels)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\n\treturn composeContainerPrintable{\n\t\tName:    info.Labels[labels.Name],\n\t\tImage:   image.Metadata().Name,\n\t\tCommand: formatter.InspectContainerCommandTrunc(spec),\n\t\tService: info.Labels[labels.ComposeService],\n\t\tState:   status,\n\t\tPorts:   formatter.FormatPorts(ports),\n\t}, nil\n}\n\n// composeContainerPrintableJSON constructs composeContainerPrintable with fields\n// only for json output and compatible docker output.\nfunc composeContainerPrintableJSON(ctx context.Context, container containerd.Container, gOptions types.GlobalCommandOptions) (composeContainerPrintable, error) {\n\tinfo, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\n\tvar (\n\t\tstate    string\n\t\texitCode uint32\n\t)\n\tstatus, err := containerutil.ContainerStatus(ctx, container)\n\tif err == nil {\n\t\t// show exitCode only when container is exited/stopped\n\t\tif status.Status == containerd.Stopped {\n\t\t\tstate = \"exited\"\n\t\t\texitCode = status.ExitStatus\n\t\t} else {\n\t\t\tstate = string(status.Status)\n\t\t}\n\t} else {\n\t\tstate = string(containerd.Unknown)\n\t}\n\timage, err := container.Image(ctx)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tdataStore, err := clientutil.DataStore(gOptions.DataRoot, gOptions.Address)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tcontainerLabels, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\tportMappings, err := portutil.LoadPortMappings(dataStore, gOptions.Namespace, info.ID, containerLabels)\n\tif err != nil {\n\t\treturn composeContainerPrintable{}, err\n\t}\n\n\treturn composeContainerPrintable{\n\t\tID:         container.ID(),\n\t\tName:       info.Labels[labels.Name],\n\t\tImage:      image.Metadata().Name,\n\t\tCommand:    formatter.InspectContainerCommand(spec, false, false),\n\t\tProject:    info.Labels[labels.ComposeProject],\n\t\tService:    info.Labels[labels.ComposeService],\n\t\tState:      state,\n\t\tHealth:     \"\",\n\t\tExitCode:   exitCode,\n\t\tPublishers: formatPublishers(portMappings),\n\t}, nil\n}\n\n// PortPublisher hold status about published port\n// Use this to match the json output with docker compose\n// FYI: https://github.com/docker/compose/blob/v2.13.0/pkg/api/api.go#L305C27-L311\ntype PortPublisher struct {\n\tURL           string\n\tTargetPort    int\n\tPublishedPort int\n\tProtocol      string\n}\n\n// formatPublishers parses and returns docker-compatible []PortPublisher from\n// label map. If an error happens, an empty slice is returned.\nfunc formatPublishers(portMappings []cni.PortMapping) []PortPublisher {\n\tmapper := func(pm cni.PortMapping) PortPublisher {\n\t\treturn PortPublisher{\n\t\t\tURL:           pm.HostIP,\n\t\t\tTargetPort:    int(pm.ContainerPort),\n\t\t\tPublishedPort: int(pm.HostPort),\n\t\t\tProtocol:      pm.Protocol,\n\t\t}\n\t}\n\n\tvar dockerPorts []PortPublisher\n\tfor _, p := range portMappings {\n\t\tdockerPorts = append(dockerPorts, mapper(p))\n\t}\n\treturn dockerPorts\n}\n\n// statusForFilter returns the status value to be matched with the 'status' filter\nfunc statusForFilter(ctx context.Context, c containerd.Container) string {\n\ttask, err := c.Task(ctx, nil)\n\tif err != nil {\n\t\t// NOTE: NotFound doesn't mean that container hasn't started.\n\t\t// In docker/CRI-containerd plugin, the task will be deleted\n\t\t// when it exits. So, the status will be \"created\" for this\n\t\t// case.\n\t\tif errdefs.IsNotFound(err) {\n\t\t\treturn string(containerd.Created)\n\t\t}\n\t\treturn string(containerd.Unknown)\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn string(containerd.Unknown)\n\t}\n\tlabels, err := c.Labels(ctx)\n\tif err != nil {\n\t\treturn string(containerd.Unknown)\n\t}\n\n\tswitch s := status.Status; s {\n\tcase containerd.Stopped:\n\t\tif labels[restart.StatusLabel] == string(containerd.Running) && restart.Reconcile(status, labels) {\n\t\t\treturn \"restarting\"\n\t\t}\n\t\treturn \"exited\"\n\tdefault:\n\t\treturn string(s)\n\t}\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_ps_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/tabutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestComposePs(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  wordpress:\n    image: %s\n    container_name: wordpress_container\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n  db:\n    image: %s\n    container_name: db_container\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n  alpine:\n    image: %s\n    container_name: alpine_container\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage, testutil.CommonImage)\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"-d\").AssertOK()\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tassertHandler := func(expectedName, expectedImage string) func(stdout string) error {\n\t\treturn func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) < 2 {\n\t\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t\t}\n\n\t\t\ttab := tabutil.NewReader(\"NAME\\tIMAGE\\tCOMMAND\\tSERVICE\\tSTATUS\\tPORTS\")\n\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t\t}\n\n\t\t\tcontainer, _ := tab.ReadRow(lines[1], \"NAME\")\n\t\t\tassert.Equal(t, container, expectedName)\n\n\t\t\timage, _ := tab.ReadRow(lines[1], \"IMAGE\")\n\t\t\tassert.Equal(t, image, expectedImage)\n\n\t\t\treturn nil\n\t\t}\n\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"wordpress\").AssertOutWithFunc(assertHandler(\"wordpress_container\", testutil.WordpressImage))\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"db\").AssertOutWithFunc(assertHandler(\"db_container\", testutil.MariaDBImage))\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\").AssertOutNotContains(testutil.CommonImage)\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"alpine\", \"-a\").AssertOutWithFunc(assertHandler(\"alpine_container\", testutil.CommonImage))\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"-a\", \"--filter\", \"status=exited\").AssertOutWithFunc(assertHandler(\"alpine_container\", testutil.CommonImage))\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"--services\", \"-a\").AssertOutContainsAll(\"wordpress\\n\", \"db\\n\", \"alpine\\n\")\n}\n\nfunc TestComposePsJSON(t *testing.T) {\n\t// docker parses unknown 'format' as a Go template and won't output an error\n\ttestutil.DockerIncompatible(t)\n\n\tbase := testutil.NewBase(t)\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  wordpress:\n    image: %s\n    ports:\n      - 8080:80\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n  db:\n    image: %s\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"-d\").AssertOK()\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tassertHandler := func(svc string, count int, fields ...string) func(stdout string) error {\n\t\treturn func(stdout string) error {\n\t\t\t// 1. check json output can be unmarshalled back to printables.\n\t\t\tvar printables []composeContainerPrintable\n\t\t\tif err := json.Unmarshal([]byte(stdout), &printables); err != nil {\n\t\t\t\treturn fmt.Errorf(\"[service: %s]failed to unmarshal json output from `compose ps`: %s\", svc, stdout)\n\t\t\t}\n\t\t\t// 2. check #printables matches expected count.\n\t\t\tif len(printables) != count {\n\t\t\t\treturn fmt.Errorf(\"[service: %s]unmarshal generates %d printables, expected %d: %s\", svc, len(printables), count, stdout)\n\t\t\t}\n\t\t\t// 3. check marshalled json string has all expected substrings.\n\t\t\tfor _, field := range fields {\n\t\t\t\tif !strings.Contains(stdout, field) {\n\t\t\t\t\treturn fmt.Errorf(\"[service: %s]marshalled json output doesn't have expected string (%s): %s\", svc, field, stdout)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// check other formats are not supported\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"--format\", \"yaml\").AssertFail()\n\t// check all services are up (can be marshalled and unmarshalled) and check Image field exists\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"--format\", \"json\").\n\t\tAssertOutWithFunc(assertHandler(\"all\", 2, `\"Service\":\"wordpress\"`, `\"Service\":\"db\"`,\n\t\t\tfmt.Sprintf(`\"Image\":\"%s\"`, testutil.WordpressImage), fmt.Sprintf(`\"Image\":\"%s\"`, testutil.MariaDBImage)))\n\t// check wordpress is running\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"--format\", \"json\", \"wordpress\").\n\t\tAssertOutWithFunc(assertHandler(\"wordpress\", 1, `\"Service\":\"wordpress\"`, `\"State\":\"running\"`, `\"TargetPort\":80`, `\"PublishedPort\":8080`))\n\t// check wordpress is stopped\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"stop\", \"wordpress\").AssertOK()\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"--format\", \"json\", \"wordpress\", \"-a\").\n\t\tAssertOutWithFunc(assertHandler(\"wordpress\", 1, `\"Service\":\"wordpress\"`, `\"State\":\"exited\"`))\n\t// check wordpress is removed\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"rm\", \"-f\", \"wordpress\").AssertOK()\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"--format\", \"json\", \"wordpress\").\n\t\tAssertOutWithFunc(assertHandler(\"wordpress\", 0))\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_pull.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc pullCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"pull [flags] [SERVICE...]\",\n\t\tShort:         \"Pull service images\",\n\t\tRunE:          pullAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Pull without printing progress information\")\n\treturn cmd\n}\n\nfunc pullAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tpo := composer.PullOptions{\n\t\tQuiet: quiet,\n\t}\n\treturn c.Pull(ctx, po, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_pull_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestComposePullWithService(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n\n  wordpress:\n    image: %s\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n\n  db:\n    image: %s\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"pull\", \"db\").AssertOutNotContains(\"wordpress\")\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc pushCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"push [flags] [SERVICE...]\",\n\t\tShort:         \"Push service images\",\n\t\tRunE:          pushAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\treturn cmd\n}\n\nfunc pushAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpo := composer.PushOptions{}\n\treturn c.Push(ctx, po, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_restart.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc restartCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"restart [flags] [SERVICE...]\",\n\t\tShort:         \"Restart containers of given (or all) services\",\n\t\tRunE:          restartAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().UintP(\"timeout\", \"t\", 10, \"Seconds to wait before restarting them\")\n\treturn cmd\n}\n\nfunc restartAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar opt composer.RestartOptions\n\n\tif cmd.Flags().Changed(\"timeout\") {\n\t\ttimeValue, err := cmd.Flags().GetUint(\"timeout\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topt.Timeout = &timeValue\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.Restart(ctx, opt, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_restart_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestComposeRestart(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  wordpress:\n    image: %s\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n  db:\n    image: %s\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"-d\").AssertOK()\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\t// stop and restart a single service.\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"stop\", \"db\").AssertOK()\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"db\", \"-a\").AssertOutContainsAny(\"Exit\", \"exited\")\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"restart\", \"db\").AssertOK()\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"db\").AssertOutContainsAny(\"Up\", \"running\")\n\n\t// stop one service and restart all (also check `--timeout` arg).\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"stop\", \"db\").AssertOK()\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"db\", \"-a\").AssertOutContainsAny(\"Exit\", \"exited\")\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"restart\", \"--timeout\", \"5\").AssertOK()\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"db\").AssertOutContainsAny(\"Up\", \"running\")\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"ps\", \"wordpress\").AssertOutContainsAny(\"Up\", \"running\")\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_rm.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc removeCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"rm [flags] [SERVICE...]\",\n\t\tShort:         \"Remove stopped service containers\",\n\t\tRunE:          removeAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\tcmd.Flags().BoolP(\"stop\", \"s\", false, \"Stop containers before removing\")\n\tcmd.Flags().BoolP(\"volumes\", \"v\", false, \"Remove anonymous volumes associated with containers\")\n\treturn cmd\n}\n\nfunc removeAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !force {\n\t\tservices := \"all\"\n\t\tif len(args) != 0 {\n\t\t\tservices = strings.Join(args, \",\")\n\t\t}\n\n\t\tmsg := fmt.Sprintf(\"This will remove all stopped containers from services: %s.\", services)\n\n\t\tif confirmed, err := helpers.Confirm(cmd, fmt.Sprintf(\"WARNING! %s.\", msg)); err != nil || !confirmed {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tstop, err := cmd.Flags().GetBool(\"stop\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tvolumes, err := cmd.Flags().GetBool(\"volumes\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trmOpts := composer.RemoveOptions{\n\t\tStop:    stop,\n\t\tVolumes: volumes,\n\t}\n\treturn c.Remove(ctx, rmOpts, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_rm_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeRemove(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n\n  wordpress:\n    image: %s\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n\n  db:\n    image: %s\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t\tdata.Labels().Set(\"yamlPath\", data.Temp().Path(\"compose.yaml\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"All services are still up\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"rm\", \"-f\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\twp := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"wordpress\")\n\t\t\t\t\t\tdb := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"db\")\n\t\t\t\t\t\tcomp := expect.Match(regexp.MustCompile(\"Up|running\"))\n\t\t\t\t\t\tcomp(wp, t)\n\t\t\t\t\t\tcomp(db, t)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove stopped service\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"stop\", \"wordpress\")\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"rm\", \"-f\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\twp := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"wordpress\")\n\t\t\t\t\t\tdb := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"db\")\n\t\t\t\t\t\texpect.DoesNotContain(\"wordpress\")(wp, t)\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(\"Up|running\"))(db, t)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove all services with stop\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"rm\", \"-f\", \"-s\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tdb := helpers.Capture(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"db\")\n\t\t\t\t\t\texpect.DoesNotContain(\"db\")(db, t)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_run.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc runCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:                   \"run [flags] SERVICE [COMMAND] [ARGS...]\",\n\t\tShort:                 \"Run a one-off command on a service\",\n\t\tArgs:                  cobra.MinimumNArgs(1),\n\t\tRunE:                  runAction,\n\t\tSilenceUsage:          true,\n\t\tSilenceErrors:         true,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\tcmd.Flags().SetInterspersed(false)\n\tcmd.Flags().BoolP(\"detach\", \"d\", false, \"Detached mode: Run containers in the background\")\n\tcmd.Flags().Bool(\"no-build\", false, \"Don't build an image, even if it's missing.\")\n\tcmd.Flags().Bool(\"no-color\", false, \"Produce monochrome output\")\n\tcmd.Flags().Bool(\"no-log-prefix\", false, \"Don't print prefix in logs\")\n\tcmd.Flags().Bool(\"build\", false, \"Build images before starting containers.\")\n\tcmd.Flags().Bool(\"quiet-pull\", false, \"Pull without printing progress information\")\n\tcmd.Flags().Bool(\"remove-orphans\", false, \"Remove containers for services not defined in the Compose file.\")\n\n\tcmd.Flags().String(\"name\", \"\", \"Assign a name to the container\")\n\tcmd.Flags().Bool(\"no-deps\", false, \"Don't start dependencies\")\n\t// TODO: no-TTY flag\n\t//       In docker-compose's documentation, no-TTY is automatically detected\n\t//       But, it follows `-i` flag because currently `run` command needs `-it` simultaneously.\n\tcmd.Flags().BoolP(\"interactive\", \"i\", true, \"Keep STDIN open even if not attached\")\n\tcmd.Flags().Bool(\"rm\", false, \"Automatically remove the container when it exits\")\n\tcmd.Flags().StringP(\"user\", \"u\", \"\", \"Username or UID (format: <name|uid>[:<group|gid>])\")\n\tcmd.Flags().StringArrayP(\"volume\", \"v\", nil, \"Bind mount a volume\")\n\tcmd.Flags().StringArray(\"entrypoint\", nil, \"Overwrite the default ENTRYPOINT of the image\")\n\tcmd.Flags().StringArrayP(\"env\", \"e\", nil, \"Set environment variables\")\n\tcmd.Flags().StringArrayP(\"label\", \"l\", nil, \"Set metadata on container\")\n\tcmd.Flags().StringP(\"workdir\", \"w\", \"\", \"Working directory inside the container\")\n\t// FIXME: `-p` conflicts with the `--project-name` in PersistentFlags of parent command `compose`\n\t//        For docker compatibility, it should be fixed.\n\tcmd.Flags().StringSlice(\"publish\", nil, \"Publish a container's port(s) to the host\")\n\tcmd.Flags().Bool(\"service-ports\", false, \"Run command with the service's ports enabled and mapped to the host\")\n\t// TODO: use-aliases\n\n\treturn cmd\n}\n\nfunc runAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdetach, err := cmd.Flags().GetBool(\"detach\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoBuild, err := cmd.Flags().GetBool(\"no-build\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoColor, err := cmd.Flags().GetBool(\"no-color\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoLogPrefix, err := cmd.Flags().GetBool(\"no-log-prefix\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuild, err := cmd.Flags().GetBool(\"build\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif build && noBuild {\n\t\treturn errors.New(\"--build and --no-build can not be combined\")\n\t}\n\tquietPull, err := cmd.Flags().GetBool(\"quiet-pull\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tremoveOrphans, err := cmd.Flags().GetBool(\"remove-orphans\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tname, err := cmd.Flags().GetString(\"name\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnodeps, err := cmd.Flags().GetBool(\"no-deps\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinteractive, err := cmd.Flags().GetBool(\"interactive\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t// FIXME : https://github.com/containerd/nerdctl/blob/v0.22.2/cmd/nerdctl/run.go#L100\n\ttty := interactive\n\trm, err := cmd.Flags().GetBool(\"rm\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tuser, err := cmd.Flags().GetString(\"user\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tvolume, err := cmd.Flags().GetStringArray(\"volume\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tentrypoint, err := cmd.Flags().GetStringArray(\"entrypoint\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tenv, err := cmd.Flags().GetStringArray(\"env\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tlabel, err := cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tworkdir, err := cmd.Flags().GetString(\"workdir\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tpublish, err := cmd.Flags().GetStringSlice(\"publish\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tservicePorts, err := cmd.Flags().GetBool(\"service-ports\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif servicePorts && publish != nil && len(publish) > 0 {\n\t\treturn fmt.Errorf(\"--service-ports and --publish(-p) cannot exist simultaneously\")\n\t}\n\t// https://github.com/containerd/nerdctl/blob/v0.22.2/cmd/nerdctl/run.go#L475\n\tif interactive && detach {\n\t\treturn errors.New(\"currently flag -i and -d cannot be specified together (FIXME)\")\n\t}\n\n\t// https://github.com/containerd/nerdctl/blob/v0.22.2/cmd/nerdctl/run.go#L479\n\tif tty && detach {\n\t\treturn errors.New(\"currently flag -t and -d cannot be specified together (FIXME)\")\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tro := composer.RunOptions{\n\t\tDetach:        detach,\n\t\tNoBuild:       noBuild,\n\t\tNoColor:       noColor,\n\t\tNoLogPrefix:   noLogPrefix,\n\t\tForceBuild:    build,\n\t\tQuietPull:     quietPull,\n\t\tRemoveOrphans: removeOrphans,\n\n\t\tServiceName: args[0],\n\t\tArgs:        args[1:],\n\n\t\tName:         name,\n\t\tNoDeps:       nodeps,\n\t\tTty:          tty,\n\t\tInteractive:  interactive,\n\t\tRm:           rm,\n\t\tUser:         user,\n\t\tVolume:       volume,\n\t\tEntrypoint:   entrypoint,\n\t\tEnv:          env,\n\t\tLabel:        label,\n\t\tWorkDir:      workdir,\n\t\tServicePorts: servicePorts,\n\t\tPublish:      publish,\n\t}\n\n\treturn c.Run(ctx, ro)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_run_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry\"\n)\n\nfunc TestComposeRun(t *testing.T) {\n\tconst expectedOutput = \"speed 38400 baud\"\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n      - stty\n`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"pty run\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Temp().Path(\"compose.yaml\"),\n\t\t\t\t\t\"run\",\n\t\t\t\t\t\"--name\",\n\t\t\t\t\tdata.Identifier(),\n\t\t\t\t\t\"alpine\",\n\t\t\t\t)\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(expectedOutput)),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", \"-v\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"pty run with --rm\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\n\t\t\t\t\t\"compose\",\n\t\t\t\t\t\"-f\",\n\t\t\t\t\tdata.Temp().Path(\"compose.yaml\"),\n\t\t\t\t\t\"run\",\n\t\t\t\t\t\"--name\",\n\t\t\t\t\tdata.Identifier(),\n\t\t\t\t\t\"--rm\",\n\t\t\t\t\t\"alpine\",\n\t\t\t\t)\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t// Ensure the container has been removed\n\t\t\t\tcapt := helpers.Capture(\"ps\", \"-a\", \"--format=\\\"{{.Names}}\\\"\")\n\t\t\t\tassert.Assert(t, !strings.Contains(capt, data.Identifier()), capt)\n\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(expectedOutput),\n\t\t\t\t}\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", \"-v\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\", \"-v\")\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeRunWithServicePorts(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\t// specify the name of container in order to remove\n\t// TODO: when `compose rm` is implemented, replace it.\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  web:\n    image: %s\n    ports:\n      - 8080:80\n`, testutil.NginxAlpineImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\tgo func() {\n\t\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t\t// unbuffer(1) can be installed with `apt-get install expect`.\n\t\tunbuffer := []string{\"unbuffer\"}\n\t\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\t\"run\", \"--service-ports\", \"--name\", containerName, \"web\").Run()\n\t}()\n\n\tcheckNginx := func() error {\n\t\tresp, err := nettestutil.HTTPGet(\"http://127.0.0.1:8080\", 5, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) {\n\t\t\tt.Logf(\"respBody=%q\", respBody)\n\t\t\treturn fmt.Errorf(\"respBody does not contain %q\", testutil.NginxAlpineIndexHTMLSnippet)\n\t\t}\n\t\treturn nil\n\t}\n\tvar nginxWorking bool\n\tfor i := 0; i < 30; i++ {\n\t\tt.Logf(\"(retry %d)\", i)\n\t\terr := checkNginx()\n\t\tif err == nil {\n\t\t\tnginxWorking = true\n\t\t\tbreak\n\t\t}\n\t\tt.Log(err)\n\t\ttime.Sleep(3 * time.Second)\n\t}\n\tif !nginxWorking {\n\t\tt.Fatal(\"nginx is not working\")\n\t}\n\tt.Log(\"nginx seems functional\")\n}\n\nfunc TestComposeRunWithPublish(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\t// specify the name of container in order to remove\n\t// TODO: when `compose rm` is implemented, replace it.\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  web:\n    image: %s\n`, testutil.NginxAlpineImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\tgo func() {\n\t\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t\t// unbuffer(1) can be installed with `apt-get install expect`.\n\t\tunbuffer := []string{\"unbuffer\"}\n\t\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\t\"run\", \"--publish\", \"8080:80\", \"--name\", containerName, \"web\").Run()\n\t}()\n\n\tcheckNginx := func() error {\n\t\tresp, err := nettestutil.HTTPGet(\"http://127.0.0.1:8080\", 5, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) {\n\t\t\tt.Logf(\"respBody=%q\", respBody)\n\t\t\treturn fmt.Errorf(\"respBody does not contain %q\", testutil.NginxAlpineIndexHTMLSnippet)\n\t\t}\n\t\treturn nil\n\t}\n\tvar nginxWorking bool\n\tfor i := 0; i < 30; i++ {\n\t\tt.Logf(\"(retry %d)\", i)\n\t\terr := checkNginx()\n\t\tif err == nil {\n\t\t\tnginxWorking = true\n\t\t\tbreak\n\t\t}\n\t\tt.Log(err)\n\t\ttime.Sleep(3 * time.Second)\n\t}\n\tif !nginxWorking {\n\t\tt.Fatal(\"nginx is not working\")\n\t}\n\tt.Log(\"nginx seems functional\")\n}\n\nfunc TestComposeRunWithEnv(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\t// specify the name of container in order to remove\n\t// TODO: when `compose rm` is implemented, replace it.\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n      - sh\n      - -c\n      - \"echo $$FOO\"\n`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\tconst partialOutput = \"bar\"\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\"run\", \"-e\", \"FOO=bar\", \"--name\", containerName, \"alpine\").AssertOutContains(partialOutput)\n}\n\nfunc TestComposeRunWithUser(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\t// specify the name of container in order to remove\n\t// TODO: when `compose rm` is implemented, replace it.\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n      - id\n      - -u\n`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\tconst partialOutput = \"5000\"\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\"run\", \"--user\", \"5000\", \"--name\", containerName, \"alpine\").AssertOutContains(partialOutput)\n}\n\nfunc TestComposeRunWithLabel(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n      - echo\n      - \"dummy log\"\n    labels:\n      - \"foo=bar\"\n`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\"run\", \"--label\", \"foo=rab\", \"--label\", \"x=y\", \"--name\", containerName, \"alpine\").AssertOK()\n\n\tcontainer := base.InspectContainer(containerName)\n\tif container.Config == nil {\n\t\tlog.L.Errorf(\"test failed, cannot fetch container config\")\n\t\tt.Fail()\n\t}\n\tassert.Equal(t, container.Config.Labels[\"foo\"], \"rab\")\n\tassert.Equal(t, container.Config.Labels[\"x\"], \"y\")\n}\n\nfunc TestComposeRunWithArgs(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n      - echo\n`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\tconst partialOutput = \"hello world\"\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\"run\", \"--name\", containerName, \"alpine\", partialOutput).AssertOutContains(partialOutput)\n}\n\nfunc TestComposeRunWithEntrypoint(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\t// specify the name of container in order to remove\n\t// TODO: when `compose rm` is implemented, replace it.\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n      - stty # should be changed\n`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\tconst partialOutput = \"hello world\"\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\"run\", \"--entrypoint\", \"echo\", \"--name\", containerName, \"alpine\", partialOutput).AssertOutContains(partialOutput)\n}\n\nfunc TestComposeRunWithVolume(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  alpine:\n    image: %s\n    entrypoint:\n    - stty # no meaning, just put any command\n`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\t// The directory is automatically removed by Cleanup\n\ttmpDir := t.TempDir()\n\tdestinationDir := \"/data\"\n\tvolumeFlagStr := fmt.Sprintf(\"%s:%s\", tmpDir, destinationDir)\n\n\tdefer base.Cmd(\"rm\", \"-f\", \"-v\", containerName).Run()\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(),\n\t\t\"run\", \"--volume\", volumeFlagStr, \"--name\", containerName, \"alpine\").AssertOK()\n\n\tcontainer := base.InspectContainer(containerName)\n\terrMsg := fmt.Sprintf(\"test failed, cannot find volume: %v\", container.Mounts)\n\tassert.Assert(t, container.Mounts != nil, errMsg)\n\tassert.Assert(t, len(container.Mounts) == 1, errMsg)\n\tassert.Assert(t, container.Mounts[0].Source == tmpDir, errMsg)\n\tassert.Assert(t, container.Mounts[0].Destination == destinationDir, errMsg)\n}\n\nfunc TestComposePushAndPullWithCosignVerify(t *testing.T) {\n\ttestutil.RequireExecutable(t, \"cosign\")\n\ttestutil.DockerIncompatible(t)\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\tt.Parallel()\n\n\tbase := testutil.NewBase(t)\n\tbase.Env = append(base.Env, \"COSIGN_PASSWORD=1\")\n\n\tkeyPair := helpers.NewCosignKeyPair(t, \"cosign-key-pair\", \"1\")\n\treg := testregistry.NewWithNoAuth(base, 0, false)\n\tt.Cleanup(func() {\n\t\tkeyPair.Cleanup()\n\t\treg.Cleanup(nil)\n\t})\n\n\ttID := testutil.Identifier(t)\n\ttestImageRefPrefix := fmt.Sprintf(\"127.0.0.1:%d/%s/\", reg.Port, tID)\n\n\tvar (\n\t\timageSvc0 = testImageRefPrefix + \"composebuild_svc0\"\n\t\timageSvc1 = testImageRefPrefix + \"composebuild_svc1\"\n\t\timageSvc2 = testImageRefPrefix + \"composebuild_svc2\"\n\t)\n\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  svc0:\n    build: .\n    image: %s\n    x-nerdctl-verify: cosign\n    x-nerdctl-cosign-public-key: %s\n    x-nerdctl-sign: cosign\n    x-nerdctl-cosign-private-key: %s\n    entrypoint:\n      - stty\n  svc1:\n    build: .\n    image: %s\n    x-nerdctl-verify: cosign\n    x-nerdctl-cosign-public-key: dummy_pub_key\n    x-nerdctl-sign: cosign\n    x-nerdctl-cosign-private-key: %s\n    entrypoint:\n      - stty\n  svc2:\n    build: .\n    image: %s\n    x-nerdctl-verify: none\n    x-nerdctl-sign: none\n    entrypoint:\n      - stty\n`, imageSvc0, keyPair.PublicKey, keyPair.PrivateKey,\n\t\timageSvc1, keyPair.PrivateKey, imageSvc2)\n\n\tdockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\tcomp.WriteFile(\"Dockerfile\", dockerfile)\n\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\t// 1. build both services/images\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"build\").AssertOK()\n\t// 2. compose push with cosign for svc0/svc1, (and none for svc2)\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"push\").AssertOK()\n\t// 3. compose pull with cosign\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"pull\", \"svc0\").AssertOK()   // key match\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"pull\", \"svc1\").AssertFail() // key mismatch\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"pull\", \"svc2\").AssertOK()   // verify passed\n\t// 4. compose run\n\tconst sttyPartialOutput = \"speed 38400 baud\"\n\t// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.\n\t// unbuffer(1) can be installed with `apt-get install expect`.\n\tunbuffer := []string{\"unbuffer\"}\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(), \"run\", \"svc0\").AssertOutContains(sttyPartialOutput) // key match\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(), \"run\", \"svc1\").AssertFail()                         // key mismatch\n\tbase.ComposeCmdWithHelper(unbuffer, \"-f\", comp.YAMLFullPath(), \"run\", \"svc2\").AssertOutContains(sttyPartialOutput) // verify passed\n\t// 5. compose up\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"svc0\").AssertOK()   // key match\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"svc1\").AssertFail() // key mismatch\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"svc2\").AssertOK()   // verify passed\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_start.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/sync/errgroup\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc startCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:                   \"start [SERVICE...]\",\n\t\tShort:                 \"Start existing containers for service(s)\",\n\t\tRunE:                  startAction,\n\t\tSilenceUsage:          true,\n\t\tSilenceErrors:         true,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\treturn cmd\n}\n\nfunc startAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnerdctlCmd, nerdctlArgs := helpers.GlobalFlags(cmd)\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// TODO(djdongjin): move to `pkg/composer` and rewrite `c.Services + for-loop`\n\t// with `c.project.WithServices` after refactor (#1639) is done.\n\tservices, err := c.Services(ctx, args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, svc := range services {\n\t\tsvcName := svc.Unparsed.Name\n\t\tcontainers, err := c.Containers(ctx, svcName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// return error if no containers and service replica is not zero\n\t\tif len(containers) == 0 {\n\t\t\tif len(svc.Containers) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"service %q has no container to start\", svcName)\n\t\t}\n\n\t\tif err := startContainers(ctx, client, containers, &globalOptions, nerdctlCmd, nerdctlArgs); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc startContainers(ctx context.Context, client *containerd.Client, containers []containerd.Container, globalOptions *types.GlobalCommandOptions, nerdctlCmd string, nerdctlArgs []string) error {\n\teg, ctx := errgroup.WithContext(ctx)\n\tfor _, c := range containers {\n\t\tc := c\n\t\teg.Go(func() error {\n\t\t\tif cStatus, err := containerutil.ContainerStatus(ctx, c); err != nil {\n\t\t\t\t// NOTE: NotFound doesn't mean that container hasn't started.\n\t\t\t\t// In docker/CRI-containerd plugin, the task will be deleted\n\t\t\t\t// when it exits. So, the status will be \"created\" for this\n\t\t\t\t// case.\n\t\t\t\tif !errdefs.IsNotFound(err) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if cStatus.Status == containerd.Running {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// in compose, always disable attach\n\t\t\tif err := containerutil.Start(ctx, c, false, false, client, \"\", \"\", (*config.Config)(globalOptions), nerdctlCmd, nerdctlArgs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tinfo, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t_, err = fmt.Fprintf(os.Stdout, \"Container %s started\\n\", info.Labels[labels.Name])\n\t\t\treturn err\n\t\t})\n\t}\n\n\treturn eg.Wait()\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_start_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeStart(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n  svc1:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"start\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"stop\", \"--timeout\", \"1\", \"svc0\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"kill\", \"svc1\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"start\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   nil,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tsvc0 := helpers.Capture(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"ps\", \"svc0\")\n\t\t\t\tsvc1 := helpers.Capture(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"ps\", \"svc1\")\n\t\t\t\tcomp := expect.Match(regexp.MustCompile(\"Up|running\"))\n\t\t\t\tcomp(svc0, t)\n\t\t\t\tcomp(svc1, t)\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeStartFailWhenServicePause(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.CGroup\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"pause\", \"svc0\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"start\")\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeGenericFail, nil, nil)\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_stop.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc stopCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"stop [flags] [SERVICE...]\",\n\t\tShort:         \"Stop running containers without removing them.\",\n\t\tRunE:          stopAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().UintP(\"timeout\", \"t\", 10, \"Seconds to wait for stop before killing them\")\n\treturn cmd\n}\n\nfunc stopAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar opt composer.StopOptions\n\n\tif cmd.Flags().Changed(\"timeout\") {\n\t\ttimeValue, err := cmd.Flags().GetUint(\"timeout\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topt.Timeout = &timeValue\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.Stop(ctx, opt, args)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_stop_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeStop(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n\n  wordpress:\n    image: %s\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n\n  db:\n    image: %s\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, testutil.MariaDBImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t\tdata.Labels().Set(\"yamlPath\", data.Temp().Path(\"compose.yaml\"))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"stop db\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"stop\", \"db\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"db\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"Exit|exited\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"wordpress is still running\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"wordpress\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"Up|running\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stop wordpress\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"stop\", \"--timeout\", \"5\", \"wordpress\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"ps\", \"wordpress\", \"-a\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"Exit|exited\"))),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_top.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc topCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:                   \"top [SERVICE...]\",\n\t\tShort:                 \"Display the running processes of service containers\",\n\t\tRunE:                  topAction,\n\t\tSilenceUsage:          true,\n\t\tSilenceErrors:         true,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\treturn cmd\n}\n\nfunc topAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tserviceNames, err := c.ServiceNames(args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tstdout := cmd.OutOrStdout()\n\tfor _, c := range containers {\n\t\tcStatus, err := containerutil.ContainerStatus(ctx, c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif cStatus.Status != containerd.Running {\n\t\t\tcontinue\n\t\t}\n\n\t\tinfo, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintln(stdout, info.Labels[labels.Name])\n\t\t// `compose ps` uses empty ps args\n\t\terr = container.Top(ctx, client, []string{c.ID()}, types.ContainerTopOptions{\n\t\t\tStdout:   cmd.OutOrStdout(),\n\t\t\tGOptions: globalOptions,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintln(stdout)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_top_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeTop(t *testing.T) {\n\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    command: \"sleep infinity\"\n  svc1:\n    image: %s\n`, testutil.CommonImage, testutil.NginxAlpineImage)\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(nerdtest.CgroupsAccessible)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t\thelpers.Ensure(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t\tdata.Labels().Set(\"yamlPath\", data.Temp().Path(\"compose.yaml\"))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"svc0 contains sleep infinity\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"top\", \"svc0\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"sleep infinity\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"svc1 contains sleep nginx\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"yamlPath\"), \"top\", \"svc1\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"nginx\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_up.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/compose\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n)\n\nfunc upCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"up [flags] [SERVICE...]\",\n\t\tShort:         \"Create and start containers\",\n\t\tRunE:          upAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().Bool(\"abort-on-container-exit\", false, \"Stops all containers if any container was stopped. Incompatible with -d.\")\n\tcmd.Flags().BoolP(\"detach\", \"d\", false, \"Detached mode: Run containers in the background. Incompatible with --abort-on-container-exit.\")\n\tcmd.Flags().Bool(\"no-build\", false, \"Don't build an image, even if it's missing.\")\n\tcmd.Flags().Bool(\"no-color\", false, \"Produce monochrome output\")\n\tcmd.Flags().Bool(\"no-log-prefix\", false, \"Don't print prefix in logs\")\n\tcmd.Flags().Bool(\"build\", false, \"Build images before starting containers.\")\n\tcmd.Flags().Bool(\"ipfs\", false, \"Allow pulling base images from IPFS during build\")\n\tcmd.Flags().Bool(\"quiet-pull\", false, \"Pull without printing progress information\")\n\tcmd.Flags().Bool(\"remove-orphans\", false, \"Remove containers for services not defined in the Compose file.\")\n\tcmd.Flags().Bool(\"force-recreate\", false, \"Recreate containers even if their configuration and image haven't changed.\")\n\tcmd.Flags().Bool(\"no-recreate\", false, \"Don't recreate containers if they exist, conflict with --force-recreate.\")\n\tcmd.Flags().StringArray(\"scale\", []string{}, \"Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.\")\n\tcmd.Flags().String(\"pull\", \"\", \"Pull image before running (\\\"always\\\"|\\\"missing\\\"|\\\"never\\\")\")\n\treturn cmd\n}\n\nfunc upAction(cmd *cobra.Command, services []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdetach, err := cmd.Flags().GetBool(\"detach\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tabortOnContainerExit, err := cmd.Flags().GetBool(\"abort-on-container-exit\")\n\tif detach && abortOnContainerExit {\n\t\treturn fmt.Errorf(\"--abort-on-container-exit flag is incompatible with flag --detach\")\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoBuild, err := cmd.Flags().GetBool(\"no-build\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoColor, err := cmd.Flags().GetBool(\"no-color\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoLogPrefix, err := cmd.Flags().GetBool(\"no-log-prefix\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuild, err := cmd.Flags().GetBool(\"build\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif build && noBuild {\n\t\treturn errors.New(\"--build and --no-build can not be combined\")\n\t}\n\tenableIPFS, err := cmd.Flags().GetBool(\"ipfs\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tquietPull, err := cmd.Flags().GetBool(\"quiet-pull\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tpull, err := cmd.Flags().GetString(\"pull\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tremoveOrphans, err := cmd.Flags().GetBool(\"remove-orphans\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tscaleSlice, err := cmd.Flags().GetStringArray(\"scale\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tforceRecreate, err := cmd.Flags().GetBool(\"force-recreate\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoRecreate, err := cmd.Flags().GetBool(\"no-recreate\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif forceRecreate && noRecreate {\n\t\treturn errors.New(\"flag --force-recreate and --no-recreate cannot be specified together\")\n\t}\n\tscale := make(map[string]int)\n\tfor _, s := range scaleSlice {\n\t\tparts := strings.Split(s, \"=\")\n\t\tif len(parts) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid --scale option %q. Should be SERVICE=NUM\", s)\n\t\t}\n\t\treplicas, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tscale[parts[0]] = replicas\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental)\n\tif err != nil {\n\t\treturn err\n\t}\n\toptions.Services = services\n\tc, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tuo := composer.UpOptions{\n\t\tAbortOnContainerExit: abortOnContainerExit,\n\t\tDetach:               detach,\n\t\tNoBuild:              noBuild,\n\t\tNoColor:              noColor,\n\t\tNoLogPrefix:          noLogPrefix,\n\t\tForceBuild:           build,\n\t\tIPFS:                 enableIPFS,\n\t\tQuietPull:            quietPull,\n\t\tRemoveOrphans:        removeOrphans,\n\t\tScale:                scale,\n\t\tPull:                 pull,\n\t\tForceRecreate:        forceRecreate,\n\t\tNoRecreate:           noRecreate,\n\t}\n\treturn c.Up(ctx, uo, services)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_up_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/docker/go-connections/nat\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\nfunc TestComposeUp(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thostPort, err := portlock.Acquire(0)\n\t\tif err != nil {\n\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\thelpers.T().FailNow()\n\t\t}\n\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  wordpress:\n    image: %s\n    restart: always\n    ports:\n      - %d:80\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n  db:\n    image: %s\n    restart: always\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, hostPort, testutil.MariaDBImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\twordpressContainerName := serviceparser.DefaultContainerName(projectName, \"wordpress\", \"1\")\n\t\tdbContainerName := serviceparser.DefaultContainerName(projectName, \"db\", \"1\")\n\n\t\tdata.Labels().Set(\"hostPort\", strconv.Itoa(hostPort))\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"projectName\", projectName)\n\t\tdata.Labels().Set(\"wordpressContainerName\", wordpressContainerName)\n\t\tdata.Labels().Set(\"dbContainerName\", dbContainerName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, wordpressContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, dbContainerName)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(data.Labels().Get(\"wordpressContainerName\")),\n\t\t\t\texpect.Contains(data.Labels().Get(\"dbContainerName\")),\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t\tif p := data.Labels().Get(\"hostPort\"); p != \"\" {\n\t\t\tif port, err := strconv.Atoi(p); err == nil {\n\t\t\t\t_ = portlock.Release(port)\n\t\t\t}\n\t\t}\n\t\tif projectName := data.Labels().Get(\"projectName\"); projectName != \"\" {\n\t\t\thelpers.Command(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName)).Run(&test.Expected{ExitCode: 1})\n\t\t\thelpers.Command(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName)).Run(&test.Expected{ExitCode: 1})\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpBuild(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Build\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thostPort, err := portlock.Acquire(0)\n\t\tif err != nil {\n\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\thelpers.T().FailNow()\n\t\t}\n\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  web:\n    build: .\n    ports:\n    - %d:80\n`, hostPort)\n\t\tdockerfile := fmt.Sprintf(`FROM %s\nCOPY index.html /usr/share/nginx/html/index.html\n`, testutil.NginxAlpineImage)\n\t\tindexHTML := data.Identifier(\"indexHTML\")\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\tdata.Temp().Save(indexHTML, \"index.html\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"hostPort\", strconv.Itoa(hostPort))\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"indexHTML\", data.Temp().Path(\"index.html\"))\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\", \"--build\")\n\t\tnerdtest.EnsureContainerStarted(helpers, serviceparser.DefaultContainerName(projectName, \"web\", \"1\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"HTTP request to the web container\",\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thost := fmt.Sprintf(\"http://127.0.0.1:%s\", data.Labels().Get(\"hostPort\"))\n\t\t\t\t\t\tresp, err := nettestutil.HTTPGet(host, 5, false)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\trespBody, err := io.ReadAll(resp.Body)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tt.Log(fmt.Sprintf(\"respBody=%q\", respBody))\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(string(respBody), data.Labels().Get(\"indexHTML\")))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t\thelpers.Anyhow(\"builder\", \"prune\", \"--all\", \"--force\")\n\t\tif portStr := data.Labels().Get(\"hostPort\"); portStr != \"\" {\n\t\t\tport, _ := strconv.Atoi(portStr)\n\t\t\t_ = portlock.Release(port)\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpNetWithStaticIP(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tstaticIP := \"10.4.255.254\"\n\t\tsubnet := \"10.4.255.0/24\"\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    networks:\n      net0:\n        ipv4_address: %s\n\nnetworks:\n  net0:\n    ipam:\n      config:\n        - subnet: %s\n`, testutil.NginxAlpineImage, staticIP, subnet)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tcontainerName := serviceparser.DefaultContainerName(projectName, \"svc0\", \"1\")\n\n\t\tdata.Labels().Set(\"staticIP\", staticIP)\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"static IP is assigned to container\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Labels().Get(\"containerName\"), \"--format\", \"\\\"{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}\\\"\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, data.Labels().Get(\"staticIP\")))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpMultiNet(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  svc0:\n    image: %s\n    networks:\n      - net0\n      - net1\n      - net2\n  svc1:\n    image: %s\n    networks:\n      - net0\n      - net1\n  svc2:\n    image: %s\n    networks:\n      - net2\n\nnetworks:\n  net0: {}\n  net1: {}\n  net2: {}\n`, testutil.NginxAlpineImage, testutil.NginxAlpineImage, testutil.NginxAlpineImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tsvc0 := serviceparser.DefaultContainerName(projectName, \"svc0\", \"1\")\n\t\tsvc1 := serviceparser.DefaultContainerName(projectName, \"svc1\", \"1\")\n\t\tsvc2 := serviceparser.DefaultContainerName(projectName, \"svc2\", \"1\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"svc0\", svc0)\n\t\tdata.Labels().Set(\"svc1\", svc1)\n\t\tdata.Labels().Set(\"svc2\", svc2)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, svc0)\n\t\tnerdtest.EnsureContainerStarted(helpers, svc1)\n\t\tnerdtest.EnsureContainerStarted(helpers, svc2)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"svc0 can ping itself\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"svc0\"), \"ping\", \"-c\", \"1\", \"svc0\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"svc0 can ping svc1\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"svc0\"), \"ping\", \"-c\", \"1\", \"svc1\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"svc0 can ping svc2\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"svc0\"), \"ping\", \"-c\", \"1\", \"svc2\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"svc1 can ping svc0\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"svc1\"), \"ping\", \"-c\", \"1\", \"svc0\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"svc2 can ping svc0\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"svc2\"), \"ping\", \"-c\", \"1\", \"svc0\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"svc1 cannot ping svc2\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"svc1\"), \"ping\", \"-c\", \"1\", \"svc2\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpOsEnvVar(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Env = map[string]string{\n\t\t\"ADDRESS\": \"0.0.0.0\",\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tconst containerName = \"nginxAlpine\"\n\n\t\thostPort, err := portlock.Acquire(0)\n\t\tif err != nil {\n\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\thelpers.T().FailNow()\n\t\t}\n\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  svc1:\n    image: %s\n    container_name: %s\n    ports:\n      - ${ADDRESS:-127.0.0.1}:%d:80\n`, testutil.NginxAlpineImage, containerName, hostPort)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\tdata.Labels().Set(\"hostPort\", strconv.Itoa(hostPort))\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"container\", \"inspect\", data.Labels().Get(\"containerName\"))\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: expect.JSON([]dockercompat.Container{}, func(dc []dockercompat.Container, t tig.T) {\n\t\t\t\tassert.Equal(t, 1, len(dc), \"unexpected number of containers\")\n\t\t\t\tinspect80TCP := (*dc[0].NetworkSettings.Ports)[\"80/tcp\"]\n\t\t\t\tassert.Assert(t, len(inspect80TCP) > 0, \"no host bindings for 80/tcp\")\n\t\t\t\texpected := nat.PortBinding{\n\t\t\t\t\tHostIP:   \"0.0.0.0\",\n\t\t\t\t\tHostPort: data.Labels().Get(\"hostPort\"),\n\t\t\t\t}\n\t\t\t\tassert.Equal(t, expected, inspect80TCP[0])\n\t\t\t}),\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpDotEnvFile(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = `\nservices:\n  svc3:\n    image: ghcr.io/stargz-containers/nginx:$TAG\n`\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tdata.Temp().Save(`TAG=1.19-alpine-org`, \".env\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\")\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpEnvFileNotFoundError(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = `\nservices:\n  svc4:\n    image: ghcr.io/stargz-containers/nginx:$TAG\n`\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tdata.Temp().Save(`TAG=1.19-alpine-org`, \"envFile\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// env-file is relative to the current working directory and not the project directory\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"--env-file\", \"envFile\", \"up\", \"-d\")\n\t}\n\n\ttestCase.Expected = test.Expects(1, nil, nil)\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpWithScale(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\ttest1 := serviceparser.DefaultContainerName(projectName, \"test\", \"1\")\n\t\ttest2 := serviceparser.DefaultContainerName(projectName, \"test\", \"2\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"test1\", test1)\n\t\tdata.Labels().Set(\"test2\", test2)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\", \"--scale\", \"test=2\")\n\t\tnerdtest.EnsureContainerStarted(helpers, test1)\n\t\tnerdtest.EnsureContainerStarted(helpers, test2)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"ps\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(data.Labels().Get(\"test1\")),\n\t\t\t\texpect.Contains(data.Labels().Get(\"test2\")),\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeIPAMConfig(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  foo:\n    image: %s\n    command: \"sleep infinity\"\n\nnetworks:\n  default:\n    ipam:\n      config:\n        - subnet: 10.1.100.0/24\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tfooContainer := serviceparser.DefaultContainerName(projectName, \"foo\", \"1\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"fooContainer\", fooContainer)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, fooContainer)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"inspect\", \"-f\", \"{{json .NetworkSettings.Networks }}\", data.Labels().Get(\"fooContainer\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(\"10.1.100.\"))\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpRemoveOrphans(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar (\n\t\t\tdockerComposeYAMLOrphan = fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\t\t\tdockerComposeYAMLFull = fmt.Sprintf(`\n%s\n  orphan:\n    image: %s\n    command: \"sleep infinity\"\n`, dockerComposeYAMLOrphan, testutil.CommonImage)\n\t\t)\n\n\t\tcomposeOrphanPath := data.Temp().Save(dockerComposeYAMLOrphan, \"compose-orphan.yaml\")\n\t\tcomposeFullPath := data.Temp().Save(dockerComposeYAMLFull, \"compose-full.yaml\")\n\n\t\tprojectName := data.Identifier(\"project\")\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\ttestContainer := serviceparser.DefaultContainerName(projectName, \"test\", \"1\")\n\t\torphanContainer := serviceparser.DefaultContainerName(projectName, \"orphan\", \"1\")\n\n\t\tdata.Labels().Set(\"composeOrphanPath\", composeOrphanPath)\n\t\tdata.Labels().Set(\"composeFullPath\", composeFullPath)\n\t\tdata.Labels().Set(\"projectName\", projectName)\n\t\tdata.Labels().Set(\"orphanContainer\", orphanContainer)\n\n\t\thelpers.Ensure(\"compose\", \"-p\", projectName, \"-f\", composeFullPath, \"up\", \"-d\")\n\t\thelpers.Ensure(\"compose\", \"-p\", projectName, \"-f\", composeOrphanPath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainer)\n\t\tnerdtest.EnsureContainerStarted(helpers, orphanContainer)\n\n\t\thelpers.Command(\"compose\", \"-p\", projectName, \"-f\", composeFullPath, \"ps\").Run(\n\t\t\t&test.Expected{\n\t\t\t\tExitCode: 0,\n\t\t\t\tOutput:   expect.Contains(orphanContainer),\n\t\t\t},\n\t\t)\n\t\thelpers.Ensure(\"compose\", \"-p\", projectName, \"-f\", composeOrphanPath, \"up\", \"-d\", \"--remove-orphans\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", data.Labels().Get(\"composeFullPath\"), \"ps\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput:   expect.DoesNotContain(data.Labels().Get(\"orphanContainer\")),\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeOrphanPath\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", data.Labels().Get(\"composeOrphanPath\"), \"down\", \"-v\")\n\t\t}\n\t\tif data.Labels().Get(\"composeFullPath\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-p\", data.Labels().Get(\"projectName\"), \"-f\", data.Labels().Get(\"composeFullPath\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpIdempotent(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: \"sleep infinity\"\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, serviceparser.DefaultContainerName(projectName, \"test\", \"1\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\")\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpNoRecreateDependencies(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  foo:\n    image: %s\n    command: \"sleep infinity\"\n  bar:\n    image: %s\n    command: \"sleep infinity\"\n    depends_on:\n      - foo\n`, testutil.CommonImage, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tfooContainer := serviceparser.DefaultContainerName(projectName, \"foo\", \"1\")\n\t\tbarContainer := serviceparser.DefaultContainerName(projectName, \"bar\", \"1\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"projectName\", projectName)\n\t\tdata.Labels().Set(\"fooContainer\", fooContainer)\n\t\tdata.Labels().Set(\"barContainer\", barContainer)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"foo is not recreated when starting bar\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\", \"foo\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"fooContainer\"))\n\n\t\t\t\thelpers.Command(\"inspect\", data.Labels().Get(\"fooContainer\"), \"--format\", \"{{.Id}}\").Run(\n\t\t\t\t\t&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tdata.Labels().Set(\"fooContainerID\", strings.TrimSpace(stdout))\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\t// Bring up dependent service; ensure foo is not recreated (ID unchanged)\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\", \"bar\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"barContainer\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Labels().Get(\"fooContainer\"), \"--format\", \"{{.Id}}\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Equal(t, strings.TrimSpace(stdout), data.Labels().Get(\"fooContainerID\"))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpWithExternalNetwork(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar dockerComposeYaml1 = fmt.Sprintf(`\nservices:\n  %s:\n    image: %s\n    container_name: %s\n    networks:\n      %s:\n        aliases:\n          - nginx-1\nnetworks:\n  %s:\n    external: true\n`, data.Identifier(\"con-1\"), testutil.NginxAlpineImage, data.Identifier(\"con-1\"), data.Identifier(\"network\"), data.Identifier(\"network\"))\n\t\tvar dockerComposeYaml2 = fmt.Sprintf(`\nservices:\n  %s:\n    image: %s\n    container_name: %s\n    networks:\n      %s:\n        aliases:\n          - nginx-2\nnetworks:\n  %s:\n    external: true\n`, data.Identifier(\"con-2\"), testutil.NginxAlpineImage, data.Identifier(\"con-2\"), data.Identifier(\"network\"), data.Identifier(\"network\"))\n\t\ttmp := data.Temp()\n\n\t\ttmp.Save(dockerComposeYaml1, \"project-1\", \"compose.yaml\")\n\t\ttmp.Save(dockerComposeYaml2, \"project-2\", \"compose.yaml\")\n\n\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"network\"))\n\t\thelpers.Ensure(\"compose\", \"-f\", tmp.Path(\"project-1\", \"compose.yaml\"), \"up\", \"-d\")\n\t\thelpers.Ensure(\"compose\", \"-f\", tmp.Path(\"project-2\", \"compose.yaml\"), \"up\", \"-d\")\n\t\thelpers.Ensure(\"compose\", \"-f\", tmp.Path(\"project-2\", \"compose.yaml\"), \"down\", \"-v\")\n\t\thelpers.Ensure(\"compose\", \"-f\", tmp.Path(\"project-2\", \"compose.yaml\"), \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"con-2\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\thelpers.Ensure(\"exec\", data.Identifier(\"con-1\"), \"cat\", \"/etc/hosts\")\n\t\treturn helpers.Command(\"exec\", data.Identifier(\"con-1\"), \"wget\", \"-qO-\", \"http://\"+data.Identifier(\"con-2\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(testutil.NginxAlpineIndexHTMLSnippet))\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"project-1\", \"compose.yaml\"), \"down\", \"-v\")\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"project-2\", \"compose.yaml\"), \"down\", \"-v\")\n\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier(\"network\"))\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpWithBypass4netns(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.Rootless,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\ttestutil.RequireKernelVersion(t, \">= 5.9.0-0\")\n\t\ttestutil.RequireSystemService(t, \"bypass4netnsd\")\n\n\t\thostPort, err := portlock.Acquire(0)\n\t\tif err != nil {\n\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\thelpers.T().FailNow()\n\t\t}\n\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  wordpress:\n    image: %s\n    restart: always\n    ports:\n      - %d:80\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n    annotations:\n      - nerdctl/bypass4netns=1\n  db:\n    image: %s\n    restart: always\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n    annotations:\n      - nerdctl/bypass4netns=1\n\nvolumes:\n  wordpress:\n  db:\n`, testutil.WordpressImage, hostPort, testutil.MariaDBImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"hostPort\", strconv.Itoa(hostPort))\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"projectName\", projectName)\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composePath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, serviceparser.DefaultContainerName(projectName, \"wordpress\", \"1\"))\n\t\tnerdtest.EnsureContainerStarted(helpers, serviceparser.DefaultContainerName(projectName, \"db\", \"1\"))\n\n\t\thelpers.Command(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName)).Run(&test.Expected{ExitCode: 0})\n\t\thelpers.Command(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName)).Run(&test.Expected{ExitCode: 0})\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tOutput: func(_ string, tt tig.T) {\n\t\t\t\thost := fmt.Sprintf(\"http://127.0.0.1:%s\", data.Labels().Get(\"hostPort\"))\n\t\t\t\tresp, err := nettestutil.HTTPGet(host, 5, false)\n\t\t\t\tassert.NilError(tt, err)\n\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\tassert.NilError(tt, err)\n\t\t\t\t_ = resp.Body.Close()\n\t\t\t\tassert.Assert(tt, strings.Contains(string(body), testutil.WordpressIndexHTMLSnippet))\n\t\t\t\tt.Log(\"wordpress seems functional\")\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"composeYAML\") != \"\" {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t}\n\t\tif p := data.Labels().Get(\"hostPort\"); p != \"\" {\n\t\t\tif port, err := strconv.Atoi(p); err == nil {\n\t\t\t\t_ = portlock.Release(port)\n\t\t\t}\n\t\t}\n\n\t\tif projectName := data.Labels().Get(\"projectName\"); projectName != \"\" {\n\t\t\thelpers.Command(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName)).Run(&test.Expected{ExitCode: 1})\n\t\t\thelpers.Command(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName)).Run(&test.Expected{ExitCode: 1})\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpProfile(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tserviceRegular := data.Identifier(\"regular\")\n\t\tserviceProfiled := data.Identifier(\"profiled\")\n\n\t\tenvFilePath := data.Temp().Save(`TEST_ENV_INJECTION=WORKS\\n`, \"env.common\")\n\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  %s:\n    image: %[3]s\n\n  %[2]s:\n    image: %[3]s\n    profiles:\n      - test-profile\n    env_file:\n      - %[4]s\n`, serviceRegular, serviceProfiled, testutil.NginxAlpineImage, envFilePath)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"serviceRegular\", serviceRegular)\n\t\tdata.Labels().Set(\"serviceProfiled\", serviceProfiled)\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"regularContainer\", serviceparser.DefaultContainerName(projectName, serviceRegular, \"1\"))\n\t\tdata.Labels().Set(\"profiledContainer\", serviceparser.DefaultContainerName(projectName, serviceProfiled, \"1\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with profile\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"--profile\", \"test-profile\", \"up\", \"-d\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"regularContainer\"))\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"profiledContainer\"))\n\n\t\t\t\thelpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"exec\", data.Labels().Get(\"serviceProfiled\"), \"env\").\n\t\t\t\t\tRun(&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput:   expect.Contains(\"TEST_ENV_INJECTION=WORKS\"),\n\t\t\t\t\t})\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\", \"--format={{.Names}}\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"serviceRegular\")),\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"serviceProfiled\")),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"--profile\", \"test-profile\", \"down\", \"-v\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"profiled not started without profile flag\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"regularContainer\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\", \"--format={{.Names}}\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"serviceRegular\")),\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"serviceProfiled\")),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpAbortOnContainerExit(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tserviceRegular := data.Identifier(\"regular\")\n\t\tserviceProfiled := data.Identifier(\"exited\")\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  %s:\n    image: %s\n  %s:\n    image: %s\n    entrypoint: /bin/sh -c \"exit 1\"\n`, serviceRegular, testutil.NginxAlpineImage, serviceProfiled, testutil.BusyboxImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\t\tprojectName := filepath.Base(filepath.Dir(composePath))\n\t\tt.Logf(\"projectName=%q\", projectName)\n\n\t\tdata.Labels().Set(\"serviceRegular\", serviceRegular)\n\t\tdata.Labels().Set(\"serviceProfiled\", serviceProfiled)\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t\tdata.Labels().Set(\"regularContainer\", serviceparser.DefaultContainerName(projectName, serviceRegular, \"1\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"abort on container exit\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"--abort-on-container-exit\").Run(\n\t\t\t\t\t&test.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\", \"--format={{.Names}}\", \"--filter\", \"status=exited\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"serviceRegular\")),\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"serviceProfiled\")),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"no abort flag keeps other services running\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"regularContainer\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\", \"--format={{.Names}}\", \"--filter\", \"status=exited\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"serviceRegular\")),\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"serviceProfiled\")),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\", \"-v\")\n\t\t\t},\n\t\t},\n\t\t// in this sub-test we are ensuring that flags '-d' and '--abort-on-container-exit' cannot be ran together\n\t\t{\n\t\t\tDescription: \"flag -d incompatible with --abort-on-container-exit\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"-d\", \"--abort-on-container-exit\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpPull(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.NoParallel = true\n\ttestCase.Require = nerdtest.Private\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: sh -euxc \"echo hi\"\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"pull=missing\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Command(\"images\").Run(\n\t\t\t\t\t&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput:   expect.DoesNotContain(testutil.CommonImage),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"--pull\", \"missing\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"hi\")),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"pull=always\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Command(\"images\").Run(\n\t\t\t\t\t&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput:   expect.DoesNotContain(testutil.CommonImage),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"--pull\", \"always\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"hi\")),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"pull=never, no pull\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Command(\"images\").Run(\n\t\t\t\t\t&test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput:   expect.DoesNotContain(testutil.CommonImage),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\", \"--pull\", \"never\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\")\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeUpServicePullPolicy(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Private\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar composeYAML = fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: sh -euxc \"echo hi\"\n    pull_policy: \"never\"\n`, testutil.CommonImage)\n\n\t\tcomposePath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\tdata.Labels().Set(\"composeYAML\", composePath)\n\n\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\thelpers.Command(\"images\").Run(\n\t\t\t&test.Expected{\n\t\t\t\tExitCode: 0,\n\t\t\t\tOutput:   expect.DoesNotContain(testutil.CommonImage),\n\t\t\t},\n\t\t)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"up\")\n\t}\n\n\ttestCase.Expected = test.Expects(1, nil, nil)\n\n\ttestCase.Run(t)\n}\n\nfunc TestComposeTmpfsVolume(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Identifier(\"tmpfs\")\n\t\tcomposeYAML := fmt.Sprintf(`\nservices:\n  tmpfs:\n    container_name: %s\n    image: %s\n    command: sleep infinity\n    volumes:\n      - type: tmpfs\n        target: /target-rw\n        tmpfs:\n          size: 64m\n      - type: tmpfs\n        target: /target-ro\n        read_only: true\n        tmpfs:\n          size: 64m\n          mode: 0o1770\n`, containerName, testutil.CommonImage)\n\n\t\tcomposeYAMLPath := data.Temp().Save(composeYAML, \"compose.yaml\")\n\n\t\thelpers.Ensure(\"compose\", \"-f\", composeYAMLPath, \"up\", \"-d\")\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\n\t\tdata.Labels().Set(\"composeYAML\", composeYAMLPath)\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"rw tmpfs mount\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"containerName\"), \"grep\", \"/target-rw\", \"/proc/mounts\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil,\n\t\t\t\texpect.All(\n\t\t\t\t\texpect.Contains(\"/target-rw\"),\n\t\t\t\t\texpect.Contains(\"rw\"),\n\t\t\t\t\texpect.Contains(\"size=65536k\"),\n\t\t\t\t),\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tDescription: \"ro tmpfs mount with mode\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"containerName\"), \"grep\", \"/target-ro\", \"/proc/mounts\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil,\n\t\t\t\texpect.All(\n\t\t\t\t\texpect.Contains(\"/target-ro\"),\n\t\t\t\t\texpect.Contains(\"ro\"),\n\t\t\t\t\texpect.Contains(\"size=65536k\"),\n\t\t\t\t\texpect.Contains(\"mode=1770\"),\n\t\t\t\t),\n\t\t\t),\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Labels().Get(\"composeYAML\"), \"down\")\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_up_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// https://github.com/containerd/nerdctl/issues/1942\nfunc TestComposeUpDetailedError(t *testing.T) {\n\tdockerComposeYAML := fmt.Sprintf(`\nservices:\n  foo:\n    image: %s\n    runtime: invalid\n`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\t// \"FIXME: test does not work on Windows yet (runtime \\\"io.containerd.runc.v2\\\" binary not installed \\\"containerd-shim-runc-v2.exe\\\": file does not exist)\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\", \"-d\")\n\t}\n\n\ttestCase.Expected = test.Expects(\n\t\t1,\n\t\t[]error{errors.New(`invalid runtime name`)},\n\t\tnil,\n\t)\n\n\ttestCase.Run(t)\n}\n\n// https://github.com/containerd/nerdctl/issues/1652\nfunc TestComposeUpBindCreateHostPath(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// `FIXME: no support for Windows path: (error: \"volume target must be an absolute path, got \\\"/mnt\\\")`\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar dockerComposeYAML = fmt.Sprintf(`\nservices:\n  test:\n    image: %s\n    command: sh -euxc \"echo hi >/mnt/test\"\n    volumes:\n      # tempdir/foo should be automatically created\n      - %s:/mnt\n`, testutil.CommonImage, data.Temp().Path(\"foo\"))\n\n\t\tdata.Temp().Save(dockerComposeYAML, \"compose.yaml\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"up\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"compose\", \"-f\", data.Temp().Path(\"compose.yaml\"), \"down\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   nil,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Equal(t, data.Temp().Load(\"foo\", \"test\"), \"hi\\n\")\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_version.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/version\"\n)\n\nfunc versionCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"version\",\n\t\tShort:         \"Show the Compose version information\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          versionAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringP(\"format\", \"f\", \"pretty\", \"Format the output. Values: [pretty | json]\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\", \"pretty\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().Bool(\"short\", false, \"Shows only Compose's version number\")\n\treturn cmd\n}\n\nfunc versionAction(cmd *cobra.Command, args []string) error {\n\tshort, err := cmd.Flags().GetBool(\"short\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif short {\n\t\tfmt.Fprintln(cmd.OutOrStdout(), strings.TrimPrefix(version.GetVersion(), \"v\"))\n\t\treturn nil\n\t}\n\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch format {\n\tcase \"pretty\":\n\t\tfmt.Fprintln(cmd.OutOrStdout(), \"nerdctl Compose version \"+version.GetVersion())\n\tcase \"json\":\n\t\tfmt.Fprintf(cmd.OutOrStdout(), \"{\\\"version\\\":\\\"%v\\\"}\\n\", version.Version)\n\tdefault:\n\t\treturn fmt.Errorf(\"format can be either pretty or json, not %v\", format)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/compose/compose_version_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestComposeVersion(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Command = test.Command(\"compose\", \"version\")\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(\"Compose version \"))\n\ttestCase.Run(t)\n}\n\nfunc TestComposeVersionShort(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Command = test.Command(\"compose\", \"version\", \"--short\")\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\ttestCase.Run(t)\n}\n\nfunc TestComposeVersionJson(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Command = test.Command(\"compose\", \"version\", \"--format\", \"json\")\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(\"{\\\"version\\\":\\\"\"))\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"container\",\n\t\tShort:         \"Manage containers\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tCreateCommand(),\n\t\tRunCommand(),\n\t\tUpdateCommand(),\n\t\tExecCommand(),\n\t\tlistCommand(),\n\t\tinspectCommand(),\n\t\tLogsCommand(),\n\t\tPortCommand(),\n\t\tRemoveCommand(),\n\t\tStopCommand(),\n\t\tStartCommand(),\n\t\tRestartCommand(),\n\t\tKillCommand(),\n\t\tPauseCommand(),\n\t\tDiffCommand(),\n\t\tWaitCommand(),\n\t\tUnpauseCommand(),\n\t\tCommitCommand(),\n\t\tRenameCommand(),\n\t\tpruneCommand(),\n\t\tStatsCommand(),\n\t\tAttachCommand(),\n\t\tHealthCheckCommand(),\n\t\tExportCommand(),\n\t)\n\tAddCpCommand(cmd)\n\treturn cmd\n}\n\nfunc listCommand() *cobra.Command {\n\tx := PsCommand()\n\tx.Use = \"ls\"\n\tx.Aliases = []string{\"list\"}\n\treturn x\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_attach.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"io\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n)\n\nfunc AttachCommand() *cobra.Command {\n\tconst shortHelp = \"Attach stdin, stdout, and stderr to a running container.\"\n\tconst longHelp = `Attach stdin, stdout, and stderr to a running container. For example:\n\n1. 'nerdctl run -it --name test busybox' to start a container with a pty\n2. 'ctrl-p ctrl-q' to detach from the container\n3. 'nerdctl attach test' to attach to the container\n\nCaveats:\n\n- Currently only one attach session is allowed. When the second session tries to attach, currently no error will be returned from nerdctl.\n  However, since behind the scenes, there's only one FIFO for stdin, stdout, and stderr respectively,\n  if there are multiple sessions, all the sessions will be reading from and writing to the same 3 FIFOs, which will result in mixed input and partial output.\n- Until dual logging (issue #1946) is implemented,\n  a container that is spun up by either 'nerdctl run -d' or 'nerdctl start' (without '--attach') cannot be attached to.`\n\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"attach [flags] CONTAINER\",\n\t\tArgs:              cobra.ExactArgs(1),\n\t\tShort:             shortHelp,\n\t\tLong:              longHelp,\n\t\tRunE:              attachAction,\n\t\tValidArgsFunction: attachShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"detach-keys\", consoleutil.DefaultDetachKeys, \"Override the default detach keys\")\n\tcmd.Flags().Bool(\"no-stdin\", false, \"Do not attach STDIN\")\n\treturn cmd\n}\n\nfunc attachOptions(cmd *cobra.Command) (types.ContainerAttachOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerAttachOptions{}, err\n\t}\n\tdetachKeys, err := cmd.Flags().GetString(\"detach-keys\")\n\tif err != nil {\n\t\treturn types.ContainerAttachOptions{}, err\n\t}\n\tnoStdin, err := cmd.Flags().GetBool(\"no-stdin\")\n\tif err != nil {\n\t\treturn types.ContainerAttachOptions{}, err\n\t}\n\n\tvar stdin io.Reader\n\tif !noStdin {\n\t\tstdin = cmd.InOrStdin()\n\t}\n\treturn types.ContainerAttachOptions{\n\t\tGOptions:   globalOptions,\n\t\tStdin:      stdin,\n\t\tStdout:     cmd.OutOrStdout(),\n\t\tStderr:     cmd.ErrOrStderr(),\n\t\tDetachKeys: detachKeys,\n\t}, nil\n}\n\nfunc attachAction(cmd *cobra.Command, args []string) error {\n\toptions, err := attachOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Attach(ctx, client, args[0], options)\n}\n\nfunc attachShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st == containerd.Running\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_attach_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n/*\nImportant notes:\n- for both docker and nerdctl, you can run+detach of a container and exit 0, while the container would actually fail starting\n- nerdctl (not docker): on run, detach will race anything on stdin before the detach sequence from reaching the container\n- nerdctl AND docker: on attach ^\n- exit code variants: https://github.com/containerd/nerdctl/issues/3571\n*/\n\nfunc TestAttach(t *testing.T) {\n\t// In nerdctl the detach return code from the container after attach is 0, but in docker the return code is 1.\n\t// This behaviour is reported in https://github.com/containerd/nerdctl/issues/3571\n\tex := 0\n\tif nerdtest.IsDocker() {\n\t\tex = 1\n\t}\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcmd := helpers.Command(\"run\", \"--rm\", \"-it\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\tcmd.WithPseudoTTY()\n\t\t// ctrl+p and ctrl+q (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\tcmd.Feed(bytes.NewReader([]byte{16, 17}))\n\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   []error{errors.New(\"read detach keys\")},\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// Run interactively and detach\n\t\tcmd := helpers.Command(\"attach\", data.Identifier())\n\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"echo mark${NON}mark\\n\"))\n\t\tcmd.WithFeeder(func() io.Reader {\n\t\t\t// Interestingly, and unlike with run, on attach, docker (like nerdctl) ALSO needs a pause so that the\n\t\t\t// container can read stdin before we detach\n\t\t\ttime.Sleep(time.Second)\n\t\t\t// ctrl+p and ctrl+q (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\t\treturn bytes.NewReader([]byte{16, 17})\n\t\t})\n\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: ex,\n\t\t\tErrors:   []error{errors.New(\"read detach keys\")},\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(\"markmark\"),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestAttachDetachKeys(t *testing.T) {\n\t// In nerdctl the detach return code from the container after attach is 0, but in docker the return code is 1.\n\t// This behaviour is reported in https://github.com/containerd/nerdctl/issues/3571\n\tex := 0\n\tif nerdtest.IsDocker() {\n\t\tex = 1\n\t}\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcmd := helpers.Command(\"run\", \"--rm\", \"-it\", \"--detach-keys=ctrl-q\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(bytes.NewReader([]byte{17}))\n\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   []error{errors.New(\"read detach keys\")},\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// Run interactively and detach\n\t\tcmd := helpers.Command(\"attach\", \"--detach-keys=ctrl-a,ctrl-b\", data.Identifier())\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"echo mark${NON}mark\\n\"))\n\t\tcmd.WithFeeder(func() io.Reader {\n\t\t\t// Interestingly, and unlike with run, on attach, docker (like nerdctl) ALSO needs a pause so that the\n\t\t\t// container can read stdin before we detach\n\t\t\ttime.Sleep(time.Second)\n\t\t\t// ctrl+p and ctrl+q (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\t\treturn bytes.NewReader([]byte{1, 2})\n\t\t})\n\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: ex,\n\t\t\tErrors:   []error{errors.New(\"read detach keys\")},\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(\"markmark\"),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestIssue3568 tests https://github.com/containerd/nerdctl/issues/3568\nfunc TestAttachForAutoRemovedContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Description = \"Issue #3568 - A container should be deleted when detaching and attaching a container started with the --rm option.\"\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcmd := helpers.Command(\"run\", \"--rm\", \"-it\", \"--detach-keys=ctrl-a,ctrl-b\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\tcmd.WithPseudoTTY()\n\t\t// ctrl+a and ctrl+b (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\tcmd.Feed(bytes.NewReader([]byte{1, 2}))\n\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   []error{errors.New(\"read detach keys\")},\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// Run interactively and detach\n\t\tcmd := helpers.Command(\"attach\", data.Identifier())\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"echo mark${NON}mark\\nexit 42\\n\"))\n\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 42,\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(\"markmark\"),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, !strings.Contains(helpers.Capture(\"ps\", \"-a\"), data.Identifier()))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestAttachNoStdin(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcmd := helpers.Command(\"run\", \"-it\", \"--detach-keys=ctrl-p,ctrl-q\", \"--name\", data.Identifier(),\n\t\t\ttestutil.CommonImage, \"sleep\", \"5\")\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(bytes.NewReader([]byte{16, 17})) // Ctrl-p, Ctrl-q to detach (https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"{{.State.Running}}\", data.Identifier()), \"true\"))\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcmd := helpers.Command(\"attach\", \"--no-stdin\", data.Identifier())\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"should-not-appear\\n\"))\n\t\tcmd.Feed(bytes.NewReader([]byte{16, 17}))\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0, // Since it's a normal exit and not detach.\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tlogs := helpers.Capture(\"logs\", data.Identifier())\n\t\t\t\tassert.Assert(t, !strings.Contains(logs, \"should-not-appear\"))\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_commit.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc CommitCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"commit [flags] CONTAINER REPOSITORY[:TAG]\",\n\t\tShort:             \"Create a new image from a container's changes\",\n\t\tArgs:              helpers.IsExactArgs(2),\n\t\tRunE:              commitAction,\n\t\tValidArgsFunction: commitShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().StringP(\"author\", \"a\", \"\", `Author (e.g., \"nerdctl contributor <nerdctl-dev@example.com>\")`)\n\tcmd.Flags().StringP(\"message\", \"m\", \"\", \"Commit message\")\n\tcmd.Flags().StringArrayP(\"change\", \"c\", nil, \"Apply Dockerfile instruction to the created image (supported directives: [CMD, ENTRYPOINT])\")\n\tcmd.Flags().BoolP(\"pause\", \"p\", true, \"Pause container during commit\")\n\tcmd.Flags().StringP(\"compression\", \"\", \"gzip\", \"commit compression algorithm (zstd or gzip)\")\n\tcmd.Flags().String(\"format\", \"docker\", \"Format of the committed image (docker or oci)\")\n\tcmd.Flags().Bool(\"estargz\", false, \"Convert the committed layer to eStargz for lazy pulling\")\n\tcmd.Flags().Int(\"estargz-compression-level\", 9, \"eStargz compression level (1-9)\")\n\tcmd.Flags().Int(\"estargz-chunk-size\", 0, \"eStargz chunk size\")\n\tcmd.Flags().Int(\"estargz-min-chunk-size\", 0, \"The minimal number of bytes of data must be written in one gzip stream\")\n\tcmd.Flags().Bool(\"zstdchunked\", false, \"Convert the committed layer to zstd:chunked for lazy pulling\")\n\tcmd.Flags().Int(\"zstdchunked-compression-level\", 3, \"zstd:chunked compression level\")\n\tcmd.Flags().Int(\"zstdchunked-chunk-size\", 0, \"zstd:chunked chunk size\")\n\treturn cmd\n}\n\nfunc commitOptions(cmd *cobra.Command) (types.ContainerCommitOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tauthor, err := cmd.Flags().GetString(\"author\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tmessage, err := cmd.Flags().GetString(\"message\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tpause, err := cmd.Flags().GetBool(\"pause\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\n\tchange, err := cmd.Flags().GetStringArray(\"change\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\n\tcom, err := cmd.Flags().GetString(\"compression\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tif com != string(types.Zstd) && com != string(types.Gzip) {\n\t\treturn types.ContainerCommitOptions{}, errors.New(\"--compression param only supports zstd or gzip\")\n\t}\n\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tif format != string(types.ImageFormatDocker) && format != string(types.ImageFormatOCI) {\n\t\treturn types.ContainerCommitOptions{}, errors.New(\"--format param only supports docker or oci\")\n\t}\n\n\testargz, err := cmd.Flags().GetBool(\"estargz\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\testargzCompressionLevel, err := cmd.Flags().GetInt(\"estargz-compression-level\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\testargzChunkSize, err := cmd.Flags().GetInt(\"estargz-chunk-size\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\testargzMinChunkSize, err := cmd.Flags().GetInt(\"estargz-min-chunk-size\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\n\tzstdchunked, err := cmd.Flags().GetBool(\"zstdchunked\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tzstdchunkedCompressionLevel, err := cmd.Flags().GetInt(\"zstdchunked-compression-level\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\tzstdchunkedChunkSize, err := cmd.Flags().GetInt(\"zstdchunked-chunk-size\")\n\tif err != nil {\n\t\treturn types.ContainerCommitOptions{}, err\n\t}\n\n\t// estargz and zstdchunked are mutually exclusive\n\tif estargz && zstdchunked {\n\t\treturn types.ContainerCommitOptions{}, errors.New(\"options --estargz and --zstdchunked lead to conflict, only one of them can be used\")\n\t}\n\n\treturn types.ContainerCommitOptions{\n\t\tStdout:      cmd.OutOrStdout(),\n\t\tGOptions:    globalOptions,\n\t\tAuthor:      author,\n\t\tMessage:     message,\n\t\tPause:       pause,\n\t\tChange:      change,\n\t\tCompression: types.CompressionType(com),\n\t\tFormat:      types.ImageFormat(format),\n\t\tEstargzOptions: types.EstargzOptions{\n\t\t\tEstargz:                 estargz,\n\t\t\tEstargzCompressionLevel: estargzCompressionLevel,\n\t\t\tEstargzChunkSize:        estargzChunkSize,\n\t\t\tEstargzMinChunkSize:     estargzMinChunkSize,\n\t\t},\n\t\tZstdChunkedOptions: types.ZstdChunkedOptions{\n\t\t\tZstdChunked:                 zstdchunked,\n\t\t\tZstdChunkedCompressionLevel: zstdchunkedCompressionLevel,\n\t\t\tZstdChunkedChunkSize:        zstdchunkedChunkSize,\n\t\t},\n\t}, nil\n}\n\nfunc commitAction(cmd *cobra.Command, args []string) error {\n\toptions, err := commitOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Commit(ctx, client, args[1], args[0], options)\n}\n\nfunc commitShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tif len(args) == 0 {\n\t\treturn completion.ContainerNames(cmd, nil)\n\t}\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_commit_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestKubeCommitSave(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.OnlyKubernetes\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tidentifier := data.Identifier()\n\t\tcontainerID := \"\"\n\t\t// NOTE: kubectl namespaces are not the same as containerd namespaces.\n\t\t// We still want kube test objects segregated in their own Kube API namespace.\n\t\tnerdtest.KubeCtlCommand(helpers, \"create\", \"namespace\", \"nerdctl-test-k8s\").Run(&test.Expected{})\n\t\tnerdtest.KubeCtlCommand(helpers, \"run\", \"--image\", testutil.CommonImage, identifier, \"--\", \"sleep\", nerdtest.Infinity).Run(&test.Expected{})\n\t\tnerdtest.KubeCtlCommand(helpers, \"wait\", \"pod\", identifier, \"--for=condition=ready\", \"--timeout=1m\").Run(&test.Expected{})\n\t\tnerdtest.KubeCtlCommand(helpers, \"exec\", identifier, \"--\", \"mkdir\", \"-p\", \"/tmp/whatever\").Run(&test.Expected{})\n\t\tnerdtest.KubeCtlCommand(helpers, \"get\", \"pods\", identifier, \"-o\", \"jsonpath={ .status.containerStatuses[0].containerID }\").Run(&test.Expected{\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tcontainerID = strings.TrimPrefix(stdout, \"containerd://\")\n\t\t\t},\n\t\t})\n\t\tdata.Labels().Set(\"containerID\", containerID)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tnerdtest.KubeCtlCommand(helpers, \"delete\", \"pod\", \"--all\").Run(nil)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\thelpers.Ensure(\"commit\", data.Labels().Get(\"containerID\"), data.Identifier(\"testcommitsave\"))\n\t\t// Wait for the image to show up\n\t\tfor range 5 {\n\t\t\tfound := false\n\t\t\tcmd := helpers.Command(\"images\", data.Identifier(\"testcommitsave\"), \"--format\", \"json\")\n\t\t\tcmd.Run(&test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tfound = strings.TrimSpace(stdout) != \"\"\n\t\t\t\t},\n\t\t\t})\n\t\t\tif found {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t\treturn helpers.Command(\"save\", data.Identifier(\"testcommitsave\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\ttestCase.Run(t)\n\n\t// This below is missing configuration to allow for plain http communication\n\t// This is left here for future work to successfully start a registry usable in the cluster\n\t/*\n\t\t// Start a registry\n\t\t\t\tnerdtest.KubeCtlCommand(helpers, \"run\", \"--port\", \"5000\", \"--image\", testutil.RegistryImageStable, \"testregistry\").\n\t\t\t\t\tRun(&test.Expected{})\n\n\t\t\t\tnerdtest.KubeCtlCommand(helpers, \"wait\", \"pod\", \"testregistry\", \"--for=condition=ready\", \"--timeout=1m\").\n\t\t\t\t\tAssertOK()\n\n\t\t\t\tcmd = nerdtest.KubeCtlCommand(helpers, \"get\", \"pods\", tID, \"-o\", \"jsonpath={ .status.hostIPs[0].ip }\")\n\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tregistryIP = stdout\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\tcmd = nerdtest.KubeCtlCommand(helpers, \"apply\", \"-f\", \"-\", fmt.Sprintf(`apiVersion: v1\n\t\t\tkind: ConfigMap\n\t\t\tmetadata:\n\t\t\t\tname: local-registry\n\t\t\t\tnamespace: nerdctl-test\n\t\t\tdata:\n\t\t\t\tlocalRegistryHosting.v1: |\n\t\t\t\thost: \"%s:5000\"\n\t\t\t\thelp: \"https://kind.sigs.k8s.io/docs/user/local-registry/\"\n\t\t`, registryIP))\n\t*/\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_commit_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestCommit(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with pause\",\n\t\t\tRequire:     nerdtest.CGroup,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", identifier)\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", identifier)\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", identifier, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"exec\", identifier, \"sh\", \"-euxc\", `echo hello-test-commit > /foo`)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\n\t\t\t\t\t\"commit\",\n\t\t\t\t\t\"-c\", `CMD [\"/foo\"]`,\n\t\t\t\t\t\"-c\", `ENTRYPOINT [\"cat\"]`,\n\t\t\t\t\t\"--pause=true\",\n\t\t\t\t\tidentifier, identifier)\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", identifier)\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello-test-commit\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"no pause\",\n\t\t\tRequire:     require.Not(require.Windows),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", identifier)\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", identifier)\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", identifier, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, identifier)\n\t\t\t\thelpers.Ensure(\"exec\", identifier, \"sh\", \"-euxc\", `echo hello-test-commit > /foo`)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\n\t\t\t\t\t\"commit\",\n\t\t\t\t\t\"-c\", `CMD [\"/foo\"]`,\n\t\t\t\t\t\"-c\", `ENTRYPOINT [\"cat\"]`,\n\t\t\t\t\t\"--pause=false\",\n\t\t\t\t\tidentifier, identifier)\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", identifier)\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello-test-commit\\n\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestZstdCommit(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\t// FIXME: Docker  does not support compression\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.ContainerdVersion(\"2.0.0\"),\n\t\tnerdtest.CGroup,\n\t)\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"image\"))\n\t}\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tidentifier := data.Identifier()\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", identifier, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, identifier)\n\t\thelpers.Ensure(\"exec\", identifier, \"sh\", \"-euxc\", `echo hello-test-commit > /foo`)\n\t\thelpers.Ensure(\"commit\", identifier, data.Identifier(\"image\"), \"--compression=zstd\")\n\t\tdata.Labels().Set(\"image\", data.Identifier(\"image\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"verify zstd has been used\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"image\", \"inspect\", \"--mode=native\", data.Labels().Get(\"image\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.JSON([]native.Image{}, func(images []native.Image, t tig.T) {\n\t\t\t\t\t\tassert.Equal(t, len(images), 1)\n\t\t\t\t\t\tassert.Equal(helpers.T(), images[0].Manifest.Layers[len(images[0].Manifest.Layers)-1].MediaType, \"application/vnd.docker.image.rootfs.diff.tar.zstd\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"verify the image is working\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"image\"), \"sh\", \"-c\", \"--\", \"cat /foo\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello-test-commit\\n\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_cp_acid_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// This is a separate set of tests for cp specifically meant to test corner or extreme cases that do not fit in the normal testing rig\n// because of their complexity\n\nfunc TestCopyAcid(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\ttestID := data.Identifier()\n\t\ttempDir := t.TempDir()\n\n\t\tsourceFile := filepath.Join(tempDir, \"hostfile\")\n\t\tsourceFileContent := []byte(testID)\n\n\t\troContainer := testID + \"-ro\"\n\t\trwContainer := testID + \"-rw\"\n\n\t\tdata.Labels().Set(\"sourceFile\", sourceFile)\n\t\tdata.Labels().Set(\"sourceFileContent\", string(sourceFileContent))\n\t\tdata.Labels().Set(\"roContainer\", roContainer)\n\t\tdata.Labels().Set(\"rwContainer\", rwContainer)\n\n\t\thelpers.Ensure(\"volume\", \"create\", testID+\"-1-ro\")\n\t\thelpers.Ensure(\"volume\", \"create\", testID+\"-2-ro\")\n\t\thelpers.Ensure(\"volume\", \"create\", testID+\"-3-ro\")\n\n\t\thelpers.Ensure(\"run\", \"-d\", \"-w\", containerCwd, \"--name\", roContainer, \"--read-only\",\n\t\t\t\"-v\", fmt.Sprintf(\"%s:%s:ro\", testID+\"-1-ro\", \"/vol1/dir1/ro\"),\n\t\t\t\"-v\", fmt.Sprintf(\"%s:%s\", testID+\"-2-rw\", \"/vol2/dir2/rw\"),\n\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity,\n\t\t)\n\t\tnerdtest.EnsureContainerStarted(helpers, roContainer)\n\n\t\thelpers.Ensure(\"run\", \"-d\", \"-w\", containerCwd, \"--name\", rwContainer,\n\t\t\t\"-v\", fmt.Sprintf(\"%s:%s:ro\", testID+\"-1-ro\", \"/vol1/dir1/ro\"),\n\t\t\t\"-v\", fmt.Sprintf(\"%s:%s\", testID+\"-3-rw\", \"/vol3/dir3/rw\"),\n\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity,\n\t\t)\n\t\tnerdtest.EnsureContainerStarted(helpers, rwContainer)\n\n\t\thelpers.Ensure(\"exec\", rwContainer, \"sh\", \"-euxc\", \"cd /vol3/dir3/rw; ln -s ../../../ relativelinktoroot\")\n\t\thelpers.Ensure(\"exec\", rwContainer, \"sh\", \"-euxc\", \"cd /vol3/dir3/rw; ln -s / absolutelinktoroot\")\n\t\thelpers.Ensure(\"exec\", roContainer, \"sh\", \"-euxc\", \"cd /vol2/dir2/rw; ln -s ../../../ relativelinktoroot\")\n\t\thelpers.Ensure(\"exec\", roContainer, \"sh\", \"-euxc\", \"cd /vol2/dir2/rw; ln -s / absolutelinktoroot\")\n\n\t\t// Create file on the host\n\t\terr := os.WriteFile(sourceFile, sourceFileContent, filePerm)\n\t\tassert.NilError(t, err)\n\n\t\texpectedErr := containerutil.ErrTargetIsReadOnly.Error()\n\t\tif nerdtest.IsDocker() {\n\t\t\texpectedErr = \"\"\n\t\t}\n\t\tdata.Labels().Set(\"expectedErr\", expectedErr)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\ttestID := data.Identifier()\n\t\thelpers.Anyhow(\"rm\", \"-f\", testID+\"-ro\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", testID+\"-rw\")\n\t\thelpers.Anyhow(\"volume\", \"rm\", testID+\"-1-ro\")\n\t\thelpers.Anyhow(\"volume\", \"rm\", testID+\"-2-rw\")\n\t\thelpers.Anyhow(\"volume\", \"rm\", testID+\"-3-rw\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Cannot copy into a read-only root\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"roContainer\")+\":/\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Cannot copy into a read-only mount, in a rw container\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"rwContainer\")+\":/vol1/dir1/ro\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Can copy into a read-write mount in a read-only container\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"roContainer\")+\":/vol2/dir2/rw\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Traverse read-only locations to a read-write location\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"roContainer\")+\":/vol1/dir1/ro/../../../vol2/dir2/rw\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Follow an absolute symlink inside a read-write mount to a read-only root\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"roContainer\")+\":/vol2/dir2/rw/absolutelinktoroot\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Follow am absolute symlink inside a read-write mount to a read-only mount\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"rwContainer\")+\":/vol3/dir3/rw/absolutelinktoroot/vol1/dir1/ro\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Follow a relative symlink inside a read-write location to a read-only root\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"roContainer\")+\":/vol2/dir2/rw/relativelinktoroot\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Follow a relative symlink inside a read-write location to a read-only mount\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"sourceFile\"), data.Labels().Get(\"rwContainer\")+\":/vol3/dir3/rw/relativelinktoroot/vol1/dir1/ro\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Cannot copy into a HOST read-only location\",\n\t\t\tRequire:     nerdtest.Rootless,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttempDir := t.TempDir()\n\t\t\t\terr := os.MkdirAll(filepath.Join(tempDir, \"rotest\"), 0o000)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\treturn helpers.Command(\"cp\", data.Labels().Get(\"roContainer\")+\":/etc/issue\", filepath.Join(tempDir, \"rotest\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"expectedErr\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_cp_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc copyCommand() *cobra.Command {\n\tshortHelp := \"Copy files/folders between a running container and the local filesystem.\"\n\n\tlongHelp := shortHelp + `\nThis command requires 'tar' to be installed on the host (not in the container).\nUsing GNU tar is recommended.\nThe path of the 'tar' binary can be specified with an environment variable '$TAR'.\n\nWARNING: 'nerdctl cp' is designed only for use with trusted, cooperating containers.\nUsing 'nerdctl cp' with untrusted or malicious containers is unsupported and may not provide protection against unexpected behavior.\n`\n\n\tusage := `cp [flags] CONTAINER:SRC_PATH DEST_PATH|-\n  nerdctl cp [flags] SRC_PATH|- CONTAINER:DEST_PATH`\n\tvar cmd = &cobra.Command{\n\t\tUse:               usage,\n\t\tArgs:              helpers.IsExactArgs(2),\n\t\tShort:             shortHelp,\n\t\tLong:              longHelp,\n\t\tRunE:              copyAction,\n\t\tValidArgsFunction: copyShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\tcmd.Flags().BoolP(\"follow-link\", \"L\", false, \"Always follow symbolic link in SRC_PATH.\")\n\n\treturn cmd\n}\n\nfunc copyAction(cmd *cobra.Command, args []string) error {\n\toptions, err := copyOptions(cmd, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif rootlessutil.IsRootless() {\n\t\toptions.GOptions.Address, err = rootlessutil.RootlessContainredSockAddress()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Cp(ctx, client, options)\n}\n\nfunc copyOptions(cmd *cobra.Command, args []string) (types.ContainerCpOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerCpOptions{}, err\n\t}\n\tflagL, err := cmd.Flags().GetBool(\"follow-link\")\n\tif err != nil {\n\t\treturn types.ContainerCpOptions{}, err\n\t}\n\n\tsrcSpec, err := parseCpFileSpec(args[0])\n\tif err != nil {\n\t\treturn types.ContainerCpOptions{}, err\n\t}\n\n\tdestSpec, err := parseCpFileSpec(args[1])\n\tif err != nil {\n\t\treturn types.ContainerCpOptions{}, err\n\t}\n\n\tif (srcSpec.Container != nil && destSpec.Container != nil) || (len(srcSpec.Path) == 0 && len(destSpec.Path) == 0) {\n\t\treturn types.ContainerCpOptions{}, fmt.Errorf(\"one of src or dest must be a local file specification\")\n\t}\n\tif srcSpec.Container == nil && destSpec.Container == nil {\n\t\treturn types.ContainerCpOptions{}, fmt.Errorf(\"one of src or dest must be a container file specification\")\n\t}\n\n\tcontainer2host := srcSpec.Container != nil\n\tvar containerReq string\n\tif container2host {\n\t\tcontainerReq = *srcSpec.Container\n\t} else {\n\t\tcontainerReq = *destSpec.Container\n\t}\n\treturn types.ContainerCpOptions{\n\t\tGOptions:       globalOptions,\n\t\tContainer2Host: container2host,\n\t\tContainerReq:   containerReq,\n\t\tDestPath:       destSpec.Path,\n\t\tSrcPath:        srcSpec.Path,\n\t\tFollowSymLink:  flagL,\n\t\tFromStdin:      srcSpec.Path == \"-\",\n\t\tToStdout:       destSpec.Path == \"-\",\n\t}, nil\n}\n\nfunc AddCpCommand(rootCmd *cobra.Command) {\n\trootCmd.AddCommand(copyCommand())\n}\n\nvar errFileSpecDoesntMatchFormat = errors.New(\"filespec must match the canonical format: [container:]file/path\")\n\nfunc parseCpFileSpec(arg string) (*copyFileSpec, error) {\n\tif arg == \"\" {\n\t\treturn &copyFileSpec{\n\t\t\tPath: \"-\",\n\t\t}, nil\n\t}\n\n\ti := strings.Index(arg, \":\")\n\n\t// filespec starting with a semicolon is invalid\n\tif i == 0 {\n\t\treturn nil, errFileSpecDoesntMatchFormat\n\t}\n\n\tif filepath.IsAbs(arg) {\n\t\t// Explicit local absolute path, e.g., `C:\\foo` or `/foo`.\n\t\treturn &copyFileSpec{\n\t\t\tContainer: nil,\n\t\t\tPath:      arg,\n\t\t}, nil\n\t}\n\n\tparts := strings.SplitN(arg, \":\", 2)\n\n\tif len(parts) == 1 || strings.HasPrefix(parts[0], \".\") {\n\t\t// Either there's no `:` in the arg\n\t\t// OR it's an explicit local relative path like `./file:name.txt`.\n\t\treturn &copyFileSpec{\n\t\t\tPath: arg,\n\t\t}, nil\n\t}\n\n\treturn &copyFileSpec{\n\t\tContainer: &parts[0],\n\t\tPath:      parts[1],\n\t}, nil\n}\n\ntype copyFileSpec struct {\n\tContainer *string\n\tPath      string\n}\n\nfunc copyShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn nil, cobra.ShellCompDirectiveFilterFileExt\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_cp_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\t\"gotest.tools/v3/icmd\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/tarutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/\n// Obviously, none of this is fully windows ready - obviously `nerdctl cp` itself is not either, so, ok for now.\nconst (\n\t// Use this to poke the testing rig for improper path handling\n\t// TODO: fuzz this more seriously\n\t// FIXME: the following will break the test (anything that will evaluate on the shell, obviously):\n\t// - `\n\t// - $a, ${a}, etc\n\tcomplexify = \"\" //  = \"-~a0-_.(){}[]*#! \\\"'∞\"\n\n\tpathDoesNotExistRelative = \"does-not-exist\" + complexify\n\tpathDoesNotExistAbsolute = string(os.PathSeparator) + \"does-not-exist\" + complexify\n\tpathIsAFileRelative      = \"is-a-file\" + complexify\n\tpathIsAFileAbsolute      = string(os.PathSeparator) + \"is-a-file\" + complexify\n\tpathIsADirRelative       = \"is-a-dir\" + complexify\n\tpathIsADirAbsolute       = string(os.PathSeparator) + \"is-a-dir\" + complexify\n\tpathIsAVolumeMount       = string(os.PathSeparator) + \"is-a-volume-mount\" + complexify\n\n\tsrcFileName  = \"test-file\" + complexify\n\ttarballName  = \"test-tar\" + complexify\n\tcpFolderName = \"nerdctl-cp-test\"\n\n\t// Since nerdctl cp must NOT obey container wd, but instead resolve paths against the root, we set this\n\t// explicitly to ensure we do the right thing wrt that.\n\tcontainerCwd = \"/nerdctl/cp/test\"\n\n\tdirPerm  = 0o755\n\tfilePerm = 0o644\n)\n\nvar srcDirName = filepath.Join(\"three-levels-src-dir\", \"test-dir\", \"dir\"+complexify)\n\ntype testgroup struct {\n\tdescription string // parent test description\n\ttoContainer bool   // copying to, or from container\n\n\t// sourceSpec as specified by the user (without the container: part) - can be relative or absolute -\n\t// if sourceSpec points to a file, you must use srcFileName for filename\n\tsourceSpec    string\n\tsourceIsAFile bool        // whether the provided sourceSpec points to a file or a dir\n\ttestCases     []testcases // testcases\n}\n\ntype testcases struct {\n\tdescription     string        // textual description of what the test is doing\n\tdestinationSpec string        // destination path as specified by the user (without the container: part) - can be relative or absolute\n\texpect          icmd.Expected // expectation\n\n\t// Optional\n\tcatFile  string                                                       // path that we \"cat\" - defaults to destinationSpec if not specified\n\tsetup    func(base *testutil.Base, container string, destPath string) // additional test setup if needed\n\ttearDown func()                                                       // additional cleanup if needed\n\tvolume   func(base *testutil.Base, id string) (string, string, bool)  // volume creation function if needed (should return the volume name, mountPoint, readonly flag)\n}\n\nfunc TestCopyToContainer(t *testing.T) {\n\tt.Parallel()\n\n\ttestGroups := []*testgroup{\n\t\t{\n\t\t\tdescription:   \"Copying to container, SRC_PATH is a file, absolute\",\n\t\t\tsourceSpec:    filepath.Join(string(os.PathSeparator), srcDirName, srcFileName),\n\t\t\tsourceIsAFile: true,\n\t\t\ttoContainer:   true,\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative, and ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationDirMustExist.Error(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute, and ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationDirMustExist.Error(),\n\t\t\t\t\t},\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative\",\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative, ends with improper \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute, ends with improper \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t// FIXME: it is unclear why the code path with absolute (this test) versus relative (just above)\n\t\t\t\t\t\t// yields a different error. Both should ideally be ErrCannotCopyDirToFile\n\t\t\t\t\t\t// This is probably happening somewhere in resolve.\n\t\t\t\t\t\t// This is not a deal killer, as both DO error with a reasonable explanation, but a bit\n\t\t\t\t\t\t// frustrating\n\t\t\t\t\t\tErr: containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative, ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsADirRelative + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute, ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a volume mount-point\",\n\t\t\t\t\tdestinationSpec: pathIsAVolumeMount,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsAVolumeMount, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\t// FIXME the way we handle volume is not right - too complicated for the test author\n\t\t\t\t\tvolume: func(base *testutil.Base, id string) (string, string, bool) {\n\t\t\t\t\t\tbase.Cmd(\"volume\", \"create\", id).Run()\n\t\t\t\t\t\treturn id, pathIsAVolumeMount, false\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a read-only volume mount-point\",\n\t\t\t\t\tdestinationSpec: pathIsAVolumeMount,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrTargetIsReadOnly.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tvolume: func(base *testutil.Base, id string) (string, string, bool) {\n\t\t\t\t\t\tbase.Cmd(\"volume\", \"create\", id).Run()\n\t\t\t\t\t\treturn id, pathIsAVolumeMount, true\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Copying to container, SRC_PATH is a directory\",\n\t\t\tsourceSpec:  srcDirName,\n\t\t\ttoContainer: true,\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative, and ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute, and ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative\",\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrCannotCopyDirToFile.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrCannotCopyDirToFile.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative, ends with improper \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute, ends with improper \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t// FIXME: it is unclear why the code path with absolute (this test) versus relative (just above)\n\t\t\t\t\t\t// yields a different error. Both should ideally be ErrCannotCopyDirToFile\n\t\t\t\t\t\t// This is probably happening somewhere in resolve.\n\t\t\t\t\t\t// This is not a deal killer, as both DO error with a reasonable explanation, but a bit\n\t\t\t\t\t\t// frustrating\n\t\t\t\t\t\tErr: containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative, ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsADirRelative + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute, ends with \" + string(os.PathSeparator),\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Copying to container, SRC_PATH is a directory ending with /.\",\n\t\t\tsourceSpec:  srcDirName + string(os.PathSeparator) + \".\",\n\t\t\ttoContainer: true,\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription:   \"Copying to container, SRC_PATH is stdin\",\n\t\t\tsourceSpec:    \"-\",\n\t\t\tsourceIsAFile: true,\n\t\t\ttoContainer:   true,\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"mkdir\", \"-p\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is stdout\",\n\t\t\t\t\tdestinationSpec: \"-\",\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      \"one of src or dest must be a container file specification\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute,\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\tbase.Cmd(\"exec\", container, \"touch\", destPath).AssertOK()\n\t\t\t\t\t},\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrCannotCopyDirToFile.Error(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tg := range testGroups {\n\t\tcpTestHelper(t, tg)\n\t}\n}\n\nfunc TestCopyFromContainer(t *testing.T) {\n\tt.Parallel()\n\n\ttestGroups := []*testgroup{\n\t\t{\n\t\t\tdescription:   \"Copying from container, SRC_PATH specifies a file\",\n\t\t\tsourceSpec:    srcFileName,\n\t\t\tsourceIsAFile: true,\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative, and ends with a path separator\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationDirMustExist.Error(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute, and ends with a path separator\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationDirMustExist.Error(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative\",\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative, improperly ends with a separator\",\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute, improperly ends with a separator\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative, ending with a path separator\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute, ending with a path separator\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is stdout\",\n\t\t\t\t\tdestinationSpec: \"-\",\n\t\t\t\t\t// Extra dir to account for folder created from extracted tar file\n\t\t\t\t\tcatFile: filepath.Join(pathIsADirAbsolute, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Copying from container, SRC_PATH specifies a dir\",\n\t\t\tsourceSpec:  srcDirName,\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, relative, ends with path separator\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistRelative + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH does not exist, absolute, ends with path separator\",\n\t\t\t\t\tdestinationSpec: pathDoesNotExistAbsolute + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathDoesNotExistAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative\",\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrCannotCopyDirToFile.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(filepath.Dir(destPath), dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute,\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrCannotCopyDirToFile.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(filepath.Dir(destPath), dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, relative, improperly ends with path separator\",\n\t\t\t\t\tdestinationSpec: pathIsAFileRelative + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(filepath.Dir(destPath), dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a file, absolute, improperly ends with path separator\",\n\t\t\t\t\tdestinationSpec: pathIsAFileAbsolute + string(os.PathSeparator),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErr:      containerutil.ErrDestinationIsNotADir.Error(),\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(filepath.Dir(destPath), dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.WriteFile(destPath, []byte(\"\"), filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative, ends with path separator\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute, ends with path separator\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute + string(os.PathSeparator),\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, filepath.Base(srcDirName), srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is stdout\",\n\t\t\t\t\tdestinationSpec: \"-\",\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcDirName, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\t// Don't make the topmost dir as this is where the tarball must extract\n\t\t\t\t\t\terr := os.MkdirAll(filepath.Dir(destPath), dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tdescription: \"SRC_PATH is a dir, with a trailing slash/dot\",\n\t\t\tsourceSpec:  srcDirName + string(os.PathSeparator) + \".\",\n\t\t\ttestCases: []testcases{\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, relative\",\n\t\t\t\t\tdestinationSpec: pathIsADirRelative,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirRelative, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is a directory, absolute\",\n\t\t\t\t\tdestinationSpec: pathIsADirAbsolute,\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:     \"DEST_PATH is stdout\",\n\t\t\t\t\tdestinationSpec: \"-\",\n\t\t\t\t\tcatFile:         filepath.Join(pathIsADirAbsolute, srcFileName),\n\t\t\t\t\texpect: icmd.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t},\n\t\t\t\t\tsetup: func(base *testutil.Base, container string, destPath string) {\n\t\t\t\t\t\terr := os.MkdirAll(destPath, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tg := range testGroups {\n\t\tcpTestHelper(t, tg)\n\t}\n}\n\nfunc assertCatHelper(base *testutil.Base, catPath string, fileContent []byte, container string, expectedUID int, containerIsStopped bool) {\n\tbase.T.Logf(\"catPath=%q\", catPath)\n\tif container != \"\" && containerIsStopped {\n\t\tbase.Cmd(\"start\", container).AssertOK()\n\t\tdefer base.Cmd(\"stop\", container).AssertOK()\n\t}\n\n\tif container == \"\" {\n\t\tgot, err := os.ReadFile(catPath)\n\t\tassert.NilError(base.T, err, \"Failed reading from file\")\n\t\tassert.DeepEqual(base.T, fileContent, got)\n\t\tst, err := os.Stat(catPath)\n\t\tassert.NilError(base.T, err)\n\t\tstSys := st.Sys().(*syscall.Stat_t)\n\t\texpected := uint32(expectedUID)\n\t\tactual := stSys.Uid\n\t\tassert.DeepEqual(base.T, expected, actual)\n\t} else {\n\t\tbase.Cmd(\"exec\", container, \"sh\", \"-c\", \"--\", fmt.Sprintf(\"ls -lA /; echo %q; cat %q\", catPath, catPath)).AssertOutContains(string(fileContent))\n\t\tbase.Cmd(\"exec\", container, \"stat\", \"-c\", \"%u\", catPath).AssertOutExactly(fmt.Sprintf(\"%d\\n\", expectedUID))\n\t}\n}\n\nfunc cpTestHelper(t *testing.T, tg *testgroup) {\n\t// Get the source path\n\tgroupSourceSpec := tg.sourceSpec\n\tgroupSourceDir := groupSourceSpec\n\tfromStdin := false\n\tif tg.sourceSpec == \"-\" {\n\t\tgroupSourceSpec = filepath.Join(srcDirName, tarballName)\n\t\tgroupSourceDir = srcDirName\n\t\tfromStdin = true\n\t} else if tg.sourceIsAFile {\n\t\tgroupSourceDir = filepath.Dir(groupSourceSpec)\n\t}\n\n\t// Copy direction\n\tcopyToContainer := tg.toContainer\n\t// Description\n\tdescription := tg.description\n\t// Test cases\n\ttestCases := tg.testCases\n\n\t// Compute UIDs dependent on cp direction\n\tvar srcUID, destUID int\n\tif copyToContainer {\n\t\tsrcUID = os.Geteuid()\n\t\tdestUID = srcUID\n\t} else {\n\t\tsrcUID = 42\n\t\tdestUID = os.Geteuid()\n\t}\n\n\tt.Run(description, func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tfor _, tc := range testCases {\n\t\t\ttestCase := tc\n\n\t\t\tt.Run(testCase.description, func(t *testing.T) {\n\t\t\t\tt.Parallel()\n\n\t\t\t\t// Compute test-specific values\n\t\t\t\ttestID := testutil.Identifier(t)\n\t\t\t\tcontainerRunning := testID + \"-r\"\n\t\t\t\tcontainerStopped := testID + \"-s\"\n\t\t\t\tsourceFileContent := []byte(testID)\n\t\t\t\ttempDir := t.TempDir()\n\n\t\t\t\tbase := testutil.NewBase(t)\n\t\t\t\t// Change working directory for commands to execute to the newly created temp directory on the host\n\t\t\t\t// Note that ChDir won't do in a parallel context - and that setup func on the host below\n\t\t\t\t// has to deal with that problem separately by making sure relative paths are resolved against temp\n\t\t\t\tbase.Dir = tempDir\n\n\t\t\t\t// Prepare the specs and derived variables\n\t\t\t\tsourceSpec := groupSourceSpec\n\t\t\t\tcatFile := testCase.catFile\n\n\t\t\t\tdestinationSpec := testCase.destinationSpec\n\t\t\t\ttoStdout := false\n\t\t\t\t// tarball destination just sets up the dir to extract to\n\t\t\t\tif destinationSpec == \"-\" {\n\t\t\t\t\ttoStdout = true\n\t\t\t\t\tdestinationSpec = filepath.Dir(catFile)\n\t\t\t\t}\n\n\t\t\t\t// If the test case does not specify a catFile, start with the destination spec\n\t\t\t\tif catFile == \"\" {\n\t\t\t\t\tcatFile = destinationSpec\n\t\t\t\t}\n\n\t\t\t\tsourceFile := filepath.Join(groupSourceDir, srcFileName)\n\t\t\t\tif copyToContainer {\n\t\t\t\t\tif !filepath.IsAbs(catFile) {\n\t\t\t\t\t\tcatFile = filepath.Join(string(os.PathSeparator), catFile)\n\t\t\t\t\t}\n\n\t\t\t\t\tif fromStdin {\n\t\t\t\t\t\tsourceFile = filepath.Join(tempDir, groupSourceDir, tarballName)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Use an absolute path for evaluation\n\t\t\t\t\t\t// If the sourceFile is still relative, make it absolute to the temp\n\t\t\t\t\t\tsourceFile = filepath.Join(tempDir, sourceFile)\n\t\t\t\t\t\t// If the spec path for source on the host was absolute, make sure we put that under tempDir\n\t\t\t\t\t\tif filepath.IsAbs(sourceSpec) {\n\t\t\t\t\t\t\tsourceSpec = tempDir + sourceSpec\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// If we are copying to host, we need to make sure we have an absolute path to cat, relative to temp,\n\t\t\t\t\t// whether it is relative, or \"absolute\"\n\t\t\t\t\tcatFile = filepath.Join(tempDir, catFile)\n\t\t\t\t\t// If the spec for destination on the host was absolute, make sure we put that under tempDir\n\t\t\t\t\tif filepath.IsAbs(destinationSpec) {\n\t\t\t\t\t\tdestinationSpec = tempDir + destinationSpec\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Teardown: clean-up containers and optional volume\n\t\t\t\ttearDown := func() {\n\t\t\t\t\tbase.Cmd(\"rm\", \"-f\", containerRunning).Run()\n\t\t\t\t\tbase.Cmd(\"rm\", \"-f\", containerStopped).Run()\n\t\t\t\t\tif testCase.volume != nil {\n\t\t\t\t\t\tvolID, _, _ := testCase.volume(base, testID)\n\t\t\t\t\t\tbase.Cmd(\"volume\", \"rm\", volID).Run()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcreateFileOnHost := func() {\n\t\t\t\t\tswitch fromStdin {\n\t\t\t\t\tcase true:\n\t\t\t\t\t\td := filepath.Dir(sourceFile)\n\t\t\t\t\t\ttarCpFolder := filepath.Join(d, cpFolderName)\n\t\t\t\t\t\ttarBinary, _, err := tarutil.FindTarBinary()\n\t\t\t\t\t\tassert.NilError(t, err)\n\n\t\t\t\t\t\terr = os.MkdirAll(tarCpFolder, dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.WriteFile(filepath.Join(tarCpFolder, srcFileName), sourceFileContent, filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\n\t\t\t\t\t\terr = exec.Command(tarBinary, \"-cf\", sourceFile, \"-C\", tarCpFolder, \".\").Run()\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.RemoveAll(tarCpFolder)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\tcase false:\n\t\t\t\t\t\t// Create file on the host\n\t\t\t\t\t\terr := os.MkdirAll(filepath.Dir(sourceFile), dirPerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\terr = os.WriteFile(sourceFile, sourceFileContent, filePerm)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Setup: create volume, containers, create the source file\n\t\t\t\tsetup := func() {\n\t\t\t\t\targs := []string{\"run\", \"-d\", \"-w\", containerCwd}\n\t\t\t\t\tif testCase.volume != nil {\n\t\t\t\t\t\tvol, mount, ro := testCase.volume(base, testID)\n\t\t\t\t\t\tvolArg := fmt.Sprintf(\"%s:%s\", vol, mount)\n\t\t\t\t\t\tif ro {\n\t\t\t\t\t\t\tvolArg += \":ro\"\n\t\t\t\t\t\t}\n\t\t\t\t\t\targs = append(args, \"-v\", volArg)\n\t\t\t\t\t}\n\t\t\t\t\tbase.Cmd(append(args, \"--name\", containerRunning, testutil.CommonImage, \"sleep\", \"Inf\")...).AssertOK()\n\t\t\t\t\tbase.Cmd(append(args, \"--name\", containerStopped, testutil.CommonImage, \"sleep\", \"Inf\")...).AssertOK()\n\n\t\t\t\t\tif copyToContainer {\n\t\t\t\t\t\tcreateFileOnHost()\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create file content in the container\n\t\t\t\t\t\t// Note: cd /, otherwise we end-up in the container cwd, which is NOT obeyed by cp\n\t\t\t\t\t\tmkSrcScript := fmt.Sprintf(\"cd /; mkdir -p %q && echo -n %q >%q && chown %d %q\", filepath.Dir(sourceFile), sourceFileContent, sourceFile, srcUID, sourceFile)\n\t\t\t\t\t\tbase.Cmd(\"exec\", containerRunning, \"sh\", \"-euc\", mkSrcScript).AssertOK()\n\t\t\t\t\t\tbase.Cmd(\"exec\", containerStopped, \"sh\", \"-euc\", mkSrcScript).AssertOK()\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we have optional setup, run that now\n\t\t\t\t\tif testCase.setup != nil {\n\t\t\t\t\t\t// Some specs may come with a trailing slash (proper or improper)\n\t\t\t\t\t\t// Setup should still work in all cases (including if its a file), and get through to the actual test\n\t\t\t\t\t\tsetupDest := destinationSpec\n\t\t\t\t\t\tsetupDest = strings.TrimSuffix(setupDest, string(os.PathSeparator))\n\t\t\t\t\t\tif !filepath.IsAbs(setupDest) {\n\t\t\t\t\t\t\tif copyToContainer {\n\t\t\t\t\t\t\t\tsetupDest = filepath.Join(string(os.PathSeparator), setupDest)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetupDest = filepath.Join(tempDir, setupDest)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttestCase.setup(base, containerRunning, setupDest)\n\t\t\t\t\t\ttestCase.setup(base, containerStopped, setupDest)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stop the \"stopped\" container\n\t\t\t\t\tbase.Cmd(\"stop\", containerStopped).AssertOK()\n\t\t\t\t}\n\n\t\t\t\ttearDown()\n\t\t\t\tt.Cleanup(tearDown)\n\t\t\t\t// If we have custom teardown, do that\n\t\t\t\tif testCase.tearDown != nil {\n\t\t\t\t\ttestCase.tearDown()\n\t\t\t\t\tt.Cleanup(testCase.tearDown)\n\t\t\t\t}\n\n\t\t\t\t// Do the setup\n\t\t\t\tsetup()\n\n\t\t\t\t// If Docker, removes the err part of expectation\n\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\ttestCase.expect.Err = \"\"\n\t\t\t\t}\n\n\t\t\t\t// Build the final src and dest specifiers, including `containerXYZ:`\n\t\t\t\tcontainer := \"\"\n\t\t\t\tif copyToContainer {\n\t\t\t\t\tif fromStdin {\n\t\t\t\t\t\tif toStdout {\n\t\t\t\t\t\t\tnerdctlCmd := base.Cmd(\"cp\", \"-\", \"-\")\n\t\t\t\t\t\t\tnerdctlCmd.Run()\n\t\t\t\t\t\t\tnerdctlCmd.Assert(testCase.expect)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsourceSpec = \"-\"\n\t\t\t\t\t\t\tf, err := os.Open(sourceFile)\n\t\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\t\tnerdctlCmd := base.Cmd(\"cp\", sourceSpec, containerRunning+\":\"+destinationSpec)\n\t\t\t\t\t\t\tnerdctlCmd.Stdin = f\n\n\t\t\t\t\t\t\tnerdctlCmd.Run()\n\t\t\t\t\t\t\tnerdctlCmd.Assert(testCase.expect)\n\t\t\t\t\t\t\tf.Close()\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbase.Cmd(\"cp\", sourceSpec, containerRunning+\":\"+destinationSpec).Assert(testCase.expect)\n\t\t\t\t\t}\n\t\t\t\t\tcontainer = containerRunning\n\t\t\t\t} else {\n\t\t\t\t\tnerdctlCmd := base.Cmd(\"cp\", containerRunning+\":\"+sourceSpec, destinationSpec)\n\t\t\t\t\tif toStdout {\n\t\t\t\t\t\tout := nerdctlCmd.Out()\n\t\t\t\t\t\tnerdctlCmd.Assert(testCase.expect)\n\n\t\t\t\t\t\t// Since we can't check tar file directly easily, extract to the same destination\n\t\t\t\t\t\ttarDst := filepath.Dir(catFile)\n\t\t\t\t\t\ttarBinary, _, err := tarutil.FindTarBinary()\n\t\t\t\t\t\tassert.NilError(t, err)\n\n\t\t\t\t\t\ttarCmd := exec.Command(tarBinary, \"-C\", tarDst, \"-xf\", \"-\")\n\t\t\t\t\t\ttarCmd.Stdin = strings.NewReader(out)\n\t\t\t\t\t\ttarCmd.Stdout = os.Stdout\n\n\t\t\t\t\t\ttarCmd.Run()\n\t\t\t\t\t\tassert.NilError(t, tarCmd.Err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnerdctlCmd.Assert(testCase.expect)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Run the actual test for the running container\n\t\t\t\t// If we expect the op to be a success, also check the destination file\n\t\t\t\tif testCase.expect.ExitCode == 0 {\n\t\t\t\t\tassertCatHelper(base, catFile, sourceFileContent, container, destUID, false)\n\t\t\t\t}\n\n\t\t\t\t// When copying container > host, we get shadowing from the previous container, possibly hiding failures\n\t\t\t\t// Solution: clear-up the tempDir\n\t\t\t\tif copyToContainer {\n\t\t\t\t\terr := os.RemoveAll(tempDir)\n\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\terr = os.MkdirAll(tempDir, dirPerm)\n\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\tcreateFileOnHost()\n\t\t\t\t\tdefer os.RemoveAll(tempDir)\n\t\t\t\t}\n\n\t\t\t\t// ... and for the stopped container\n\t\t\t\tcontainer = \"\"\n\t\t\t\tvar cmd *testutil.Cmd\n\t\t\t\tif fromStdin && toStdout {\n\t\t\t\t\tcmd = base.Cmd(\"cp\", \"-\", \"-\")\n\t\t\t\t} else if copyToContainer {\n\t\t\t\t\tcontainer = containerStopped\n\t\t\t\t\tcmd = base.Cmd(\"cp\", sourceSpec, containerStopped+\":\"+destinationSpec)\n\t\t\t\t\tif fromStdin {\n\t\t\t\t\t\tf, err := os.Open(sourceFile)\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tdefer f.Close()\n\t\t\t\t\t\tcmd.Stdin = f\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tcmd = base.Cmd(\"cp\", containerStopped+\":\"+sourceSpec, destinationSpec)\n\t\t\t\t}\n\n\t\t\t\tif rootlessutil.IsRootless() && !nerdtest.IsDocker() {\n\t\t\t\t\tif fromStdin && toStdout {\n\t\t\t\t\t\t// Regular assert test case should work fine if src and dst are invalid\n\t\t\t\t\t\tcmd.Assert(testCase.expect)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcmd.Assert(\n\t\t\t\t\t\t\ticmd.Expected{\n\t\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\t\tErr:      containerutil.ErrRootlessCannotCp.Error(),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tcmd.Assert(testCase.expect)\n\t\t\t\tif testCase.expect.ExitCode == 0 {\n\t\t\t\t\tassertCatHelper(base, catFile, sourceFileContent, container, destUID, true)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_cp_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport \"github.com/spf13/cobra\"\n\nfunc AddCpCommand(rootCmd *cobra.Command) {\n\t// NOP\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\t\"github.com/spf13/cobra\"\n\tcdiparser \"tags.cncf.io/container-device-interface/pkg/parser\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n)\n\nfunc CreateCommand() *cobra.Command {\n\tshortHelp := \"Create a new container. Optionally specify \\\"ipfs://\\\" or \\\"ipns://\\\" scheme to pull image from IPFS.\"\n\tlongHelp := shortHelp\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\tlongHelp += \"\\n\"\n\t\tlongHelp += \"WARNING: `nerdctl create` is experimental on Windows and currently broken (https://github.com/containerd/nerdctl/issues/28)\"\n\tcase \"freebsd\":\n\t\tlongHelp += \"\\n\"\n\t\tlongHelp += \"WARNING: `nerdctl create` is experimental on FreeBSD and currently requires `--net=none` (https://github.com/containerd/nerdctl/blob/main/docs/freebsd.md)\"\n\t}\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"create [flags] IMAGE [COMMAND] [ARG...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             shortHelp,\n\t\tLong:              longHelp,\n\t\tRunE:              createAction,\n\t\tValidArgsFunction: runShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().SetInterspersed(false)\n\tsetCreateFlags(cmd)\n\treturn cmd\n}\n\n//revive:disable:function-length\nfunc createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {\n\tvar err error\n\topt := types.ContainerCreateOptions{\n\t\tStdout: cmd.OutOrStdout(),\n\t\tStderr: cmd.ErrOrStderr(),\n\t}\n\n\topt.GOptions, err = helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\n\topt.NerdctlCmd, opt.NerdctlArgs = helpers.GlobalFlags(cmd)\n\n\t// #region for basic flags\n\t// The command `container start` doesn't support the flag `--interactive`. Set the default value of `opt.Interactive` false.\n\topt.Interactive = false\n\topt.TTY, err = cmd.Flags().GetBool(\"tty\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// The nerdctl create command similar to nerdctl run -d except the container is never started.\n\t// So we keep the default value of `opt.Detach` true.\n\topt.Detach = true\n\topt.Restart, err = cmd.Flags().GetString(\"restart\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Rm, err = cmd.Flags().GetBool(\"rm\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Pull, err = cmd.Flags().GetString(\"pull\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Pid, err = cmd.Flags().GetString(\"pid\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.StopSignal, err = cmd.Flags().GetString(\"stop-signal\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.StopTimeout, err = cmd.Flags().GetInt(\"stop-timeout\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for platform flags\n\topt.Platform, err = cmd.Flags().GetString(\"platform\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for init process flags\n\topt.InitProcessFlag, err = cmd.Flags().GetBool(\"init\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\tif opt.InitProcessFlag || cmd.Flags().Changed(\"init-binary\") {\n\t\tvar initBinary string\n\t\tinitBinary, err = cmd.Flags().GetString(\"init-binary\")\n\t\tif err != nil {\n\t\t\treturn opt, err\n\t\t}\n\t\topt.InitBinary = &initBinary\n\t}\n\t// #endregion\n\n\t// #region for isolation flags\n\topt.Isolation, err = cmd.Flags().GetString(\"isolation\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for resource flags\n\topt.CPUs, err = cmd.Flags().GetFloat64(\"cpus\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPUQuota, err = cmd.Flags().GetInt64(\"cpu-quota\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPUPeriod, err = cmd.Flags().GetUint64(\"cpu-period\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPUShares, err = cmd.Flags().GetUint64(\"cpu-shares\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPUSetCPUs, err = cmd.Flags().GetString(\"cpuset-cpus\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPUSetMems, err = cmd.Flags().GetString(\"cpuset-mems\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPURealtimePeriod, err = cmd.Flags().GetUint64(\"cpu-rt-period\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CPURealtimeRuntime, err = cmd.Flags().GetUint64(\"cpu-rt-runtime\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Memory, err = cmd.Flags().GetString(\"memory\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.MemoryReservationChanged = cmd.Flags().Changed(\"memory-reservation\")\n\topt.MemoryReservation, err = cmd.Flags().GetString(\"memory-reservation\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.MemorySwap, err = cmd.Flags().GetString(\"memory-swap\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.MemorySwappiness64Changed = cmd.Flags().Changed(\"memory-swappiness\")\n\topt.MemorySwappiness64, err = cmd.Flags().GetInt64(\"memory-swappiness\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.KernelMemoryChanged = cmd.Flag(\"kernel-memory\").Changed\n\topt.KernelMemory, err = cmd.Flags().GetString(\"kernel-memory\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.OomKillDisable, err = cmd.Flags().GetBool(\"oom-kill-disable\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.OomScoreAdjChanged = cmd.Flags().Changed(\"oom-score-adj\")\n\topt.OomScoreAdj, err = cmd.Flags().GetInt(\"oom-score-adj\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.PidsLimit, err = cmd.Flags().GetInt64(\"pids-limit\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CgroupConf, err = cmd.Flags().GetStringSlice(\"cgroup-conf\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Cgroupns, err = cmd.Flags().GetString(\"cgroupns\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CgroupParent, err = cmd.Flags().GetString(\"cgroup-parent\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\n\tallDevices, err := cmd.Flags().GetStringSlice(\"device\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\tfor _, device := range allDevices {\n\t\tif cdiparser.IsQualifiedName(device) {\n\t\t\topt.CDIDevices = append(opt.CDIDevices, device)\n\t\t} else {\n\t\t\topt.Device = append(opt.Device, device)\n\t\t}\n\t}\n\t// #endregion\n\n\t// #region for blkio flags\n\topt.BlkioWeight, err = cmd.Flags().GetUint16(\"blkio-weight\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.BlkioWeightDevice, err = cmd.Flags().GetStringArray(\"blkio-weight-device\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.BlkioDeviceReadBps, err = cmd.Flags().GetStringArray(\"device-read-bps\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.BlkioDeviceWriteBps, err = cmd.Flags().GetStringArray(\"device-write-bps\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.BlkioDeviceReadIOps, err = cmd.Flags().GetStringArray(\"device-read-iops\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.BlkioDeviceWriteIOps, err = cmd.Flags().GetStringArray(\"device-write-iops\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for healthcheck flags\n\topt.HealthCmd, err = cmd.Flags().GetString(\"health-cmd\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.HealthInterval, err = cmd.Flags().GetDuration(\"health-interval\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.HealthTimeout, err = cmd.Flags().GetDuration(\"health-timeout\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.HealthRetries, err = cmd.Flags().GetInt(\"health-retries\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.HealthStartPeriod, err = cmd.Flags().GetDuration(\"health-start-period\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.NoHealthcheck, err = cmd.Flags().GetBool(\"no-healthcheck\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\tif err := helpers.ValidateHealthcheckFlags(opt); err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for intel RDT flags\n\topt.RDTClass, err = cmd.Flags().GetString(\"rdt-class\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for user flags\n\t// If user is set we will attempt to start container with that user (must be present on the host)\n\t// Otherwise we will inherit permissions from the user that the containerd process is running as\n\topt.User, err = cmd.Flags().GetString(\"user\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Umask = \"\"\n\tif cmd.Flags().Changed(\"umask\") {\n\t\topt.Umask, err = cmd.Flags().GetString(\"umask\")\n\t\tif err != nil {\n\t\t\treturn opt, err\n\t\t}\n\t}\n\topt.GroupAdd, err = cmd.Flags().GetStringSlice(\"group-add\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for security flags\n\topt.SecurityOpt, err = cmd.Flags().GetStringArray(\"security-opt\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CapAdd, err = cmd.Flags().GetStringSlice(\"cap-add\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CapDrop, err = cmd.Flags().GetStringSlice(\"cap-drop\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Privileged, err = cmd.Flags().GetBool(\"privileged\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Systemd, err = cmd.Flags().GetString(\"systemd\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for runtime flags\n\topt.Runtime, err = cmd.Flags().GetString(\"runtime\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Sysctl, err = cmd.Flags().GetStringArray(\"sysctl\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for volume flags\n\topt.Volume, err = cmd.Flags().GetStringArray(\"volume\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// tmpfs needs to be StringArray, not StringSlice, to prevent \"/foo:size=64m,exec\" from being split to {\"/foo:size=64m\", \"exec\"}\n\topt.Tmpfs, err = cmd.Flags().GetStringArray(\"tmpfs\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Mount, err = cmd.Flags().GetStringArray(\"mount\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.VolumesFrom, err = cmd.Flags().GetStringArray(\"volumes-from\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for rootfs flags\n\topt.ReadOnly, err = cmd.Flags().GetBool(\"read-only\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Rootfs, err = cmd.Flags().GetBool(\"rootfs\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for env flags\n\topt.EntrypointChanged = cmd.Flags().Changed(\"entrypoint\")\n\topt.Entrypoint, err = cmd.Flags().GetStringArray(\"entrypoint\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Workdir, err = cmd.Flags().GetString(\"workdir\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Env, err = cmd.Flags().GetStringArray(\"env\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.EnvFile, err = cmd.Flags().GetStringSlice(\"env-file\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for metadata flags\n\topt.Name, err = cmd.Flags().GetString(\"name\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Label, err = cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.LabelFile, err = cmd.Flags().GetStringSlice(\"label-file\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Annotations, err = cmd.Flags().GetStringArray(\"annotation\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.CidFile, err = cmd.Flags().GetString(\"cidfile\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.PidFile = \"\"\n\tif cmd.Flags().Changed(\"pidfile\") {\n\t\topt.PidFile, err = cmd.Flags().GetString(\"pidfile\")\n\t\tif err != nil {\n\t\t\treturn opt, err\n\t\t}\n\t}\n\t// #endregion\n\n\t// #region for logging flags\n\t// json-file is the built-in and default log driver for nerdctl\n\topt.LogDriver, err = cmd.Flags().GetString(\"log-driver\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.LogOpt, err = cmd.Flags().GetStringArray(\"log-opt\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for shared memory flags\n\topt.IPC, err = cmd.Flags().GetString(\"ipc\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.ShmSize, err = cmd.Flags().GetString(\"shm-size\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for gpu flags\n\topt.GPUs, err = cmd.Flags().GetStringArray(\"gpus\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for ulimit flags\n\topt.Ulimit, err = cmd.Flags().GetStringSlice(\"ulimit\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for ipfs flags\n\topt.IPFSAddress, err = cmd.Flags().GetString(\"ipfs-address\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\t// #endregion\n\n\t// #region for image pull and verify options\n\timageVerifyOpt, err := helpers.VerifyOptions(cmd)\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.ImagePullOpt = types.ImagePullOptions{\n\t\tGOptions:      opt.GOptions,\n\t\tVerifyOptions: imageVerifyOpt,\n\t\tIPFSAddress:   opt.IPFSAddress,\n\t\tStdout:        opt.Stdout,\n\t\tStderr:        opt.Stderr,\n\t\tQuiet:         quiet,\n\t}\n\t// #endregion\n\n\t// #region for UserNS\n\topt.UserNS, err = cmd.Flags().GetString(\"userns-remap\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\n\tuserns, err := cmd.Flags().GetString(\"userns\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\n\tif userns == \"host\" {\n\t\topt.UserNS = \"\"\n\t} else if userns != \"\" {\n\t\treturn opt, fmt.Errorf(\"invalid user mode\")\n\t}\n\n\tif opt.Privileged && opt.UserNS != \"\" {\n\t\t//userns-remap is not supported with privileged flag.\n\t\t// Ref: https://docs.docker.com/engine/security/userns-remap/\n\t\treturn opt, fmt.Errorf(\"privileged flag cannot be used with userns-remap\")\n\t}\n\t// #endregion\n\n\treturn opt, nil\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\tcreateOpt, err := createOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif (createOpt.Platform == \"windows\" || createOpt.Platform == \"freebsd\") && !createOpt.GOptions.Experimental {\n\t\treturn fmt.Errorf(\"%s requires experimental mode to be enabled\", createOpt.Platform)\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClientWithPlatform(cmd.Context(), createOpt.GOptions.Namespace, createOpt.GOptions.Address, createOpt.Platform)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tnetFlags, err := loadNetworkFlags(cmd, createOpt.GOptions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load networking flags: %w\", err)\n\t}\n\n\tnetManager, err := containerutil.NewNetworkingOptionsManager(createOpt.GOptions, netFlags, client)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc, gc, err := container.Create(ctx, client, args, netManager, createOpt)\n\tif err != nil {\n\t\tif gc != nil {\n\t\t\tgc()\n\t\t}\n\t\treturn err\n\t}\n\t// defer setting `nerdctl/error` label in case of error\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tcontainerutil.UpdateErrorLabel(ctx, c, err)\n\t\t}\n\t}()\n\n\tfmt.Fprintln(createOpt.Stdout, c.ID())\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_create_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"github.com/opencontainers/go-digest\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\nfunc TestCreateWithLabel(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\n\tbase.Cmd(\"create\", \"--name\", tID, \"--label\", \"foo=bar\", testutil.NginxAlpineImage, \"echo\", \"foo\").AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", tID).Run()\n\tinspect := base.InspectContainer(tID)\n\tassert.Equal(base.T, \"bar\", inspect.Config.Labels[\"foo\"])\n\t// the label `maintainer`` is defined by image\n\tassert.Equal(base.T, \"NGINX Docker Maintainers <docker-maint@nginx.com>\", inspect.Config.Labels[\"maintainer\"])\n}\n\nfunc TestCreateWithMACAddress(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\tnetworkBridge := \"testNetworkBridge\" + tID\n\tnetworkMACvlan := \"testNetworkMACvlan\" + tID\n\tnetworkIPvlan := \"testNetworkIPvlan\" + tID\n\n\ttearDown := func() {\n\t\tbase.Cmd(\"network\", \"rm\", networkBridge).Run()\n\t\tbase.Cmd(\"network\", \"rm\", networkMACvlan).Run()\n\t\tbase.Cmd(\"network\", \"rm\", networkIPvlan).Run()\n\t}\n\n\ttearDown()\n\tt.Cleanup(tearDown)\n\n\tbase.Cmd(\"network\", \"create\", networkBridge, \"--driver\", \"bridge\").AssertOK()\n\tbase.Cmd(\"network\", \"create\", networkMACvlan, \"--driver\", \"macvlan\").AssertOK()\n\tbase.Cmd(\"network\", \"create\", networkIPvlan, \"--driver\", \"ipvlan\").AssertOK()\n\n\tdefaultMac := base.Cmd(\"run\", \"--rm\", \"-i\", \"--network\", \"host\", testutil.CommonImage).\n\t\tCmdOption(testutil.WithStdin(strings.NewReader(\"ip addr show eth0 | grep ether | awk '{printf $2}'\"))).\n\t\tRun().Stdout()\n\n\tpassedMac := \"we expect the generated mac on the output\"\n\ttests := []struct {\n\t\tNetwork string\n\t\tWantErr bool\n\t\tExpect  string\n\t}{\n\t\t{\"host\", false, defaultMac}, // anything but the actual address being passed\n\t\t{\"none\", false, \"\"},\n\t\t{\"container:whatever\" + tID, true, \"container\"}, // \"No such container\" vs. \"could not find container\"\n\t\t{\"bridge\", false, passedMac},\n\t\t{networkBridge, false, passedMac},\n\t\t{networkMACvlan, false, passedMac},\n\t\t{networkIPvlan, true, \"not support\"},\n\t}\n\tfor i, test := range tests {\n\t\tcontainerName := fmt.Sprintf(\"%s_%d\", tID, i)\n\t\ttestName := fmt.Sprintf(\"%s_container:%s_network:%s_expect:%s\", tID, containerName, test.Network, test.Expect)\n\t\texpect := test.Expect\n\t\tnetwork := test.Network\n\t\twantErr := test.WantErr\n\t\tt.Run(testName, func(tt *testing.T) {\n\t\t\ttt.Parallel()\n\n\t\t\tmacAddress, err := nettestutil.GenerateMACAddress()\n\t\t\tif err != nil {\n\t\t\t\ttt.Errorf(\"failed to generate MAC address: %s\", err)\n\t\t\t}\n\t\t\tif expect == passedMac {\n\t\t\t\texpect = macAddress\n\t\t\t}\n\t\t\ttearDown := func() {\n\t\t\t\tbase.Cmd(\"rm\", \"-f\", containerName).Run()\n\t\t\t}\n\t\t\ttearDown()\n\t\t\ttt.Cleanup(tearDown)\n\t\t\t// This is currently blocked by https://github.com/containerd/nerdctl/pull/3104\n\t\t\t// res := base.Cmd(\"create\", \"-i\", \"--network\", network, \"--mac-address\", macAddress, testutil.CommonImage).Run()\n\t\t\tres := base.Cmd(\"create\", \"--network\", network, \"--name\", containerName,\n\t\t\t\t\"--mac-address\", macAddress, testutil.CommonImage,\n\t\t\t\t\"sh\", \"-c\", \"--\", \"ip addr show\").Run()\n\n\t\t\tif !wantErr {\n\t\t\t\tassert.Assert(t, res.ExitCode == 0, \"Command should have succeeded\", res)\n\t\t\t\t// This is currently blocked by: https://github.com/containerd/nerdctl/pull/3104\n\t\t\t\t// res = base.Cmd(\"start\", \"-i\", containerName).\n\t\t\t\t//\tCmdOption(testutil.WithStdin(strings.NewReader(\"ip addr show eth0 | grep ether | awk '{printf $2}'\"))).Run()\n\t\t\t\tres = base.Cmd(\"start\", \"-a\", containerName).Run()\n\t\t\t\t// FIXME: flaky - this has failed on the CI once, with the output NOT containing anything\n\t\t\t\t// https://github.com/containerd/nerdctl/actions/runs/11392051487/job/31697214002?pr=3535#step:7:271\n\t\t\t\tassert.Assert(t, strings.Contains(res.Stdout(), expect), fmt.Sprintf(\"expected output to contain %q: %q\", expect, res.Stdout()))\n\t\t\t\tassert.Assert(t, res.ExitCode == 0, \"Command should have succeeded\")\n\t\t\t} else {\n\t\t\t\tif nerdtest.IsDocker() &&\n\t\t\t\t\t(network == networkIPvlan || network == \"container:whatever\"+tID) {\n\t\t\t\t\t// unlike nerdctl\n\t\t\t\t\t// when using network ipvlan or container in Docker\n\t\t\t\t\t// it delays fail on executing start command\n\t\t\t\t\tassert.Assert(t, res.ExitCode == 0, \"Command should have succeeded\", res)\n\t\t\t\t\tres = base.Cmd(\"start\", \"-i\", \"-a\", containerName).\n\t\t\t\t\t\tCmdOption(testutil.WithStdin(strings.NewReader(\"ip addr show eth0 | grep ether | awk '{printf $2}'\"))).Run()\n\t\t\t\t}\n\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/3101\n\t\t\t\tif nerdtest.IsDocker() &&\n\t\t\t\t\t(network == networkBridge) {\n\t\t\t\t\texpect = \"\"\n\t\t\t\t}\n\t\t\t\tif expect != \"\" {\n\t\t\t\t\tassert.Assert(t, strings.Contains(res.Combined(), expect), fmt.Sprintf(\"expected output to contain %q: %q\", expect, res.Combined()))\n\t\t\t\t} else {\n\t\t\t\t\tassert.Assert(t, res.Combined() == \"\", fmt.Sprintf(\"expected output to be empty: %q\", res.Combined()))\n\t\t\t\t}\n\t\t\t\tassert.Assert(t, res.ExitCode != 0, \"Command should have failed\", res)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCreateWithTty(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\timageName := testutil.CommonImage\n\twithoutTtyContainerName := \"without-terminal-\" + testutil.Identifier(t)\n\twithTtyContainerName := \"with-terminal-\" + testutil.Identifier(t)\n\n\t// without -t, fail\n\tbase.Cmd(\"create\", \"--name\", withoutTtyContainerName, imageName, \"stty\").AssertOK()\n\tbase.Cmd(\"start\", withoutTtyContainerName).AssertOK()\n\tdefer base.Cmd(\"container\", \"rm\", \"-f\", withoutTtyContainerName).AssertOK()\n\tbase.Cmd(\"logs\", withoutTtyContainerName).AssertCombinedOutContains(\"stty: standard input: Not a tty\")\n\twithoutTtyContainer := base.InspectContainer(withoutTtyContainerName)\n\tassert.Equal(base.T, 1, withoutTtyContainer.State.ExitCode)\n\n\t// with -t, success\n\tbase.Cmd(\"create\", \"-t\", \"--name\", withTtyContainerName, imageName, \"stty\").AssertOK()\n\tbase.Cmd(\"start\", withTtyContainerName).AssertOK()\n\tdefer base.Cmd(\"container\", \"rm\", \"-f\", withTtyContainerName).AssertOK()\n\tbase.Cmd(\"logs\", withTtyContainerName).AssertCombinedOutContains(\"speed 38400 baud; line = 0;\")\n\twithTtyContainer := base.InspectContainer(withTtyContainerName)\n\tassert.Equal(base.T, 0, withTtyContainer.State.ExitCode)\n}\n\n// TestIssue2993 tests https://github.com/containerd/nerdctl/issues/2993\nfunc TestIssue2993(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\tconst (\n\t\tcontainersPathKey = \"containersPath\"\n\t\tetchostsPathKey   = \"etchostsPath\"\n\t)\n\n\tgetAddrHash := func(addr string) string {\n\t\tconst addrHashLen = 8\n\n\t\td := digest.SHA256.FromString(addr)\n\t\th := d.Encoded()[0:addrHashLen]\n\n\t\treturn h\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Issue #2993 - nerdctl no longer leaks containers and etchosts directories and files when container creation fails.\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdataRoot := data.Temp().Path()\n\n\t\t\t\thelpers.Ensure(\"run\", \"--data-root\", dataRoot, \"--name\", data.Identifier(), \"-d\", testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\n\t\t\t\th := getAddrHash(defaults.DefaultAddress)\n\t\t\t\tdataStore := filepath.Join(dataRoot, h)\n\n\t\t\t\tnamespace := string(helpers.Read(nerdtest.Namespace))\n\n\t\t\t\tcontainersPath := filepath.Join(dataStore, \"containers\", namespace)\n\t\t\t\tcontainersDirs, err := os.ReadDir(containersPath)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, len(containersDirs), 1)\n\n\t\t\t\tetchostsPath := filepath.Join(dataStore, \"etchosts\", namespace)\n\t\t\t\tetchostsDirs, err := os.ReadDir(etchostsPath)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, len(etchostsDirs), 1)\n\n\t\t\t\tdata.Labels().Set(containersPathKey, containersPath)\n\t\t\t\tdata.Labels().Set(etchostsPathKey, etchostsPath)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"--data-root\", data.Temp().Path(), \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--data-root\", data.Temp().Path(), \"--name\", data.Identifier(), \"-d\", testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"is already used by ID\")},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tcontainersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey))\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tassert.Equal(t, len(containersDirs), 1)\n\n\t\t\t\t\t\tetchostsDirs, err := os.ReadDir(data.Labels().Get(etchostsPathKey))\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tassert.Equal(t, len(etchostsDirs), 1)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Issue #2993 - nerdctl no longer leaks containers and etchosts directories and files when containers are removed.\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdataRoot := data.Temp().Path()\n\n\t\t\t\thelpers.Ensure(\"run\", \"--data-root\", dataRoot, \"--name\", data.Identifier(), \"-d\", testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\n\t\t\t\th := getAddrHash(defaults.DefaultAddress)\n\t\t\t\tdataStore := filepath.Join(dataRoot, h)\n\n\t\t\t\tnamespace := string(helpers.Read(nerdtest.Namespace))\n\n\t\t\t\tcontainersPath := filepath.Join(dataStore, \"containers\", namespace)\n\t\t\t\tcontainersDirs, err := os.ReadDir(containersPath)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, len(containersDirs), 1)\n\n\t\t\t\tetchostsPath := filepath.Join(dataStore, \"etchosts\", namespace)\n\t\t\t\tetchostsDirs, err := os.ReadDir(etchostsPath)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, len(etchostsDirs), 1)\n\n\t\t\t\tdata.Labels().Set(containersPathKey, containersPath)\n\t\t\t\tdata.Labels().Set(etchostsPathKey, etchostsPath)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"--data-root\", data.Temp().Path(), \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"--data-root\", data.Temp().Path(), \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tcontainersDirs, err := os.ReadDir(data.Labels().Get(containersPathKey))\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tassert.Equal(t, len(containersDirs), 0)\n\n\t\t\t\t\t\tetchostsDirs, err := os.ReadDir(data.Labels().Get(etchostsPathKey))\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tassert.Equal(t, len(etchostsDirs), 0)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestCreateFromOCIArchive(t *testing.T) {\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\n\t// Docker does not support creating containers from OCI archive.\n\ttestutil.DockerIncompatible(t)\n\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tteardown := func() {\n\t\tbase.Cmd(\"rm\", \"-f\", containerName).Run()\n\t\tbase.Cmd(\"rmi\", \"-f\", imageName).Run()\n\t}\n\tdefer teardown()\n\tteardown()\n\n\tconst sentinel = \"test-nerdctl-create-from-oci-archive\"\n\tdockerfile := fmt.Sprintf(`FROM %s\n\tCMD [\"echo\", \"%s\"]`, testutil.CommonImage, sentinel)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\ttag := fmt.Sprintf(\"%s:latest\", imageName)\n\ttarPath := fmt.Sprintf(\"%s/%s.tar\", buildCtx, imageName)\n\n\tbase.Cmd(\"build\", \"--tag\", tag, fmt.Sprintf(\"--output=type=oci,dest=%s\", tarPath), buildCtx).AssertOK()\n\tbase.Cmd(\"create\", \"--rm\", \"--name\", containerName, fmt.Sprintf(\"oci-archive://%s\", tarPath)).AssertOK()\n\tbase.Cmd(\"start\", \"--attach\", containerName).AssertOutContains(\"test-nerdctl-create-from-oci-archive\")\n}\n\nfunc TestUsernsMappingCreateCmd(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.AllowModifyUserns,\n\t\t\tnerdtest.RemapIDs,\n\t\t\trequire.Not(nerdtest.Docker)),\n\t\tNoParallel: true,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Labels().Set(\"validUserns\", \"nerdctltestuser\")\n\t\t\tdata.Labels().Set(\"expectedHostUID\", \"123456789\")\n\t\t\tdata.Labels().Set(\"invalidUserns\", \"invaliduser\")\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Test container create with valid Userns\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\thelpers.Ensure(\"create\", \"--tty\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--name\", data.Identifier(), testutil.NginxAlpineImage)\n\t\t\t\t\treturn helpers.Command(\"start\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tactualHostUID, err := getContainerHostUID(helpers, data.Identifier())\n\t\t\t\t\t\t\tassert.NilError(t, err, \"Failed to get container host UID\")\n\t\t\t\t\t\t\tassert.Assert(t, actualHostUID == data.Labels().Get(\"expectedHostUID\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container create failure with valid Userns and privileged flag\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"create\", \"--tty\", \"--privileged\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--name\", data.Identifier(), testutil.NginxAlpineImage)\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container create with invalid Userns\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"create\", \"--tty\", \"--userns-remap\", data.Labels().Get(\"invalidUserns\"), \"--name\", data.Identifier(), testutil.NginxAlpineImage)\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc getContainerHostUID(helpers test.Helpers, containerName string) (string, error) {\n\tresult := helpers.Capture(\"inspect\", \"--format\", \"{{.State.Pid}}\", containerName)\n\tpidStr := strings.TrimSpace(result)\n\tpid, err := strconv.Atoi(pidStr)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid PID: %v\", err)\n\t}\n\n\tstat, err := os.Stat(fmt.Sprintf(\"/proc/%d\", pid))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to stat process: %v\", err)\n\t}\n\n\tuid := int(stat.Sys().(*syscall.Stat_t).Uid)\n\treturn strconv.Itoa(uid), nil\n}\n\nfunc appendUsernsConfig(userns string, hostUID string, helpers test.Helpers) error {\n\taddUser(userns, hostUID, helpers)\n\tentry := fmt.Sprintf(\"%s:%s:65536\\n\", userns, hostUID)\n\ttempDir := helpers.T().TempDir()\n\tfiles := []string{\"subuid\", \"subgid\"}\n\tfor _, file := range files {\n\n\t\tfileBak := filepath.Join(tempDir, file)\n\t\tdefer os.Remove(fileBak)\n\t\td, err := os.Create(fileBak)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create %s: %w\", fileBak, err)\n\t\t}\n\n\t\ts, err := os.Open(filepath.Join(\"/etc\", file))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open %s: %w\", file, err)\n\t\t}\n\t\tdefer s.Close()\n\n\t\t_, err = io.Copy(d, s)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to copy %s to %s: %w\", file, fileBak, err)\n\t\t}\n\n\t\tf, err := os.OpenFile(fmt.Sprintf(\"/etc/%s\", file), os.O_APPEND|os.O_WRONLY, 0644)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open %s: %w\", file, err)\n\t\t}\n\t\tdefer f.Close()\n\n\t\tif _, err := f.WriteString(entry); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to %s: %w\", file, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc addUser(username string, hostID string, helpers test.Helpers) {\n\thelpers.Custom(\"groupadd\", \"-g\", hostID, username).Run(&test.Expected{\n\t\tExitCode: 0})\n\thelpers.Custom(\"useradd\", \"-u\", hostID, \"-g\", hostID, \"-s\", \"/bin/false\", username).Run(&test.Expected{\n\t\tExitCode: 0})\n}\n\nfunc removeUsernsConfig(t *testing.T, userns string, helpers test.Helpers) {\n\tdelUser(userns, helpers)\n\tdelGroup(userns, helpers)\n\ttempDir := helpers.T().TempDir()\n\tfiles := []string{\"subuid\", \"subgid\"}\n\tfor _, file := range files {\n\t\tfileBak := filepath.Join(tempDir, file)\n\t\ts, err := os.Open(fileBak)\n\t\tif err != nil {\n\t\t\tt.Logf(\"failed to open %s, Error: %s\", fileBak, err)\n\t\t\tcontinue\n\t\t}\n\t\tdefer s.Close()\n\n\t\td, err := os.Open(filepath.Join(\"/etc/%s\", file))\n\t\tif err != nil {\n\t\t\tt.Logf(\"failed to open %s, Error: %s\", file, err)\n\t\t\tcontinue\n\n\t\t}\n\t\tdefer d.Close()\n\n\t\t_, err = io.Copy(d, s)\n\t\tif err != nil {\n\t\t\tt.Logf(\"failed to restore. Copy %s to %s failed, Error %s\", fileBak, file, err)\n\t\t\tcontinue\n\t\t}\n\n\t}\n}\n\nfunc delUser(username string, helpers test.Helpers) {\n\thelpers.Custom(\"userdel\", username).Run(&test.Expected{ExitCode: expect.ExitCodeNoCheck})\n}\n\nfunc delGroup(groupname string, helpers test.Helpers) {\n\thelpers.Custom(\"groupdel\", groupname).Run(&test.Expected{ExitCode: expect.ExitCodeNoCheck})\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_create_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestCreate(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(\"container\"), testutil.CommonImage, \"echo\", \"foo\")\n\t\tdata.Labels().Set(\"cID\", data.Identifier(\"container\"))\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"ps -a\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\", \"--filter\", \"status=created\", \"--filter\", fmt.Sprintf(\"name=%s\", data.Labels().Get(\"cID\")))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"Created\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"start\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"start\", \"-a\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"foo\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestCreateHyperVContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.HyperV\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"create\", \"--isolation\", \"hyperv\", \"--name\", data.Identifier(\"container\"), testutil.CommonImage, \"echo\", \"foo\")\n\t\tdata.Labels().Set(\"cID\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"ps -a\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand:     test.Command(\"ps\", \"-a\"),\n\t\t\t// FIXME: this might get a false positive if other tests have created a container\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"Created\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"start\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"start\", data.Labels().Get(\"cID\"))\n\t\t\t\tran := false\n\t\t\t\tfor i := 0; i < 10 && !ran; i++ {\n\t\t\t\t\thelpers.Command(\"container\", \"inspect\", data.Labels().Get(\"cID\")).\n\t\t\t\t\t\tRun(&test.Expected{\n\t\t\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tvar dc []dockercompat.Container\n\t\t\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\t\t\tif err != nil || len(dc) == 0 {\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tassert.Equal(t, len(dc), 1, \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\t\t\tran = dc[0].State.Status == \"exited\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t}\n\t\t\t\tassert.Assert(t, ran, \"container did not ran after 10 seconds\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"foo\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_diff.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/opencontainers/image-spec/identity\"\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/leases\"\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/continuity/fs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc DiffCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"diff [CONTAINER]\",\n\t\tShort:             \"Inspect changes to files or directories on a container's filesystem\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              diffAction,\n\t\tValidArgsFunction: diffShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc diffOptions(cmd *cobra.Command) (types.ContainerDiffOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerDiffOptions{}, err\n\t}\n\n\treturn types.ContainerDiffOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t}, nil\n}\n\nfunc diffAction(cmd *cobra.Command, args []string) error {\n\toptions, err := diffOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tchanges, err := getChanges(ctx, client, found.Container)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfor _, change := range changes {\n\t\t\t\tswitch change.Kind {\n\t\t\t\tcase fs.ChangeKindAdd:\n\t\t\t\t\tfmt.Fprintln(options.Stdout, \"A\", change.Path)\n\t\t\t\tcase fs.ChangeKindModify:\n\t\t\t\t\tfmt.Fprintln(options.Stdout, \"C\", change.Path)\n\t\t\t\tcase fs.ChangeKindDelete:\n\t\t\t\t\tfmt.Fprintln(options.Stdout, \"D\", change.Path)\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcontainer := args[0]\n\n\tn, err := walker.Walk(ctx, container)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", container)\n\t}\n\treturn nil\n}\n\nfunc getChanges(ctx context.Context, client *containerd.Client, container containerd.Container) ([]fs.Change, error) {\n\tid := container.ID()\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar (\n\t\tsnName = info.Snapshotter\n\t\tsn     = client.SnapshotService(snName)\n\t)\n\n\tmounts, err := sn.Mounts(ctx, id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// NOTE: Moby uses provided rootfs to run container. It doesn't support\n\t// to commit container created by moby.\n\tbaseImgWithoutPlatform, err := client.ImageService().Get(ctx, info.Image)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"container %q lacks image (wasn't created by nerdctl?): %w\", id, err)\n\t}\n\tplatformLabel := info.Labels[labels.Platform]\n\tif platformLabel == \"\" {\n\t\tplatformLabel = platforms.DefaultString()\n\t\tlog.G(ctx).Warnf(\"Image lacks label %q, assuming the platform to be %q\", labels.Platform, platformLabel)\n\t}\n\tocispecPlatform, err := platforms.Parse(platformLabel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlog.G(ctx).Debugf(\"ocispecPlatform=%q\", platforms.Format(ocispecPlatform))\n\tplatformMC := platforms.Only(ocispecPlatform)\n\tbaseImg := containerd.NewImageWithPlatform(client, baseImgWithoutPlatform, platformMC)\n\n\tbaseImgConfig, _, err := imgutil.ReadImageConfig(ctx, baseImg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Don't gc me and clean the dirty data after 1 hour!\n\tctx, done, err := client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create lease for diff: %w\", err)\n\t}\n\tdefer done(ctx)\n\n\trootfsID := identity.ChainID(baseImgConfig.RootFS.DiffIDs).String()\n\n\trandomID := idgen.GenerateID()\n\tparent, err := sn.View(ctx, randomID, rootfsID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer sn.Remove(ctx, randomID)\n\n\tvar changes []fs.Change\n\terr = mount.WithReadonlyTempMount(ctx, parent, func(lower string) error {\n\t\treturn mount.WithReadonlyTempMount(ctx, mounts, func(upper string) error {\n\t\t\treturn fs.Changes(ctx, lower, upper, func(ck fs.ChangeKind, s string, fi os.FileInfo, err error) error {\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tchanges = appendChanges(changes, fs.Change{\n\t\t\t\t\tKind: ck,\n\t\t\t\t\tPath: s,\n\t\t\t\t})\n\t\t\t\treturn nil\n\t\t\t})\n\t\t})\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn changes, err\n}\n\nfunc appendChanges(changes []fs.Change, fsChange fs.Change) []fs.Change {\n\tnewDir, _ := filepath.Split(fsChange.Path)\n\tnewDirPath := filepath.SplitList(newDir)\n\n\tif len(changes) == 0 {\n\t\tfor i := 1; i < len(newDirPath); i++ {\n\t\t\tchanges = append(changes, fs.Change{\n\t\t\t\tKind: fs.ChangeKindModify,\n\t\t\t\tPath: filepath.Join(newDirPath[:i+1]...),\n\t\t\t})\n\t\t}\n\t\treturn append(changes, fsChange)\n\t}\n\tlast := changes[len(changes)-1]\n\tlastDir, _ := filepath.Split(last.Path)\n\tlastDirPath := filepath.SplitList(lastDir)\n\tfor i := range newDirPath {\n\t\tif len(lastDirPath) > i && lastDirPath[i] == newDirPath[i] {\n\t\t\tcontinue\n\t\t}\n\t\tchanges = append(changes, fs.Change{\n\t\t\tKind: fs.ChangeKindModify,\n\t\t\tPath: filepath.Join(newDirPath[:i+1]...),\n\t\t})\n\t}\n\treturn append(changes, fsChange)\n}\n\nfunc diffShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show container names\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_diff_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestDiff(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// It is unclear why this is failing with docker when run in parallel\n\t// Obviously some other container test is interfering\n\tif nerdtest.IsDocker() {\n\t\ttestCase.NoParallel = true\n\t}\n\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-euxc\", \"touch /a; touch /bin/b; rm /bin/base64\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"diff\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(\n\t\t0,\n\t\tnil,\n\t\texpect.Contains(\n\t\t\t\"A /a\",\n\t\t\t\"C /bin\",\n\t\t\t\"A /bin/b\",\n\t\t\t\"D /bin/base64\"),\n\t)\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_exec.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc ExecCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"exec [flags] CONTAINER COMMAND [ARG...]\",\n\t\tArgs:              cobra.MinimumNArgs(2),\n\t\tShort:             \"Run a command in a running container\",\n\t\tRunE:              execAction,\n\t\tValidArgsFunction: execShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().SetInterspersed(false)\n\n\tcmd.Flags().BoolP(\"tty\", \"t\", false, \"Allocate a pseudo-TTY\")\n\tcmd.Flags().BoolP(\"interactive\", \"i\", false, \"Keep STDIN open even if not attached\")\n\tcmd.Flags().BoolP(\"detach\", \"d\", false, \"Detached mode: run command in the background\")\n\tcmd.Flags().StringP(\"workdir\", \"w\", \"\", \"Working directory inside the container\")\n\t// env needs to be StringArray, not StringSlice, to prevent \"FOO=foo1,foo2\" from being split to {\"FOO=foo1\", \"foo2\"}\n\tcmd.Flags().StringArrayP(\"env\", \"e\", nil, \"Set environment variables\")\n\t// env-file is defined as StringSlice, not StringArray, to allow specifying \"--env-file=FILE1,FILE2\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"env-file\", nil, \"Set environment variables from file\")\n\tcmd.Flags().Bool(\"privileged\", false, \"Give extended privileges to the command\")\n\tcmd.Flags().StringP(\"user\", \"u\", \"\", \"Username or UID (format: <name|uid>[:<group|gid>])\")\n\treturn cmd\n}\n\nfunc execOptions(cmd *cobra.Command) (types.ContainerExecOptions, error) {\n\t// We do not check if we have a terminal here, as container.Exec calling console.Current will ensure that\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\n\tisInteractive, err := cmd.Flags().GetBool(\"interactive\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\tisTerminal, err := cmd.Flags().GetBool(\"tty\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\tisDetach, err := cmd.Flags().GetBool(\"detach\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\n\tif isInteractive {\n\t\tif isDetach {\n\t\t\treturn types.ContainerExecOptions{}, errors.New(\"currently flag -i and -d cannot be specified together (FIXME)\")\n\t\t}\n\t}\n\n\tif isTerminal {\n\t\tif isDetach {\n\t\t\treturn types.ContainerExecOptions{}, errors.New(\"currently flag -t and -d cannot be specified together (FIXME)\")\n\t\t}\n\t}\n\n\tworkdir, err := cmd.Flags().GetString(\"workdir\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\n\tenvFile, err := cmd.Flags().GetStringSlice(\"env-file\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\tenv, err := cmd.Flags().GetStringArray(\"env\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\tprivileged, err := cmd.Flags().GetBool(\"privileged\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\tuser, err := cmd.Flags().GetString(\"user\")\n\tif err != nil {\n\t\treturn types.ContainerExecOptions{}, err\n\t}\n\n\treturn types.ContainerExecOptions{\n\t\tGOptions:    globalOptions,\n\t\tTTY:         isTerminal,\n\t\tInteractive: isInteractive,\n\t\tDetach:      isDetach,\n\t\tWorkdir:     workdir,\n\t\tEnv:         env,\n\t\tEnvFile:     envFile,\n\t\tPrivileged:  privileged,\n\t\tUser:        user,\n\t}, nil\n}\n\nfunc execAction(cmd *cobra.Command, args []string) error {\n\toptions, err := execOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// simulate the behavior of double dash\n\tnewArg := []string{}\n\tif len(args) >= 2 && args[1] == \"--\" {\n\t\tnewArg = append(newArg, args[:1]...)\n\t\tnewArg = append(newArg, args[2:]...)\n\t\targs = newArg\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Exec(ctx, client, args, options)\n}\n\nfunc execShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tif len(args) == 0 {\n\t\t// show running container names\n\t\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\t\treturn st == containerd.Running\n\t\t}\n\t\treturn completion.ContainerNames(cmd, statusFilterFn)\n\t}\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_exec_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestExecWithUser(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\n\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\n\t\tdata.Labels().Set(\"container_name\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with no user flag\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container_name\"), \"id\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"uid=0(root) gid=0(root)\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with --user 1000\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", \"--user\", \"1000\", data.Labels().Get(\"container_name\"), \"id\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"uid=1000 gid=0(root)\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with --user 1000:users\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", \"--user\", \"1000:users\", data.Labels().Get(\"container_name\"), \"id\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"uid=1000 gid=100(users)\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with --user guest\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", \"--user\", \"guest\", data.Labels().Get(\"container_name\"), \"id\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"uid=405(guest) gid=100(users)\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with --user nobody\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", \"--user\", \"nobody\", data.Labels().Get(\"container_name\"), \"id\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"uid=65534(nobody) gid=65534(nobody)\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with --user nobody:users\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", \"--user\", \"nobody:users\", data.Labels().Get(\"container_name\"), \"id\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"uid=65534(nobody) gid=100(users)\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestExecTTY(t *testing.T) {\n\tconst sttyPartialOutput = \"speed 38400 baud\"\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\n\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\n\t\tdata.Labels().Set(\"container_name\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"stty with -it\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"exec\", \"-it\", data.Labels().Get(\"container_name\"), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(sttyPartialOutput)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty with -t\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"exec\", \"-t\", data.Labels().Get(\"container_name\"), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(sttyPartialOutput)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty with -i\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"exec\", \"-i\", data.Labels().Get(\"container_name\"), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty without params\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"exec\", data.Labels().Get(\"container_name\"), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_exec_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestExec(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"exec\", data.Identifier(), \"echo\", \"success\")\n\t\t},\n\t\tExpected: test.Expects(0, nil, expect.Equals(\"success\\n\")),\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestExecWithDoubleDash(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(nerdtest.Docker),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"exec\", data.Identifier(), \"--\", \"echo\", \"success\")\n\t\t},\n\t\tExpected: test.Expects(0, nil, expect.Equals(\"success\\n\")),\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestExecStdin(t *testing.T) {\n\tnerdtest.Setup()\n\n\tconst testStr = \"test-exec-stdin\"\n\ttestCase := &test.Case{\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tcmd := helpers.Command(\"exec\", \"-i\", data.Identifier(), \"cat\")\n\t\t\tcmd.Feed(strings.NewReader(testStr))\n\t\t\treturn cmd\n\t\t},\n\t\tExpected: test.Expects(0, nil, expect.Equals(testStr)),\n\t}\n\ttestCase.Run(t)\n}\n\n// FYI: https://github.com/containerd/nerdctl/blob/e4b2b6da56555dc29ed66d0fd8e7094ff2bc002d/cmd/nerdctl/run_test.go#L177\nfunc TestExecEnv(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tEnv: map[string]string{\n\t\t\t\"CORGE\":  \"corge-value-in-host\",\n\t\t\t\"GARPLY\": \"garply-value-in-host\",\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"exec\",\n\t\t\t\t\"--env\", \"FOO=foo1,foo2\",\n\t\t\t\t\"--env\", \"BAR=bar1 bar2\",\n\t\t\t\t\"--env\", \"BAZ=\",\n\t\t\t\t\"--env\", \"QUX\", // not exported in OS\n\t\t\t\t\"--env\", \"QUUX=quux1\",\n\t\t\t\t\"--env\", \"QUUX=quux2\",\n\t\t\t\t\"--env\", \"CORGE\", // OS exported\n\t\t\t\t\"--env\", \"GRAULT=grault_key=grault_value\", // value contains `=` char\n\t\t\t\t\"--env\", \"GARPLY=\", // OS exported\n\t\t\t\t\"--env\", \"WALDO=\", // not exported in OS\n\n\t\t\t\tdata.Identifier(), \"env\")\n\t\t},\n\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nFOO=foo1,foo2\\n\"), \"got bad FOO\")\n\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nBAR=bar1 bar2\\n\"), \"got bad BAR\")\n\t\t\tif runtime.GOOS != \"windows\" {\n\t\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nBAZ=\\n\"), \"got bad BAZ\")\n\t\t\t}\n\t\t\tassert.Assert(t, !strings.Contains(stdout, \"QUX\"), \"got bad QUX (should not be set)\")\n\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nQUUX=quux2\\n\"), \"got bad QUUX\")\n\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nCORGE=corge-value-in-host\\n\"), \"got bad CORGE\")\n\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nGRAULT=grault_key=grault_value\\n\"), \"got bad GRAULT\")\n\t\t\tif runtime.GOOS != \"windows\" {\n\t\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nGARPLY=\\n\"), \"got bad GARPLY\")\n\t\t\t\tassert.Assert(t, strings.Contains(stdout, \"\\nWALDO=\\n\"), \"got bad WALDO\")\n\t\t\t}\n\t\t}),\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_export.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/mattn/go-isatty\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc ExportCommand() *cobra.Command {\n\tvar exportCommand = &cobra.Command{\n\t\tUse:               \"export [OPTIONS] CONTAINER\",\n\t\tArgs:              cobra.ExactArgs(1),\n\t\tShort:             \"Export a containers filesystem as a tar archive\",\n\t\tLong:              \"Export a containers filesystem as a tar archive\",\n\t\tRunE:              exportAction,\n\t\tValidArgsFunction: exportShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\texportCommand.Flags().StringP(\"output\", \"o\", \"\", \"Write to a file, instead of STDOUT\")\n\n\treturn exportCommand\n}\n\nfunc exportAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(args) == 0 {\n\t\treturn fmt.Errorf(\"requires at least 1 argument\")\n\t}\n\n\toutput, err := cmd.Flags().GetString(\"output\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\twriter := cmd.OutOrStdout()\n\tif output != \"\" {\n\t\tf, err := os.OpenFile(output, os.O_CREATE|os.O_WRONLY, 0644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f.Close()\n\t\twriter = f\n\t} else {\n\t\tif isatty.IsTerminal(os.Stdout.Fd()) {\n\t\t\treturn fmt.Errorf(\"cowardly refusing to save to a terminal. Use the -o flag or redirect\")\n\t\t}\n\t}\n\n\toptions := types.ContainerExportOptions{\n\t\tStdout:   writer,\n\t\tGOptions: globalOptions,\n\t}\n\n\treturn container.Export(ctx, client, args[0], options)\n}\n\nfunc exportShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show container names\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_export_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"archive/tar\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// validateExportedTar checks that the tar file exists and contains /bin/busybox\nfunc validateExportedTar(outFile string) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\t// Check if the tar file was created\n\t\t_, err := os.Stat(outFile)\n\t\tassert.Assert(t, !os.IsNotExist(err), \"exported tar file %s was not created\", outFile)\n\n\t\t// Open and read the tar file to check for /bin/busybox\n\t\tfile, err := os.Open(outFile)\n\t\tassert.NilError(t, err, \"failed to open tar file %s\", outFile)\n\t\tdefer file.Close()\n\n\t\ttarReader := tar.NewReader(file)\n\t\tbusyboxFound := false\n\n\t\tfor {\n\t\t\theader, err := tarReader.Next()\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tassert.NilError(t, err, \"failed to read tar entry\")\n\n\t\t\tif header.Name == \"bin/busybox\" || header.Name == \"./bin/busybox\" {\n\t\t\t\tbusyboxFound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.Assert(t, busyboxFound, \"exported tar file %s does not contain /bin/busybox\", outFile)\n\t\tt.Log(\"Export validation passed: tar file exists and contains /bin/busybox\")\n\t}\n}\n\nfunc TestExportStoppedContainer(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"export is not supported on Windows\")\n\t}\n\n\ttestCase := nerdtest.Setup()\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tidentifier := data.Identifier(\"container\")\n\t\thelpers.Ensure(\"create\", \"--name\", identifier, testutil.CommonImage)\n\t\tdata.Labels().Set(\"cID\", identifier)\n\t\tdata.Labels().Set(\"outFile\", filepath.Join(os.TempDir(), identifier+\".tar\"))\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", data.Labels().Get(\"cID\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"cID\"))\n\t\tos.Remove(data.Labels().Get(\"outFile\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"export command succeeds\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"export\", \"-o\", data.Labels().Get(\"outFile\"), data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"tar file exists and has content\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Use a simple command that always succeeds to trigger the validation\n\t\t\t\treturn helpers.Custom(\"echo\", \"validating tar file\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   validateExportedTar(data.Labels().Get(\"outFile\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestExportRunningContainer(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"export is not supported on Windows\")\n\t}\n\n\ttestCase := nerdtest.Setup()\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tidentifier := data.Identifier(\"container\")\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", identifier, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tdata.Labels().Set(\"cID\", identifier)\n\t\tdata.Labels().Set(\"outFile\", filepath.Join(os.TempDir(), identifier+\".tar\"))\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"cID\"))\n\t\tos.Remove(data.Labels().Get(\"outFile\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"export command succeeds\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"export\", \"-o\", data.Labels().Get(\"outFile\"), data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"tar file exists and has content\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Use a simple command that always succeeds to trigger the validation\n\t\t\t\treturn helpers.Custom(\"echo\", \"validating tar file\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   validateExportedTar(data.Labels().Get(\"outFile\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestExportNonexistentContainer(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"export is not supported on Windows\")\n\t}\n\n\ttestCase := nerdtest.Setup()\n\ttestCase.Command = test.Command(\"export\", \"nonexistent-container\")\n\ttestCase.Expected = test.Expects(1, nil, nil)\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_health_check.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// HealthCheckCommand returns a cobra command for `nerdctl container healthcheck`\nfunc HealthCheckCommand() *cobra.Command {\n\tvar healthCheckCommand = &cobra.Command{\n\t\tUse:               \"healthcheck [flags] CONTAINER\",\n\t\tShort:             \"Execute the health check command in a container\",\n\t\tArgs:              cobra.ExactArgs(1),\n\t\tRunE:              healthCheckAction,\n\t\tValidArgsFunction: healthCheckShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\treturn healthCheckCommand\n}\n\nfunc healthCheckAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tcontainerID := args[0]\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\treturn container.HealthCheck(ctx, client, found.Container)\n\t\t},\n\t}\n\n\tn, err := walker.Walk(ctx, containerID)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", containerID)\n\t}\n\treturn nil\n}\n\nfunc healthCheckShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ContainerNames(cmd, func(status containerd.ProcessStatus) bool {\n\t\treturn status == containerd.Running\n\t})\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_health_check_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestContainerHealthCheckBasic(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Docker CLI does not provide a standalone healthcheck command.\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\t// Skip systemd tests in rootless environment to bypass dbus permission issues\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"systemd healthcheck tests are skipped in rootless environment\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Container does not exist\",\n\t\t\tCommand:     test.Command(\"container\", \"healthcheck\", \"non-existent\"),\n\t\t\tExpected:    test.Expects(1, []error{errors.New(\"no such container non-existent\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Missing health check config\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"container has no health check configured\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Basic health check success\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"45s\",\n\t\t\t\t\t\"--health-timeout\", \"30s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state to be present\")\n\t\t\t\t\t\tassert.Equal(t, healthcheck.Healthy, h.Status)\n\t\t\t\t\t\tassert.Equal(t, 0, h.FailingStreak)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) > 0, \"expected at least one health check log entry\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check on stopped container\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"3s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"2\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t\thelpers.Ensure(\"stop\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"container is not running (status: stopped)\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check without task\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"failed to get container task: no running task found\")}, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestContainerHealthCheckDefaults(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Docker CLI does not provide a standalone healthcheck command.\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\t// Skip systemd tests in rootless environment to bypass dbus permission issues\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"systemd healthcheck tests are skipped in rootless environment\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Health check applies default values when not explicitly set\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create container with only --health-cmd, no other health flags\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\n\t\t\t\t\t\t// Parse the healthcheck config from container labels\n\t\t\t\t\t\thcLabel := inspect.Config.Labels[\"nerdctl/healthcheck\"]\n\t\t\t\t\t\tassert.Assert(t, hcLabel != \"\", \"expected healthcheck label to be present\")\n\n\t\t\t\t\t\tvar hc healthcheck.Healthcheck\n\t\t\t\t\t\terr := json.Unmarshal([]byte(hcLabel), &hc)\n\t\t\t\t\t\tassert.NilError(t, err, \"failed to parse healthcheck config\")\n\n\t\t\t\t\t\t// Verify default values are applied\n\t\t\t\t\t\tassert.Equal(t, hc.Interval, 30*time.Second, \"expected default interval of 30s\")\n\t\t\t\t\t\tassert.Equal(t, hc.Timeout, 30*time.Second, \"expected default timeout of 30s\")\n\t\t\t\t\t\tassert.Equal(t, hc.Retries, 3, \"expected default retries of 3\")\n\t\t\t\t\t\tassert.Equal(t, hc.StartPeriod, 0*time.Second, \"expected default start period of 0s\")\n\n\t\t\t\t\t\t// Verify the command was set correctly\n\t\t\t\t\t\tassert.DeepEqual(t, hc.Test, []string{\"CMD-SHELL\", \"echo healthy\"})\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"CLI flags override default values correctly\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create container with custom health flags that override defaults\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo custom\",\n\t\t\t\t\t\"--health-interval\", \"45s\",\n\t\t\t\t\t\"--health-timeout\", \"15s\",\n\t\t\t\t\t\"--health-retries\", \"5\",\n\t\t\t\t\t\"--health-start-period\", \"10s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\n\t\t\t\t\t\t// Parse the healthcheck config from container labels\n\t\t\t\t\t\thcLabel := inspect.Config.Labels[\"nerdctl/healthcheck\"]\n\t\t\t\t\t\tassert.Assert(t, hcLabel != \"\", \"expected healthcheck label to be present\")\n\n\t\t\t\t\t\tvar hc healthcheck.Healthcheck\n\t\t\t\t\t\terr := json.Unmarshal([]byte(hcLabel), &hc)\n\t\t\t\t\t\tassert.NilError(t, err, \"failed to parse healthcheck config\")\n\n\t\t\t\t\t\t// Verify CLI overrides are applied (not defaults)\n\t\t\t\t\t\tassert.Equal(t, hc.Interval, 45*time.Second, \"expected custom interval of 45s\")\n\t\t\t\t\t\tassert.Equal(t, hc.Timeout, 15*time.Second, \"expected custom timeout of 15s\")\n\t\t\t\t\t\tassert.Equal(t, hc.Retries, 5, \"expected custom retries of 5\")\n\t\t\t\t\t\tassert.Equal(t, hc.StartPeriod, 10*time.Second, \"expected custom start period of 10s\")\n\n\t\t\t\t\t\t// Verify the command was set correctly\n\t\t\t\t\t\tassert.DeepEqual(t, hc.Test, []string{\"CMD-SHELL\", \"echo custom\"})\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"No defaults applied when no healthcheck is configured\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create container without any health flags\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\n\t\t\t\t\t\t// Verify no healthcheck label is present\n\t\t\t\t\t\thcLabel := inspect.Config.Labels[\"nerdctl/healthcheck\"]\n\t\t\t\t\t\tassert.Equal(t, hcLabel, \"\", \"expected no healthcheck label when no healthcheck is configured\")\n\n\t\t\t\t\t\t// Verify no health state\n\t\t\t\t\t\tassert.Assert(t, inspect.State.Health == nil, \"expected no health state when no healthcheck is configured\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestContainerHealthCheckAdvance(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Docker CLI does not provide a standalone healthcheck command.\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\t// Skip systemd tests in rootless environment to bypass dbus permission issues\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"systemd healthcheck tests are skipped in rootless environment\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Health check timeout scenario\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"sleep 10\",\n\t\t\t\t\t\"--health-timeout\", \"2s\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Assert(t, h.FailingStreak >= 1, \"expected at least one failing streak\")\n\t\t\t\t\t\tassert.Assert(t, len(inspect.State.Health.Log) > 0, \"expected health log to have entries\")\n\t\t\t\t\t\tlast := inspect.State.Health.Log[0]\n\t\t\t\t\t\tassert.Equal(t, -1, last.ExitCode)\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check failing streak behavior\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"exit 1\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\t\"--health-retries\", \"2\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Run healthcheck twice to ensure failing streak\n\t\t\t\tfor i := 0; i < 2; i++ {\n\t\t\t\t\thelpers.Ensure(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t\t}\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Unhealthy)\n\t\t\t\t\t\tassert.Assert(t, h.FailingStreak >= 1, \"expected atleast one FailingStreak\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check with start period\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"exit 1\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\t\"--health-start-period\", \"60s\",\n\t\t\t\t\t\"--health-retries\", \"2\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Starting)\n\t\t\t\t\t\tassert.Equal(t, h.FailingStreak, 0)\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check with invalid command\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"not-a-real-cmd\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\t\"--health-retries\", \"1\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Unhealthy)\n\t\t\t\t\t\tassert.Assert(t, h.FailingStreak >= 1, \"expected at least one failing streak\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"No healthcheck flag disables health status\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--no-healthcheck\", testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\tassert.Assert(t, inspect.State.Health == nil, \"expected health to be nil with --no-healthcheck\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Healthcheck using CMD-SHELL format\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo shell-format\", \"--health-interval\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(_ string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Healthy)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) > 0)\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(h.Log[0].Output, \"shell-format\"))\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check uses container environment variables\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--env\", \"MYVAR=test-value\",\n\t\t\t\t\t\"--health-cmd\", \"echo $MYVAR\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\t\"--health-timeout\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Healthy)\n\t\t\t\t\t\tassert.Assert(t, h.FailingStreak == 0)\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(h.Log[0].Output, \"test\"), \"expected health log output to contain 'test'\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health check respects container WorkingDir\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--workdir\", \"/tmp\",\n\t\t\t\t\t\"--health-cmd\", \"pwd\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\t\"--health-timeout\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Healthy)\n\t\t\t\t\t\tassert.Equal(t, h.FailingStreak, 0)\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(h.Log[0].Output, \"/tmp\"), \"expected health log output to contain '/tmp'\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Healthcheck emits large output repeatedly\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"yes X | head -c 60000\",\n\t\t\t\t\t\"--health-interval\", \"1s\", \"--health-timeout\", \"2s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\t\thelpers.Ensure(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t\t}\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(_ string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Healthy)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) >= 3, \"expected at least 3 health log entries\")\n\t\t\t\t\t\tfor _, log := range h.Log {\n\t\t\t\t\t\t\tassert.Assert(t, len(log.Output) >= 1024, fmt.Sprintf(\"each output should be >= 1024 bytes, was: %s\", log.Output))\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health log in inspect keeps only the latest 5 entries\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"exit 1\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\t\"--health-retries\", \"1\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tfor i := 0; i < 7; i++ {\n\t\t\t\t\thelpers.Ensure(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t}\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(_ string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Unhealthy)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) <= 5, \"expected health log to contain at most 5 entries\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Healthcheck with large output gets truncated in health log\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"yes X | head -c 1048576\", // 1MB output\n\t\t\t\t\t\"--health-interval\", \"1s\", \"--health-timeout\", \"2s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(_ string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Healthy)\n\t\t\t\t\t\tassert.Equal(t, h.FailingStreak, 0)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) >= 1, \"expected at least one log entry\")\n\t\t\t\t\t\toutput := h.Log[0].Output\n\t\t\t\t\t\tassert.Assert(t, strings.HasSuffix(output, \"[truncated]\"), \"expected output to be truncated with '[truncated]'\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Health status transitions from healthy to unhealthy after retries\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcontainerName := data.Identifier()\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", containerName,\n\t\t\t\t\t\"--health-cmd\", \"exit 1\",\n\t\t\t\t\t\"--health-timeout\", \"10s\",\n\t\t\t\t\t\"--health-retries\", \"3\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tfor i := 0; i < 4; i++ {\n\t\t\t\t\thelpers.Ensure(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t\t}\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Unhealthy)\n\t\t\t\t\t\tassert.Assert(t, h.FailingStreak >= 3)\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Failed healthchecks in start-period do not change status\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"ls /foo || exit 1\", \"--health-retries\", \"2\",\n\t\t\t\t\t\"--health-start-period\", \"30s\", // long enough to stay in \"starting\"\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Run healthcheck 3 times (should still be in start period)\n\t\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\t\thelpers.Ensure(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t}\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Starting)\n\t\t\t\t\t\tassert.Equal(t, h.FailingStreak, 0, \"failing streak should not increase during start period\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Successful healthcheck in start-period sets status to healthy\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"ls || exit 1\", \"--health-retries\", \"2\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\thelpers.Ensure(\"container\", \"healthcheck\", data.Identifier())\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tdebug, _ := json.MarshalIndent(h, \"\", \"  \")\n\t\t\t\t\t\tt.Log(string(debug))\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state\")\n\t\t\t\t\t\tassert.Equal(t, h.Status, healthcheck.Healthy, \"expected healthy status even during start-period\")\n\t\t\t\t\t\tassert.Equal(t, h.FailingStreak, 0)\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestHealthCheck_SystemdIntegration_Basic(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\t// Skip systemd tests in rootless environment to bypass dbus permission issues\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"systemd healthcheck tests are skipped in rootless environment\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Basic healthy container with systemd-triggered healthcheck\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"2s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"30\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Ensure proper cleanup of systemd units\n\t\t\t\thelpers.Anyhow(\"stop\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar h *healthcheck.Health\n\n\t\t\t\t\t\t// Poll up to 5 times for health status\n\t\t\t\t\t\tmaxAttempts := 5\n\t\t\t\t\t\tvar finalStatus string\n\n\t\t\t\t\t\tfor i := 0; i < maxAttempts; i++ {\n\t\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\t\th = inspect.State.Health\n\n\t\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state to be present\")\n\t\t\t\t\t\t\tfinalStatus = h.Status\n\n\t\t\t\t\t\t\t// If healthy, break and pass the test\n\t\t\t\t\t\t\tif finalStatus == \"healthy\" {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Container became healthy on attempt %d/%d\", i+1, maxAttempts))\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// If unhealthy, fail immediately\n\t\t\t\t\t\t\tif finalStatus == \"unhealthy\" {\n\t\t\t\t\t\t\t\tassert.Assert(t, false, fmt.Sprintf(\"Container became unhealthy on attempt %d/%d, status: %s\", i+1, maxAttempts, finalStatus))\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// If not the last attempt, wait before retrying\n\t\t\t\t\t\t\tif i < maxAttempts-1 {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Attempt %d/%d: status is '%s', waiting 1 second before retry\", i+1, maxAttempts, finalStatus))\n\t\t\t\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif finalStatus != \"healthy\" {\n\t\t\t\t\t\t\tassert.Assert(t, false, fmt.Sprintf(\"Container did not become healthy after %d attempts, final status: %s\", maxAttempts, finalStatus))\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) > 0, \"expected at least one health check log entry\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Kill stops healthcheck execution and cleans up systemd timer\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"30\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t\thelpers.Ensure(\"kill\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Container is already killed, just remove it\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t// Get container info for verification\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\tcontainerID := inspect.ID\n\t\t\t\t\t\th := inspect.State.Health\n\n\t\t\t\t\t\t// Verify health state and logs exist\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state to be present\")\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) > 0, \"expected at least one health check log entry\")\n\n\t\t\t\t\t\t// Ensure systemd timers are removed\n\t\t\t\t\t\tresult := helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\t\t\tOutput: func(stdout string, _ tig.T) {\n\t\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\t\t\"expected nerdctl healthcheck timer for container ID %s to be removed after container stop\", containerID)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove cleans up systemd timer\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"30\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Container is already removed, no cleanup needed\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\tcontainerID := inspect.ID\n\n\t\t\t\t\t\t// Check systemd timers to ensure cleanup\n\t\t\t\t\t\tresult := helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\t\t\tOutput: func(stdout string, _ tig.T) {\n\t\t\t\t\t\t\t\t// Verify systemd timer has been cleaned up by checking systemctl output\n\t\t\t\t\t\t\t\t// We check that no timer contains our test identifier\n\t\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\t\t\"expected nerdctl healthcheck timer for container ID %s to be removed after container removal\", containerID)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Stop cleans up systemd timer\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"30\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t\thelpers.Ensure(\"stop\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Container is already stopped, just remove it\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t// Get container info for verification\n\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\tcontainerID := inspect.ID\n\n\t\t\t\t\t\t// Ensure systemd timers are removed\n\t\t\t\t\t\tresult := helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\t\t\tOutput: func(stdout string, _ tig.T) {\n\t\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\t\t\"expected nerdctl healthcheck timer for container ID %s to be removed after container stop\", containerID)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestHealthCheck_GlobalFlags(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\t// Skip systemd tests in rootless environment to bypass dbus permission issues\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"systemd healthcheck tests are skipped in rootless environment\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Healthcheck works with custom namespace flag\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create container in custom namespace with healthcheck\n\t\t\t\thelpers.Ensure(\"--namespace=healthcheck-test\", \"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"2s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"30\")\n\t\t\t\t// Wait a bit to ensure container is running (can't use EnsureContainerStarted with custom namespace)\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"--namespace=healthcheck-test\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Wait a bit for healthcheck to run\n\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t\t// Verify container is accessible in the custom namespace\n\t\t\t\treturn helpers.Command(\"--namespace=healthcheck-test\", \"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar inspectResults []dockercompat.Container\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &inspectResults)\n\t\t\t\t\t\tassert.NilError(t, err, \"failed to parse inspect output\")\n\t\t\t\t\t\tassert.Assert(t, len(inspectResults) > 0, \"expected at least one container in inspect results\")\n\n\t\t\t\t\t\tinspect := inspectResults[0]\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state to be present\")\n\t\t\t\t\t\tassert.Assert(t, h.Status == healthcheck.Healthy || h.Status == healthcheck.Starting,\n\t\t\t\t\t\t\t\"expected health status to be healthy or starting, got: %s\", h.Status)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) > 0, \"expected at least one health check log entry\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Healthcheck works correctly with namespace after container restart\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create container in custom namespace\n\t\t\t\thelpers.Ensure(\"--namespace=restart-test\", \"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"2s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"60\")\n\t\t\t\t// Wait a bit to ensure container is running (can't use EnsureContainerStarted with custom namespace)\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"--namespace=restart-test\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Wait for initial healthcheck\n\t\t\t\ttime.Sleep(3 * time.Second)\n\n\t\t\t\t// Stop and restart the container\n\t\t\t\thelpers.Ensure(\"--namespace=restart-test\", \"stop\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"--namespace=restart-test\", \"start\", data.Identifier())\n\t\t\t\t// Wait a bit to ensure container is running after restart\n\t\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\t\t// Wait for healthcheck to run after restart\n\t\t\t\ttime.Sleep(3 * time.Second)\n\n\t\t\t\treturn helpers.Command(\"--namespace=restart-test\", \"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t// Parse the inspect JSON output directly since we're in a custom namespace\n\t\t\t\t\t\tvar inspectResults []dockercompat.Container\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &inspectResults)\n\t\t\t\t\t\tassert.NilError(t, err, \"failed to parse inspect output\")\n\t\t\t\t\t\tassert.Assert(t, len(inspectResults) > 0, \"expected at least one container in inspect results\")\n\n\t\t\t\t\t\tinspect := inspectResults[0]\n\t\t\t\t\t\th := inspect.State.Health\n\t\t\t\t\t\tassert.Assert(t, h != nil, \"expected health state after restart\")\n\t\t\t\t\t\tassert.Assert(t, h.Status == healthcheck.Healthy || h.Status == healthcheck.Starting,\n\t\t\t\t\t\t\t\"expected health status to be healthy or starting after restart, got: %s\", h.Status)\n\t\t\t\t\t\tassert.Assert(t, len(h.Log) > 0, \"expected health check logs after restart\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestHealthCheck_SystemdIntegration_Advanced(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\t// Skip systemd tests in rootless environment to bypass dbus permission issues\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"systemd healthcheck tests are skipped in rootless environment\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\t// Tests that CreateTimer() successfully creates systemd timer units and\n\t\t\t// RemoveTransientHealthCheckFiles() properly cleans up units when container stops.\n\t\t\tDescription: \"Systemd timer unit creation and cleanup\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo healthy\",\n\t\t\t\t\t\"--health-interval\", \"1s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"30\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\t// Get container ID and check systemd timer\n\t\t\t\t\t\tcontainerInspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\tcontainerID := containerInspect.ID\n\n\t\t\t\t\t\t// Check systemd timer\n\t\t\t\t\t\tresult := helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\t\t\tOutput: func(stdout string, _ tig.T) {\n\t\t\t\t\t\t\t\t// Verify that a timer exists for this specific container\n\t\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\t\t\"expected to find nerdctl healthcheck timer containing container ID: %s\", containerID)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\t// Stop container and verify cleanup\n\t\t\t\t\t\thelpers.Ensure(\"stop\", data.Identifier())\n\n\t\t\t\t\t\t// Check that timer is gone\n\t\t\t\t\t\tresult = helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\t\t\tOutput: func(stdout string, _ tig.T) {\n\t\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\t\t\"expected nerdctl healthcheck timer for container ID %s to be removed after container stop\", containerID)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Container restart recreates systemd timer\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--health-cmd\", \"echo restart-test\",\n\t\t\t\t\t\"--health-interval\", \"2s\",\n\t\t\t\t\ttestutil.CommonImage, \"sleep\", \"60\")\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Get container ID for verification\n\t\t\t\tcontainerInspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\tcontainerID := containerInspect.ID\n\n\t\t\t\t// Step 1: Verify timer exists initially\n\t\t\t\tresult := helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\"expected timer for container %s to exist initially\", containerID)\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\t// Step 2: Stop container\n\t\t\t\thelpers.Ensure(\"stop\", data.Identifier())\n\n\t\t\t\t// Step 3: Verify timer is removed after stop\n\t\t\t\tresult = helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t\tresult.Run(&test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\"expected timer for container %s to be removed after stop\", containerID)\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\t// Step 4: Restart container\n\t\t\t\thelpers.Ensure(\"start\", data.Identifier())\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\n\t\t\t\t// Step 5: Verify timer is recreated after restart - this is our final verification\n\t\t\t\treturn helpers.Custom(\"systemctl\", \"list-timers\", \"--all\", \"--no-pager\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tcontainerInspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\tcontainerID := containerInspect.ID\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, containerID),\n\t\t\t\t\t\t\t\"expected timer for container %s to be recreated after restart\", containerID)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"inspect [flags] CONTAINER [CONTAINER, ...]\",\n\t\tShort:             \"Display detailed information on one or more containers.\",\n\t\tLong:              \"Hint: set `--mode=native` for showing the full output\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              inspectAction,\n\t\tValidArgsFunction: containerInspectShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"mode\", \"dockercompat\", `Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output`)\n\tcmd.Flags().BoolP(\"size\", \"s\", false, \"Display total file sizes\")\n\n\tcmd.RegisterFlagCompletionFunc(\"mode\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"dockercompat\", \"native\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nvar validModeType = map[string]bool{\n\t\"native\":       true,\n\t\"dockercompat\": true,\n}\n\nfunc InspectOptions(cmd *cobra.Command) (opt types.ContainerInspectOptions, err error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn\n\t}\n\tmode, err := cmd.Flags().GetString(\"mode\")\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(mode) > 0 && !validModeType[mode] {\n\t\terr = fmt.Errorf(\"%q is not a valid value for --mode\", mode)\n\t\treturn\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tsize, err := cmd.Flags().GetBool(\"size\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn types.ContainerInspectOptions{\n\t\tGOptions: globalOptions,\n\t\tFormat:   format,\n\t\tMode:     mode,\n\t\tSize:     size,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\topt, err := InspectOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), opt.GOptions.Namespace, opt.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tentries, err := container.Inspect(ctx, client, args, opt)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Display\n\tif len(entries) > 0 {\n\t\tif formatErr := formatter.FormatSlice(opt.Format, opt.Stdout, entries); formatErr != nil {\n\t\t\tlog.G(ctx).Error(formatErr)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc containerInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show container names\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_inspect_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/docker/go-connections/nat\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/continuity/testutil/loopback\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestContainerInspectContainsPortConfig(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer, \"-p\", \"8080:80\", testutil.NginxAlpineImage).AssertOK()\n\tinspect := base.InspectContainer(testContainer)\n\tinspect80TCP := (*inspect.NetworkSettings.Ports)[\"80/tcp\"]\n\texpected := nat.PortBinding{\n\t\tHostIP:   \"0.0.0.0\",\n\t\tHostPort: \"8080\",\n\t}\n\tassert.Equal(base.T, expected, inspect80TCP[0])\n}\n\nfunc TestContainerInspectContainsMounts(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\n\ttestVolume := testutil.Identifier(t)\n\n\tdefer base.Cmd(\"volume\", \"rm\", \"-f\", testVolume).Run()\n\tbase.Cmd(\"volume\", \"create\", \"--label\", \"tag=testVolume\", testVolume).AssertOK()\n\tinspectVolume := base.InspectVolume(testVolume)\n\tnamedVolumeSource := inspectVolume.Mountpoint\n\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\tbase.Cmd(\"run\", \"-d\", \"--privileged\",\n\t\t\"--name\", testContainer,\n\t\t\"--network\", \"none\",\n\t\t\"-v\", \"/anony-vol\",\n\t\t\"--tmpfs\", \"/app1:size=64m\",\n\t\t\"--mount\", \"type=bind,src=/tmp,dst=/app2,ro\",\n\t\t\"--mount\", fmt.Sprintf(\"type=volume,src=%s,dst=/app3,readonly=false\", testVolume),\n\t\ttestutil.NginxAlpineImage).AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\t// convert array to map to get by key of Destination\n\tactual := make(map[string]dockercompat.MountPoint)\n\tfor i := range inspect.Mounts {\n\t\tactual[inspect.Mounts[i].Destination] = inspect.Mounts[i]\n\t}\n\tt.Logf(\"actual in TestContainerInspectContainsMounts: %+v\", actual)\n\tconst localDriver = \"local\"\n\n\texpected := []struct {\n\t\tdest       string\n\t\tmountPoint dockercompat.MountPoint\n\t}{\n\t\t// anonymous volume\n\t\t{\n\t\t\tdest: \"/anony-vol\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"volume\",\n\t\t\t\tName:        \"\",\n\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\tDestination: \"/anony-vol\",\n\t\t\t\tDriver:      localDriver,\n\t\t\t\tRW:          true,\n\t\t\t},\n\t\t},\n\n\t\t// bind\n\t\t{\n\t\t\tdest: \"/app2\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"bind\",\n\t\t\t\tName:        \"\",\n\t\t\t\tSource:      \"/tmp\",\n\t\t\t\tDestination: \"/app2\",\n\t\t\t\tDriver:      \"\",\n\t\t\t\tRW:          false,\n\t\t\t},\n\t\t},\n\n\t\t// named volume\n\t\t{\n\t\t\tdest: \"/app3\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"volume\",\n\t\t\t\tName:        testVolume,\n\t\t\t\tSource:      namedVolumeSource,\n\t\t\t\tDestination: \"/app3\",\n\t\t\t\tDriver:      localDriver,\n\t\t\t\tRW:          true,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i := range expected {\n\t\ttestCase := expected[i]\n\t\tt.Logf(\"test volume[dest=%q]\", testCase.dest)\n\n\t\tmountPoint, ok := actual[testCase.dest]\n\t\tassert.Assert(base.T, ok)\n\n\t\tassert.Equal(base.T, testCase.mountPoint.Type, mountPoint.Type)\n\t\tassert.Equal(base.T, testCase.mountPoint.Driver, mountPoint.Driver)\n\t\tassert.Equal(base.T, testCase.mountPoint.RW, mountPoint.RW)\n\t\tassert.Equal(base.T, testCase.mountPoint.Destination, mountPoint.Destination)\n\n\t\tif testCase.mountPoint.Source != \"\" {\n\t\t\tassert.Equal(base.T, testCase.mountPoint.Source, mountPoint.Source)\n\t\t}\n\t\tif testCase.mountPoint.Name != \"\" {\n\t\t\tassert.Equal(base.T, testCase.mountPoint.Name, mountPoint.Name)\n\t\t}\n\t}\n}\n\nfunc TestContainerInspectContainsLabel(t *testing.T) {\n\tt.Parallel()\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer, \"--label\", \"foo=foo\", \"--label\", \"bar=bar\", testutil.NginxAlpineImage).AssertOK()\n\tbase.EnsureContainerStarted(testContainer)\n\tinspect := base.InspectContainer(testContainer)\n\tlbs := inspect.Config.Labels\n\n\tassert.Equal(base.T, \"foo\", lbs[\"foo\"])\n\tassert.Equal(base.T, \"bar\", lbs[\"bar\"])\n}\n\nfunc TestContainerInspectContainsInternalLabel(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\tt.Parallel()\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer, \"--mount\", \"type=bind,src=/tmp,dst=/app,readonly=false,bind-propagation=rprivate\", testutil.NginxAlpineImage).AssertOK()\n\tbase.EnsureContainerStarted(testContainer)\n\tinspect := base.InspectContainer(testContainer)\n\tlbs := inspect.Config.Labels\n\n\t// TODO: add more internal labels testcases\n\tlabelMount := lbs[labels.Mounts]\n\texpectedLabelMount := \"[{\\\"Type\\\":\\\"bind\\\",\\\"Source\\\":\\\"/tmp\\\",\\\"Destination\\\":\\\"/app\\\",\\\"Mode\\\":\\\"rprivate,rbind\\\",\\\"RW\\\":true,\\\"Propagation\\\":\\\"rprivate\\\"}]\"\n\tassert.Equal(base.T, expectedLabelMount, labelMount)\n}\n\nfunc TestContainerInspectConfigImage(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"Container inspect contains Config.Image field\",\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"inspect\", data.Identifier())\n\t\t},\n\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\tvar containers []dockercompat.Container\n\t\t\terr := json.Unmarshal([]byte(stdout), &containers)\n\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\tassert.Equal(t, 1, len(containers), \"Expected exactly one container in inspect output\")\n\n\t\t\tcontainer := containers[0]\n\t\t\tassert.Assert(t, container.Config != nil, \"container Config should not be nil\")\n\t\t\tassert.Assert(t, container.Config.Image != \"\", \"Config.Image should not be empty\")\n\t\t}),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestContainerInspectState(t *testing.T) {\n\tt.Parallel()\n\ttestContainer := testutil.Identifier(t)\n\tbase := testutil.NewBase(t)\n\n\ttype testCase struct {\n\t\tname, containerName, cmd string\n\t\twant                     dockercompat.ContainerState\n\t}\n\t// nerdctl: run error produces a nil Task, so the Status is empty because Status comes from Task.\n\t// docker : run error gives => `Status=created` as  in docker there is no a separation between container and Task.\n\terrStatus := \"\"\n\tif nerdtest.IsDocker() {\n\t\terrStatus = \"created\"\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:          \"inspect State with error\",\n\t\t\tcontainerName: fmt.Sprintf(\"%s-fail\", testContainer),\n\t\t\tcmd:           \"aa\",\n\t\t\twant: dockercompat.ContainerState{\n\t\t\t\tError:  \"executable file not found in $PATH\",\n\t\t\t\tStatus: errStatus,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"inspect State without error\",\n\t\t\tcontainerName: fmt.Sprintf(\"%s-success\", testContainer),\n\t\t\tcmd:           \"ls\",\n\t\t\twant: dockercompat.ContainerState{\n\t\t\t\tError:  \"\",\n\t\t\t\tStatus: \"exited\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tdefer base.Cmd(\"rm\", \"-f\", tc.containerName).Run()\n\t\t\tif tc.want.Error != \"\" {\n\t\t\t\tbase.Cmd(\"run\", \"--name\", tc.containerName, testutil.AlpineImage, tc.cmd).AssertFail()\n\t\t\t} else {\n\t\t\t\tbase.Cmd(\"run\", \"--name\", tc.containerName, testutil.AlpineImage, tc.cmd).AssertOK()\n\t\t\t}\n\t\t\tinspect := base.InspectContainer(tc.containerName)\n\t\t\tassert.Assert(t, strings.Contains(inspect.State.Error, tc.want.Error), fmt.Sprintf(\"expected: %s, actual: %s\", tc.want.Error, inspect.State.Error))\n\t\t\tassert.Equal(base.T, inspect.State.Status, tc.want.Status)\n\t\t})\n\t}\n\n}\n\nfunc TestContainerInspectHostConfig(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\tif rootlessutil.IsRootless() && infoutil.CgroupsVersion() == \"1\" {\n\t\tt.Skip(\"test skipped for rootless containers on cgroup v1\")\n\t}\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\t// Run a container with various HostConfig options\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer,\n\t\t\"--cpuset-cpus\", \"0-1\",\n\t\t\"--cpuset-mems\", \"0\",\n\t\t\"--cpu-shares\", \"1024\",\n\t\t\"--cpu-quota\", \"100000\",\n\t\t\"--group-add\", \"1000\",\n\t\t\"--group-add\", \"2000\",\n\t\t\"--add-host\", \"host1:10.0.0.1\",\n\t\t\"--add-host\", \"host2:10.0.0.2\",\n\t\t\"--ipc\", \"host\",\n\t\t\"--memory\", \"512m\",\n\t\t\"--read-only\",\n\t\t\"--shm-size\", \"256m\",\n\t\t\"--uts\", \"host\",\n\t\t\"--runtime\", \"io.containerd.runc.v2\",\n\t\ttestutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\n\tassert.Equal(t, \"0-1\", inspect.HostConfig.CPUSetCPUs)\n\tassert.Equal(t, \"0\", inspect.HostConfig.CPUSetMems)\n\tassert.Equal(t, uint64(1024), inspect.HostConfig.CPUShares)\n\tassert.Equal(t, int64(100000), inspect.HostConfig.CPUQuota)\n\tassert.Assert(t, slices.Contains(inspect.HostConfig.GroupAdd, \"1000\"), \"Expected '1000' to be in GroupAdd\")\n\tassert.Assert(t, slices.Contains(inspect.HostConfig.GroupAdd, \"2000\"), \"Expected '2000' to be in GroupAdd\")\n\texpectedExtraHosts := []string{\"host1:10.0.0.1\", \"host2:10.0.0.2\"}\n\tassert.DeepEqual(t, expectedExtraHosts, inspect.HostConfig.ExtraHosts)\n\tassert.Equal(t, \"host\", inspect.HostConfig.IpcMode)\n\tassert.Equal(t, int64(536870912), inspect.HostConfig.Memory)\n\tassert.Equal(t, int64(1073741824), inspect.HostConfig.MemorySwap)\n\tassert.Equal(t, true, inspect.HostConfig.ReadonlyRootfs)\n\tassert.Equal(t, \"host\", inspect.HostConfig.UTSMode)\n\tassert.Equal(t, int64(268435456), inspect.HostConfig.ShmSize)\n}\n\nfunc TestContainerInspectHostConfigDefaults(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\tvar hc hostConfigValues\n\n\t// Hostconfig default values differ with Docker.\n\t// This is because we directly retrieve the configured values instead of using preset defaults.\n\tif nerdtest.IsDocker() {\n\t\thc.Driver = \"\"\n\t\thc.GroupAddSize = 0\n\t\thc.ShmSize = int64(67108864) // Docker default 64M\n\t\thc.Runtime = \"runc\"\n\t} else {\n\t\thc.GroupAddSize = 10\n\t\thc.Driver = \"json-file\"\n\t\thc.ShmSize = int64(0)\n\t\thc.Runtime = \"io.containerd.runc.v2\"\n\t}\n\n\t// Run a container without specifying HostConfig options\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer, testutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\tt.Logf(\"HostConfig in TestContainerInspectHostConfigDefaults: %+v\", inspect.HostConfig)\n\tassert.Equal(t, \"\", inspect.HostConfig.CPUSetCPUs)\n\tassert.Equal(t, \"\", inspect.HostConfig.CPUSetMems)\n\tassert.Equal(t, uint16(0), inspect.HostConfig.BlkioWeight)\n\tassert.Equal(t, 0, len(inspect.HostConfig.BlkioWeightDevice))\n\tassert.Equal(t, 0, len(inspect.HostConfig.BlkioDeviceReadBps))\n\tassert.Equal(t, 0, len(inspect.HostConfig.BlkioDeviceReadIOps))\n\tassert.Equal(t, 0, len(inspect.HostConfig.BlkioDeviceWriteBps))\n\tassert.Equal(t, 0, len(inspect.HostConfig.BlkioDeviceWriteIOps))\n\tassert.Equal(t, uint64(0), inspect.HostConfig.CPUShares)\n\tassert.Equal(t, int64(0), inspect.HostConfig.CPUQuota)\n\tassert.Equal(t, hc.GroupAddSize, len(inspect.HostConfig.GroupAdd))\n\tassert.Equal(t, 0, len(inspect.HostConfig.ExtraHosts))\n\tassert.Equal(t, \"private\", inspect.HostConfig.IpcMode)\n\tassert.Equal(t, hc.Driver, inspect.HostConfig.LogConfig.Driver)\n\tassert.Equal(t, int64(0), inspect.HostConfig.Memory)\n\tassert.Equal(t, int64(0), inspect.HostConfig.MemorySwap)\n\tassert.Equal(t, bool(false), inspect.HostConfig.OomKillDisable)\n\tassert.Equal(t, bool(false), inspect.HostConfig.ReadonlyRootfs)\n\tassert.Equal(t, \"\", inspect.HostConfig.UTSMode)\n\tassert.Equal(t, hc.ShmSize, inspect.HostConfig.ShmSize)\n\tassert.Equal(t, hc.Runtime, inspect.HostConfig.Runtime)\n\tassert.Equal(t, 0, len(inspect.HostConfig.Devices))\n\t// Sysctls can be empty or contain \"net.ipv4.ip_unprivileged_port_start\" depending on the environment.\n\tgot := len(inspect.HostConfig.Sysctls)\n\tif got != 0 && got != 1 {\n\t\tt.Fatalf(\"unexpected number of Sysctls entries: %d (want 0 or 1)\", got)\n\t}\n}\n\nfunc TestContainerInspectHostConfigDNS(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\t// Run a container with DNS options\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer,\n\t\t\"--dns\", \"8.8.8.8\",\n\t\t\"--dns\", \"1.1.1.1\",\n\t\t\"--dns-search\", \"example.com\",\n\t\t\"--dns-search\", \"test.local\",\n\t\t\"--dns-option\", \"ndots:5\",\n\t\t\"--dns-option\", \"timeout:3\",\n\t\ttestutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\n\t// Check DNS servers\n\texpectedDNSServers := []string{\"8.8.8.8\", \"1.1.1.1\"}\n\tassert.DeepEqual(t, expectedDNSServers, inspect.HostConfig.DNS)\n\n\t// Check DNS search domains\n\texpectedDNSSearch := []string{\"example.com\", \"test.local\"}\n\tassert.DeepEqual(t, expectedDNSSearch, inspect.HostConfig.DNSSearch)\n\n\t// Check DNS options\n\texpectedDNSOptions := []string{\"ndots:5\", \"timeout:3\"}\n\tassert.DeepEqual(t, expectedDNSOptions, inspect.HostConfig.DNSOptions)\n}\n\nfunc TestContainerInspectHostConfigDNSDefaults(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\t// Run a container without specifying DNS options\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer, testutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\n\t// Check that DNS settings are empty by default\n\tassert.Equal(t, 0, len(inspect.HostConfig.DNS))\n\tassert.Equal(t, 0, len(inspect.HostConfig.DNSSearch))\n\tassert.Equal(t, 0, len(inspect.HostConfig.DNSOptions))\n}\n\nfunc TestContainerInspectHostConfigPID(t *testing.T) {\n\ttestContainer1 := testutil.Identifier(t) + \"-container1\"\n\ttestContainer2 := testutil.Identifier(t) + \"-container2\"\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer1, testContainer2).Run()\n\n\t// Run the first container\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer1, testutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tcontainerID1 := strings.TrimSpace(base.Cmd(\"inspect\", \"-f\", \"{{.Id}}\", testContainer1).Out())\n\n\tvar hc hostConfigValues\n\n\tif nerdtest.IsDocker() {\n\t\thc.PidMode = \"container:\" + containerID1\n\t} else {\n\t\thc.PidMode = containerID1\n\t}\n\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer2,\n\t\t\"--pid\", fmt.Sprintf(\"container:%s\", testContainer1),\n\t\ttestutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer2)\n\n\tassert.Equal(t, hc.PidMode, inspect.HostConfig.PidMode)\n\n}\n\nfunc TestContainerInspectHostConfigPIDDefaults(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer, testutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\n\tassert.Equal(t, \"\", inspect.HostConfig.PidMode)\n}\n\nfunc TestContainerInspectDevices(t *testing.T) {\n\ttestContainer := testutil.Identifier(t)\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).Run()\n\n\tif rootlessutil.IsRootless() && infoutil.CgroupsVersion() == \"1\" {\n\t\tt.Skip(\"test skipped for rootless containers on cgroup v1\")\n\t}\n\n\t// Create a temporary directory\n\tdir, err := os.MkdirTemp(t.TempDir(), \"device-dir\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif nerdtest.IsDocker() {\n\t\tdir = \"/dev/zero\"\n\t}\n\n\t// Run the container with the directory mapped as a device\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer,\n\t\t\"--device\", dir+\":/dev/xvda\",\n\t\ttestutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\n\texpectedDevices := []dockercompat.DeviceMapping{\n\t\t{\n\t\t\tPathOnHost:        dir,\n\t\t\tPathInContainer:   \"/dev/xvda\",\n\t\t\tCgroupPermissions: \"rwm\",\n\t\t},\n\t}\n\tassert.DeepEqual(t, expectedDevices, inspect.HostConfig.Devices)\n}\n\nfunc TestContainerInspectBlkioSettings(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\ttestContainer := testutil.Identifier(t)\n\t// Some of the blkio settings are not supported in cgroup v1.\n\t// So skip this test if running on cgroup v1\n\tif infoutil.CgroupsVersion() == \"1\" {\n\t\tt.Skip(\"test skipped for rootless containers or if running with cgroup v1\")\n\t}\n\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"test requires root privilege to create a dummy device\")\n\t}\n\n\t// See https://github.com/containerd/nerdctl/issues/4185\n\t// It is unclear if this is truly a kernel version problem, a runc issue, or a distro (EL9) issue.\n\t// For now, disable the test unless on a recent kernel.\n\ttestutil.RequireKernelVersion(t, \">= 6.0.0-0\")\n\n\tlo, err := loopback.New(4096)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"cannot find a loop device: %w\", err)\n\t\tt.Fatal(err)\n\t}\n\tdefer lo.Close()\n\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainer).AssertOK()\n\n\tconst (\n\t\tweight    = 500\n\t\treadBps   = 1048576\n\t\treadIops  = 1000\n\t\twriteBps  = 2097152\n\t\twriteIops = 2000\n\t)\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainer,\n\t\t\"--blkio-weight\", fmt.Sprintf(\"%d\", weight),\n\t\t\"--blkio-weight-device\", fmt.Sprintf(\"%s:%d\", lo.Device, weight),\n\t\t\"--device-read-bps\", fmt.Sprintf(\"%s:%d\", lo.Device, readBps),\n\t\t\"--device-read-iops\", fmt.Sprintf(\"%s:%d\", lo.Device, readIops),\n\t\t\"--device-write-bps\", fmt.Sprintf(\"%s:%d\", lo.Device, writeBps),\n\t\t\"--device-write-iops\", fmt.Sprintf(\"%s:%d\", lo.Device, writeIops),\n\t\ttestutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\tassert.Equal(t, uint16(weight), inspect.HostConfig.BlkioWeight)\n\tassert.Equal(t, 1, len(inspect.HostConfig.BlkioWeightDevice))\n\tassert.Equal(t, lo.Device, inspect.HostConfig.BlkioWeightDevice[0].Path)\n\tassert.Equal(t, uint16(weight), inspect.HostConfig.BlkioWeightDevice[0].Weight)\n\tassert.Equal(t, 1, len(inspect.HostConfig.BlkioDeviceReadBps))\n\tassert.Equal(t, uint64(readBps), inspect.HostConfig.BlkioDeviceReadBps[0].Rate)\n\tassert.Equal(t, 1, len(inspect.HostConfig.BlkioDeviceWriteBps))\n\tassert.Equal(t, uint64(writeBps), inspect.HostConfig.BlkioDeviceWriteBps[0].Rate)\n\tassert.Equal(t, 1, len(inspect.HostConfig.BlkioDeviceReadIOps))\n\tassert.Equal(t, uint64(readIops), inspect.HostConfig.BlkioDeviceReadIOps[0].Rate)\n\tassert.Equal(t, 1, len(inspect.HostConfig.BlkioDeviceWriteIOps))\n\tassert.Equal(t, uint64(writeIops), inspect.HostConfig.BlkioDeviceWriteIOps[0].Rate)\n}\n\nfunc TestContainerInspectUser(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tDescription: \"Container inspect contains User\",\n\t\tRequire:     nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdockerfile := fmt.Sprintf(`\nFROM %s\nRUN groupadd -r test && useradd -r -g test test\nUSER test\n`, testutil.UbuntuImage)\n\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(), data.Temp().Path())\n\t\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(), \"--user\", \"test\", data.Identifier())\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"inspect\", \"--format\", \"{{.Config.User}}\", data.Identifier())\n\t\t},\n\t\tExpected: test.Expects(0, nil, expect.Equals(\"test\\n\")),\n\t}\n\n\ttestCase.Run(t)\n}\n\ntype hostConfigValues struct {\n\tDriver       string\n\tShmSize      int64\n\tPidMode      string\n\tGroupAddSize int\n\tRuntime      string\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_inspect_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestInspectProcessContainerContainsLabel(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := testutil.Identifier(t)\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", containerName, \"--label\", \"foo=foo\", \"--label\", \"bar=bar\", testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\treturn helpers.Command(\"inspect\", containerName)\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Container\n\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, 1, len(dc))\n\n\t\t\t\tassert.Equal(t, \"foo\", dc[0].Config.Labels[\"foo\"])\n\t\t\t\tassert.Equal(t, \"bar\", dc[0].Config.Labels[\"bar\"])\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestInspectHyperVContainerContainsLabel(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = nerdtest.HyperV\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := testutil.Identifier(t)\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", containerName, \"--isolation\", \"hyperv\", \"--label\", \"foo=foo\", \"--label\", \"bar=bar\", testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\treturn helpers.Command(\"inspect\", containerName)\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Container\n\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, 1, len(dc))\n\n\t\t\t\t//check with HCS if the container is ineed a VM\n\t\t\t\tisHypervContainer, err := testutil.HyperVContainer(dc[0])\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, true, isHypervContainer)\n\n\t\t\t\tassert.Equal(t, \"foo\", dc[0].Config.Labels[\"foo\"])\n\t\t\t\tassert.Equal(t, \"bar\", dc[0].Config.Labels[\"bar\"])\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_kill.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc KillCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"kill [flags] CONTAINER [CONTAINER, ...]\",\n\t\tShort:             \"Kill one or more running containers\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              killAction,\n\t\tValidArgsFunction: killShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().StringP(\"signal\", \"s\", \"KILL\", \"Signal to send to the container\")\n\treturn cmd\n}\n\nfunc killAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tkillSignal, err := cmd.Flags().GetString(\"signal\")\n\tif err != nil {\n\t\treturn err\n\t}\n\toptions := types.ContainerKillOptions{\n\t\tGOptions:   globalOptions,\n\t\tKillSignal: killSignal,\n\t\tStdout:     cmd.OutOrStdout(),\n\t\tStderr:     cmd.ErrOrStderr(),\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Kill(ctx, client, args, options)\n}\n\nfunc killShellComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {\n\t// show non-stopped container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st != containerd.Stopped && st != containerd.Created && st != containerd.Unknown\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_kill_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/coreos/go-iptables/iptables\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\tiptablesutil \"github.com/containerd/nerdctl/v2/pkg/testutil/iptables\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// TestKillCleanupForwards runs a container that exposes a port and then kill it.\n// The test checks that the kill command effectively clean up\n// the iptables forwards creted from the run.\nfunc TestKillCleanupForwards(t *testing.T) {\n\tconst (\n\t\thostPort          = 9999\n\t\ttestContainerName = \"ngx\"\n\t)\n\tbase := testutil.NewBase(t)\n\tdefer func() {\n\t\tbase.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\t}()\n\n\t// skip if rootless\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"pkg/testutil/iptables does not support rootless\")\n\t}\n\n\tipt, err := iptables.New()\n\tassert.NilError(t, err)\n\n\tcontainerID := base.Cmd(\"run\", \"-d\",\n\t\t\"--restart=no\",\n\t\t\"--name\", testContainerName,\n\t\t\"-p\", fmt.Sprintf(\"127.0.0.1:%d:80\", hostPort),\n\t\ttestutil.NginxAlpineImage).Run().Stdout()\n\tcontainerID = strings.TrimSuffix(containerID, \"\\n\")\n\n\tcontainerIP := base.Cmd(\"inspect\",\n\t\t\"-f\",\n\t\t\"'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'\",\n\t\ttestContainerName).Run().Stdout()\n\tcontainerIP = strings.ReplaceAll(containerIP, \"'\", \"\")\n\tcontainerIP = strings.TrimSuffix(containerIP, \"\\n\")\n\n\t// define iptables chain name depending on the target (docker/nerdctl)\n\tvar chain string\n\tif nerdtest.IsDocker() {\n\t\tchain = \"DOCKER\"\n\t} else {\n\t\tredirectChain := \"CNI-HOSTPORT-DNAT\"\n\t\tchain = iptablesutil.GetRedirectedChain(t, ipt, redirectChain, testutil.Namespace, containerID)\n\t}\n\tassert.Equal(t, iptablesutil.ForwardExists(t, ipt, chain, containerIP, hostPort), true)\n\n\tbase.Cmd(\"kill\", testContainerName).AssertOK()\n\tassert.Equal(t, iptablesutil.ForwardExists(t, ipt, chain, containerIP, hostPort), false)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\nfunc PsCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"ps\",\n\t\tArgs:          cobra.NoArgs,\n\t\tShort:         \"List containers\",\n\t\tRunE:          psAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Show all containers (default shows just running)\")\n\tcmd.Flags().IntP(\"last\", \"n\", -1, \"Show n last created containers (includes all states)\")\n\tcmd.Flags().BoolP(\"latest\", \"l\", false, \"Show the latest created container (includes all states)\")\n\tcmd.Flags().Bool(\"no-trunc\", false, \"Don't truncate output\")\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only display container IDs\")\n\tcmd.Flags().BoolP(\"size\", \"s\", false, \"Display total file sizes\")\n\n\t// Alias \"-f\" is reserved for \"--filter\"\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}', 'wide'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\", \"table\", \"wide\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringSliceP(\"filter\", \"f\", nil, \"Filter matches containers based on given conditions. When specifying the condition 'status', it filters all containers\")\n\treturn cmd\n}\n\nfunc processOptions(cmd *cobra.Command) (types.ContainerListOptions, FormattingAndPrintingOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\tlatest, err := cmd.Flags().GetBool(\"latest\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\n\tlastN, err := cmd.Flags().GetInt(\"last\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\tif lastN == -1 && latest {\n\t\tlastN = 1\n\t}\n\n\tfilters, err := cmd.Flags().GetStringSlice(\"filter\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\n\tnoTrunc, err := cmd.Flags().GetBool(\"no-trunc\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\ttrunc := !noTrunc\n\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t}\n\n\tsize := false\n\tif !quiet {\n\t\tsize, err = cmd.Flags().GetBool(\"size\")\n\t\tif err != nil {\n\t\t\treturn types.ContainerListOptions{}, FormattingAndPrintingOptions{}, err\n\t\t}\n\t}\n\n\treturn types.ContainerListOptions{\n\t\t\tGOptions: globalOptions,\n\t\t\tAll:      all,\n\t\t\tLastN:    lastN,\n\t\t\tTruncate: trunc,\n\t\t\tSize:     size || (format == \"wide\" && !quiet),\n\t\t\tFilters:  filters,\n\t\t}, FormattingAndPrintingOptions{\n\t\t\tStdout: cmd.OutOrStdout(),\n\t\t\tQuiet:  quiet,\n\t\t\tFormat: format,\n\t\t\tSize:   size,\n\t\t}, nil\n}\n\nfunc psAction(cmd *cobra.Command, args []string) error {\n\tclOpts, fpOpts, err := processOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), clOpts.GOptions.Namespace, clOpts.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tcontainers, err := container.List(ctx, client, clOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn formatAndPrintContainerInfo(containers, fpOpts)\n}\n\n// FormattingAndPrintingOptions specifies options for formatting and printing of `nerdctl (container) list`.\ntype FormattingAndPrintingOptions struct {\n\tStdout io.Writer\n\t// Only display container IDs.\n\tQuiet bool\n\t// Format the output using the given Go template (e.g., '{{json .}}', 'table', 'wide').\n\tFormat string\n\t// Display total file sizes.\n\tSize bool\n}\n\nfunc formatAndPrintContainerInfo(containers []container.ListItem, options FormattingAndPrintingOptions) error {\n\tw := options.Stdout\n\tvar (\n\t\twide bool\n\t\ttmpl *template.Template\n\t)\n\tswitch options.Format {\n\tcase \"\", \"table\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !options.Quiet {\n\t\t\tprintHeader := \"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\"\n\t\t\tif options.Size {\n\t\t\t\tprintHeader += \"\\tSIZE\"\n\t\t\t}\n\t\t\tfmt.Fprintln(w, printHeader)\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tcase \"wide\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !options.Quiet {\n\t\t\tfmt.Fprintln(w, \"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\\tRUNTIME\\tPLATFORM\\tSIZE\")\n\t\t\twide = true\n\t\t}\n\tdefault:\n\t\tif options.Quiet {\n\t\t\treturn errors.New(\"format and quiet must not be specified together\")\n\t\t}\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, c := range containers {\n\t\tif tmpl != nil {\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, &c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := fmt.Fprintln(w, b.String()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if options.Quiet {\n\t\t\tif _, err := fmt.Fprintln(w, c.ID); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tformat := \"%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\"\n\t\t\targs := []interface{}{\n\t\t\t\tc.ID,\n\t\t\t\tc.Image,\n\t\t\t\tc.Command,\n\t\t\t\tformatter.TimeSinceInHuman(c.CreatedAt),\n\t\t\t\tc.Status,\n\t\t\t\tc.Ports,\n\t\t\t\tc.Names,\n\t\t\t}\n\t\t\tif wide {\n\t\t\t\tformat += \"\\t%s\\t%s\\t%s\\n\"\n\t\t\t\targs = append(args, c.Runtime, c.Platform, c.Size)\n\t\t\t} else if options.Size {\n\t\t\t\tformat += \"\\t%s\\n\"\n\t\t\t\targs = append(args, c.Size)\n\t\t\t} else {\n\t\t\t\tformat += \"\\n\"\n\t\t\t}\n\t\t\tif _, err := fmt.Fprintf(w, format, args...); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t}\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_list_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/tabutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\ntype psTestContainer struct {\n\tname    string\n\tlabels  map[string]string\n\tvolumes []string\n\tnetwork string\n}\n\n// When keepAlive is false, the container will exit immediately with status 1.\nfunc preparePsTestContainer(t *testing.T, identity string, keepAlive bool) (*testutil.Base, psTestContainer) {\n\tbase := testutil.NewBase(t)\n\n\tbase.Cmd(\"pull\", \"--quiet\", testutil.CommonImage).AssertOK()\n\n\ttestContainerName := testutil.Identifier(t) + identity\n\trwVolName := testContainerName + \"-rw\"\n\t// A container can mount named and anonymous volumes\n\trwDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbase.Cmd(\"network\", \"create\", testContainerName).AssertOK()\n\tt.Cleanup(func() {\n\t\tbase.Cmd(\"rm\", \"-f\", testContainerName).AssertOK()\n\t\tbase.Cmd(\"volume\", \"rm\", \"-f\", rwVolName).Run()\n\t\tbase.Cmd(\"network\", \"rm\", testContainerName).Run()\n\t\tos.RemoveAll(rwDir)\n\t})\n\n\t// A container can have multiple labels.\n\t// Therefore, this test container has multiple labels to check it.\n\ttestLabels := make(map[string]string)\n\tkeys := []string{\n\t\ttestutil.Identifier(t) + identity,\n\t\ttestutil.Identifier(t) + identity,\n\t}\n\t// fill the value of testLabels\n\tfor _, k := range keys {\n\t\ttestLabels[k] = k\n\t}\n\tbase.Cmd(\"volume\", \"create\", rwVolName).AssertOK()\n\tmnt1 := fmt.Sprintf(\"%s:/%s_mnt1\", rwDir, identity)\n\tmnt2 := fmt.Sprintf(\"%s:/%s_mnt3\", rwVolName, identity)\n\n\targs := []string{\n\t\t\"run\",\n\t\t\"-d\",\n\t\t\"--name\",\n\t\ttestContainerName,\n\t\t\"--label\",\n\t\tformatter.FormatLabels(testLabels),\n\t\t\"-v\", mnt1,\n\t\t\"-v\", mnt2,\n\t\t\"--net\", testContainerName,\n\t}\n\tif keepAlive {\n\t\targs = append(args, testutil.CommonImage, \"top\")\n\t} else {\n\t\targs = append(args, \"--restart=no\", testutil.CommonImage, \"false\")\n\t}\n\n\tbase.Cmd(args...).AssertOK()\n\tif keepAlive {\n\t\tbase.EnsureContainerStarted(testContainerName)\n\t} else {\n\t\tbase.EnsureContainerExited(testContainerName, 1)\n\t}\n\n\t// dd if=/dev/zero of=test_file bs=1M count=25\n\t// let the container occupy 25MiB space.\n\tif keepAlive {\n\t\tbase.Cmd(\"exec\", testContainerName, \"dd\", \"if=/dev/zero\", \"of=/test_file\", \"bs=1M\", \"count=25\").AssertOK()\n\t}\n\tvolumes := []string{}\n\tvolumes = append(volumes, strings.Split(mnt1, \":\")...)\n\tvolumes = append(volumes, strings.Split(mnt2, \":\")...)\n\n\treturn base, psTestContainer{\n\t\tname:    testContainerName,\n\t\tlabels:  testLabels,\n\t\tvolumes: volumes,\n\t\tnetwork: testContainerName,\n\t}\n}\n\nfunc TestContainerList(t *testing.T) {\n\tbase, testContainer := preparePsTestContainer(t, \"list\", true)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"-s\").AssertOutWithFunc(func(stdout string) error {\n\t\t// An example of nerdctl/docker ps -n 1 -s\n\t\t// CONTAINER ID    IMAGE                               COMMAND    CREATED           STATUS    PORTS    NAMES            SIZE\n\t\t// be8d386c991e    docker.io/library/busybox:latest    \"top\"      1 second ago    Up                 c1       16.0 KiB (virtual 1.3 MiB)\n\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\\tSIZE\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainer, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, container, testContainer.name)\n\n\t\timage, _ := tab.ReadRow(lines[1], \"IMAGE\")\n\t\tassert.Equal(t, image, testutil.CommonImage)\n\n\t\tsize, _ := tab.ReadRow(lines[1], \"SIZE\")\n\n\t\t// there is some difference between nerdctl and docker in calculating the size of the container\n\t\texpectedSize := \"26.2MB (virtual \"\n\t\tif !nerdtest.IsDocker() {\n\t\t\texpectedSize = \"25.0 MiB (virtual \"\n\t\t}\n\n\t\tif !strings.Contains(size, expectedSize) {\n\t\t\treturn fmt.Errorf(\"expect container size %s, but got %s\", expectedSize, size)\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc TestContainerListWideMode(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\tbase, testContainer := preparePsTestContainer(t, \"listWithMode\", true)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"--format\", \"wide\").AssertOutWithFunc(func(stdout string) error {\n\n\t\t// An example of nerdctl ps --format wide\n\t\t// CONTAINER ID    IMAGE                               PLATFORM       COMMAND    CREATED              STATUS    PORTS    NAMES            RUNTIME                  SIZE\n\t\t// 17181f208b61    docker.io/library/busybox:latest    linux/amd64    \"top\"      About an hour ago    Up                 busybox-17181    io.containerd.runc.v2    16.0 KiB (virtual 1.3 MiB)\n\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\\tRUNTIME\\tPLATFORM\\tSIZE\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainer, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, container, testContainer.name)\n\n\t\timage, _ := tab.ReadRow(lines[1], \"IMAGE\")\n\t\tassert.Equal(t, image, testutil.CommonImage)\n\n\t\truntime, _ := tab.ReadRow(lines[1], \"RUNTIME\")\n\t\tassert.Equal(t, runtime, \"io.containerd.runc.v2\")\n\n\t\tsize, _ := tab.ReadRow(lines[1], \"SIZE\")\n\t\texpectedSize := \"25.0 MiB (virtual \"\n\t\tif !strings.Contains(size, expectedSize) {\n\t\t\treturn fmt.Errorf(\"expect container size %s, but got %s\", expectedSize, size)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestContainerListWithLabels(t *testing.T) {\n\tbase, testContainer := preparePsTestContainer(t, \"listWithLabels\", true)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"--format\", \"{{.Labels}}\").AssertOutWithFunc(func(stdout string) error {\n\n\t\t// An example of nerdctl ps --format \"{{.Labels}}\"\n\t\t// key1=value1,key2=value2,key3=value3\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 1 {\n\t\t\treturn fmt.Errorf(\"expected 1 line, got %d\", len(lines))\n\t\t}\n\n\t\t// check labels using map\n\t\t// 1. the results has no guarantee to show the same order.\n\t\t// 2. the results has no guarantee to show only configured labels.\n\t\tlabelsMap, err := strutil.ParseCSVMap(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse labels: %v\", err)\n\t\t}\n\n\t\tfor i := range testContainer.labels {\n\t\t\tif value, ok := labelsMap[i]; ok {\n\t\t\t\tassert.Equal(t, value, testContainer.labels[i])\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestContainerListWithNames(t *testing.T) {\n\tbase, testContainer := preparePsTestContainer(t, \"listWithNames\", true)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"--format\", \"{{.Names}}\").AssertOutWithFunc(func(stdout string) error {\n\n\t\t// An example of nerdctl ps --format \"{{.Names}}\"\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 1 {\n\t\t\treturn fmt.Errorf(\"expected 1 line, got %d\", len(lines))\n\t\t}\n\n\t\tassert.Equal(t, lines[0], testContainer.name)\n\n\t\treturn nil\n\t})\n}\n\nfunc TestContainerListWithFilter(t *testing.T) {\n\tbase, testContainerA := preparePsTestContainer(t, \"listWithFilterA\", true)\n\t_, testContainerB := preparePsTestContainer(t, \"listWithFilterB\", true)\n\t_, testContainerC := preparePsTestContainer(t, \"listWithFilterC\", false)\n\n\tbase.Cmd(\"ps\", \"--filter\", \"name=\"+testContainerA.name).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainerName, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, containerName, testContainerA.name)\n\t\tid, _ := tab.ReadRow(lines[1], \"CONTAINER ID\")\n\t\tbase.Cmd(\"ps\", \"-q\", \"--filter\", \"id=\"+id).AssertOutWithFunc(func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) != 1 {\n\t\t\t\treturn fmt.Errorf(\"expected 1 line, got %d\", len(lines))\n\t\t\t}\n\t\t\tif lines[0] != id {\n\t\t\t\treturn errors.New(\"failed to filter by id\")\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tbase.Cmd(\"ps\", \"-q\", \"--filter\", \"id=\"+id+id).AssertOutWithFunc(func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) > 0 {\n\t\t\t\tfor _, line := range lines {\n\t\t\t\t\tif line != \"\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"unexpected container found: %s\", line)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tbase.Cmd(\"ps\", \"-q\", \"--filter\", \"id=\").AssertOutWithFunc(func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) > 0 {\n\t\t\t\tfor _, line := range lines {\n\t\t\t\t\tif line != \"\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"unexpected container found: %s\", line)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\treturn nil\n\t})\n\n\t// should support regexp\n\tbase.Cmd(\"ps\", \"--filter\", \"name=.*\"+testContainerA.name+\".*\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainerName, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, containerName, testContainerA.name)\n\t\treturn nil\n\t})\n\n\t// fully anchored regexp\n\tbase.Cmd(\"ps\", \"--filter\", \"name=^\"+testContainerA.name+\"$\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainerName, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, containerName, testContainerA.name)\n\t\treturn nil\n\t})\n\n\tbase.Cmd(\"ps\", \"-q\", \"--filter\", \"name=\"+testContainerA.name+testContainerA.name).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) > 0 {\n\t\t\tfor _, line := range lines {\n\t\t\t\tif line != \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"unexpected container found: %s\", line)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\tbase.Cmd(\"ps\", \"-q\", \"--filter\", \"name=\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) == 0 {\n\t\t\treturn errors.New(\"expect at least 1 container, got 0\")\n\t\t}\n\t\treturn nil\n\t})\n\n\tbase.Cmd(\"ps\", \"--filter\", \"name=listWithFilter\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 3 {\n\t\t\treturn fmt.Errorf(\"expected at least 3 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tcontainerNames := map[string]struct{}{\n\t\t\ttestContainerA.name: {}, testContainerB.name: {},\n\t\t}\n\t\tfor idx, line := range lines {\n\t\t\tif idx == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\tif _, ok := containerNames[containerName]; !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\t// docker filter by id only support full ID no truncate\n\t// https://github.com/docker/for-linux/issues/258\n\t// yet nerdctl also support truncate ID\n\tbase.Cmd(\"ps\", \"--no-trunc\", \"--filter\", \"since=\"+testContainerA.name).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tvar id string\n\t\tfor idx, line := range lines {\n\t\t\tif idx == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\tif containerName != testContainerB.name {\n\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t}\n\t\t\tid, _ = tab.ReadRow(line, \"CONTAINER ID\")\n\t\t}\n\t\tbase.Cmd(\"ps\", \"--filter\", \"before=\"+id).AssertOutWithFunc(func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) < 2 {\n\t\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t\t}\n\n\t\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t\t}\n\t\t\tfoundA := false\n\t\t\tfor idx, line := range lines {\n\t\t\t\tif idx == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\t\tif containerName == testContainerA.name {\n\t\t\t\t\tfoundA = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\t// there are other containers such as **wordpress** could be listed since\n\t\t\t// their created times are ahead of testContainerB too\n\t\t\tif !foundA {\n\t\t\t\treturn fmt.Errorf(\"expected container %s not found\", testContainerA.name)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\treturn nil\n\t})\n\n\t// docker filter by id only support full ID no truncate\n\t// https://github.com/docker/for-linux/issues/258\n\t// yet nerdctl also support truncate ID\n\tbase.Cmd(\"ps\", \"--no-trunc\", \"--filter\", \"before=\"+testContainerB.name).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tfoundA := false\n\t\tvar id string\n\t\tfor idx, line := range lines {\n\t\t\tif idx == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\tif containerName == testContainerA.name {\n\t\t\t\tfoundA = true\n\t\t\t\tid, _ = tab.ReadRow(line, \"CONTAINER ID\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// there are other containers such as **wordpress** could be listed since\n\t\t// their created times are ahead of testContainerB too\n\t\tif !foundA {\n\t\t\treturn fmt.Errorf(\"expected container %s not found\", testContainerA.name)\n\t\t}\n\t\tbase.Cmd(\"ps\", \"--filter\", \"since=\"+id).AssertOutWithFunc(func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) < 2 {\n\t\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t\t}\n\n\t\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t\t}\n\t\t\tfor idx, line := range lines {\n\t\t\t\tif idx == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\t\tif containerName != testContainerB.name {\n\t\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\treturn nil\n\t})\n\n\tfor _, testContainer := range []psTestContainer{testContainerA, testContainerB} {\n\t\tfor _, volume := range testContainer.volumes {\n\t\t\tbase.Cmd(\"ps\", \"--filter\", \"volume=\"+volume).AssertOutWithFunc(func(stdout string) error {\n\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\tif len(lines) < 2 {\n\t\t\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t\t\t}\n\n\t\t\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t\t\t}\n\t\t\t\tcontainerName, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\t\t\tassert.Equal(t, containerName, testContainer.name)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\t}\n\n\tbase.Cmd(\"ps\", \"--filter\", \"network=\"+testContainerA.network).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tcontainerName, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, containerName, testContainerA.name)\n\t\treturn nil\n\t})\n\n\tfor key, value := range testContainerB.labels {\n\t\tbase.Cmd(\"ps\", \"--filter\", \"label=\"+key+\"=\"+value).AssertOutWithFunc(func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) < 2 {\n\t\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t\t}\n\n\t\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t\t}\n\t\t\tcontainerNames := map[string]struct{}{\n\t\t\t\ttestContainerB.name: {},\n\t\t\t}\n\t\t\tfor idx, line := range lines {\n\t\t\t\tif idx == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\t\tif _, ok := containerNames[containerName]; !ok {\n\t\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tbase.Cmd(\"ps\", \"-a\", \"--filter\", \"exited=1\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tcontainerNames := map[string]struct{}{\n\t\t\ttestContainerC.name: {},\n\t\t}\n\t\tfor idx, line := range lines {\n\t\t\tif idx == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\tif _, ok := containerNames[containerName]; !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\tbase.Cmd(\"ps\", \"-a\", \"--filter\", \"status=exited\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tcontainerNames := map[string]struct{}{\n\t\t\ttestContainerC.name: {},\n\t\t}\n\t\tfor idx, line := range lines {\n\t\t\tif idx == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\tif _, ok := containerNames[containerName]; !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\t// filter container state without option \"-a\".\n\tbase.Cmd(\"ps\", \"--filter\", \"status=exited\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\t\tcontainerNames := map[string]struct{}{\n\t\t\ttestContainerC.name: {},\n\t\t}\n\t\tfor idx, line := range lines {\n\t\t\tif idx == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcontainerName, _ := tab.ReadRow(line, \"NAMES\")\n\t\t\tif _, ok := containerNames[containerName]; !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected container %s found\", containerName)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestContainerListCheckCreatedTime(t *testing.T) {\n\tbase, _ := preparePsTestContainer(t, \"checkCreatedTimeA\", true)\n\tpreparePsTestContainer(t, \"checkCreatedTimeB\", true)\n\tpreparePsTestContainer(t, \"checkCreatedTimeC\", false)\n\tpreparePsTestContainer(t, \"checkCreatedTimeD\", false)\n\n\tvar createdTimes []string\n\n\tbase.Cmd(\"ps\", \"--format\", \"'{{json .CreatedAt}}'\", \"-a\").AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 4 {\n\t\t\treturn fmt.Errorf(\"expected at least 4 lines, got %d\", len(lines))\n\t\t}\n\t\tcreatedTimes = append(createdTimes, lines...)\n\t\treturn nil\n\t})\n\n\tslices.Reverse(createdTimes)\n\tif !slices.IsSorted(createdTimes) {\n\t\tt.Errorf(\"expected containers in decending order\")\n\t}\n}\n\nfunc TestContainerListStatusFilter(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(\"container\"), testutil.CommonImage, \"echo\", \"foo\")\n\t\tdata.Labels().Set(\"cID\", data.Identifier(\"container\"))\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t// TODO: Refactor other filter tests\n\t\t{\n\t\t\tDescription: \"ps filter with status=created\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\", \"--filter\", \"status=created\", \"--filter\", fmt.Sprintf(\"name=%s\", data.Labels().Get(\"cID\")))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, data.Labels().Get(\"cID\")), \"No container found with status created\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_list_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// https://github.com/containerd/nerdctl/issues/2598\nfunc TestContainerListWithFormatLabel(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tlabelK := \"label-key-\" + data.Identifier()\n\t\t\tlabelV := \"label-value-\" + data.Identifier()\n\t\t\thelpers.Ensure(\"run\", \"-d\",\n\t\t\t\t\"--name\", data.Identifier(),\n\t\t\t\t\"--label\", labelK+\"=\"+labelV,\n\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tlabelK := \"label-key-\" + data.Identifier()\n\t\t\treturn helpers.Command(\"ps\", \"-a\",\n\t\t\t\t\"--filter\", \"label=\"+labelK,\n\t\t\t\t\"--format\", fmt.Sprintf(\"{{.Label %q}}\", labelK)) //nolint:dupামিটার\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\tlabelV := \"label-value-\" + data.Identifier()\n\t\t\treturn test.Expects(0, nil, expect.Equals(labelV+\"\\n\"))(data, helpers)\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestContainerListWithJsonFormatLabel(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tlabelK := \"label-key-\" + data.Identifier()\n\t\t\tlabelV := \"label-value-\" + data.Identifier()\n\t\t\thelpers.Ensure(\"run\", \"-d\",\n\t\t\t\t\"--name\", data.Identifier(),\n\t\t\t\t\"--label\", labelK+\"=\"+labelV,\n\t\t\t\ttestutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tlabelK := \"label-key-\" + data.Identifier()\n\t\t\treturn helpers.Command(\"ps\", \"-a\",\n\t\t\t\t\"--filter\", \"label=\"+labelK,\n\t\t\t\t\"--format\", \"json\")\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\tlabelK := \"label-key-\" + data.Identifier()\n\t\t\tlabelV := \"label-value-\" + data.Identifier()\n\t\t\treturn test.Expects(0, nil, expect.Contains(fmt.Sprintf(\"%s=%s\", labelK, labelV)))(data, helpers)\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_list_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/tabutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\ntype psTestContainer struct {\n\tname    string\n\tlabels  map[string]string\n\tnetwork string\n}\n\nfunc preparePsTestContainer(t *testing.T, identity string, restart bool, hyperv bool) (*testutil.Base, psTestContainer) {\n\tbase := testutil.NewBase(t)\n\n\tbase.Cmd(\"pull\", \"--quiet\", testutil.NginxAlpineImage).AssertOK()\n\n\ttestContainerName := testutil.Identifier(t) + identity\n\tt.Cleanup(func() {\n\t\tbase.Cmd(\"rm\", \"-f\", testContainerName).AssertOK()\n\t})\n\n\t// A container can have multiple labels.\n\t// Therefore, this test container has multiple labels to check it.\n\ttestLabels := make(map[string]string)\n\tkeys := []string{\n\t\ttestutil.Identifier(t) + identity,\n\t\ttestutil.Identifier(t) + identity,\n\t}\n\t// fill the value of testLabels\n\tfor _, k := range keys {\n\t\ttestLabels[k] = k\n\t}\n\n\targs := []string{\n\t\t\"run\",\n\t\t\"-d\",\n\t\t\"--name\",\n\t\ttestContainerName,\n\t\t\"--label\",\n\t\tformatter.FormatLabels(testLabels),\n\t\ttestutil.NginxAlpineImage,\n\t}\n\tif !restart {\n\t\targs = append(args, \"--restart=no\")\n\t}\n\tif hyperv {\n\t\targs = append(args[:3], args[1:]...)\n\t\targs[1], args[2] = \"--isolation\", \"hyperv\"\n\t}\n\n\tbase.Cmd(args...).AssertOK()\n\tif restart {\n\t\tbase.EnsureContainerStarted(testContainerName)\n\t}\n\n\treturn base, psTestContainer{\n\t\tname:    testContainerName,\n\t\tlabels:  testLabels,\n\t\tnetwork: testContainerName,\n\t}\n}\n\nfunc TestListProcessContainer(t *testing.T) {\n\tbase, testContainer := preparePsTestContainer(t, \"list\", true, false)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"-s\").AssertOutWithFunc(func(stdout string) error {\n\t\t// An example of nerdctl/docker ps -n 1 -s\n\t\t// CONTAINER ID    IMAGE                               COMMAND    CREATED           STATUS    PORTS    NAMES            SIZE\n\t\t// be8d386c991e    docker.io/library/busybox:latest    \"top\"      1 second ago    Up                 c1       16.0 KiB (virtual 1.3 MiB)\n\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\\tSIZE\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainer, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, container, testContainer.name)\n\n\t\timage, _ := tab.ReadRow(lines[1], \"IMAGE\")\n\t\tassert.Equal(t, image, testutil.NginxAlpineImage)\n\n\t\tsize, _ := tab.ReadRow(lines[1], \"SIZE\")\n\n\t\t// there is some difference between nerdctl and docker in calculating the size of the container\n\t\texpectedSize := \"36.0 MiB (virtual \"\n\n\t\tif !strings.Contains(size, expectedSize) {\n\t\t\treturn fmt.Errorf(\"expect container size %s, but got %s\", expectedSize, size)\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc TestListHyperVContainer(t *testing.T) {\n\tif !testutil.HyperVSupported() {\n\t\tt.Skip(\"HyperV is not enabled, skipping test\")\n\t}\n\n\tbase, testContainer := preparePsTestContainer(t, \"list\", true, true)\n\tinspect := base.InspectContainer(testContainer.name)\n\t//check with HCS if the container is ineed a VM\n\tisHypervContainer, err := testutil.HyperVContainer(inspect)\n\tif err != nil {\n\t\tt.Fatalf(\"unable to list HCS containers: %s\", err)\n\t}\n\tassert.Assert(t, isHypervContainer, true)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"-s\").AssertOutWithFunc(func(stdout string) error {\n\t\t// An example of nerdctl/docker ps -n 1 -s\n\t\t// CONTAINER ID    IMAGE                               COMMAND    CREATED           STATUS    PORTS    NAMES            SIZE\n\t\t// be8d386c991e    docker.io/library/busybox:latest    \"top\"      1 second ago    Up                 c1       16.0 KiB (virtual 1.3 MiB)\n\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\\tSIZE\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainer, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, container, testContainer.name)\n\n\t\timage, _ := tab.ReadRow(lines[1], \"IMAGE\")\n\t\tassert.Equal(t, image, testutil.NginxAlpineImage)\n\n\t\tsize, _ := tab.ReadRow(lines[1], \"SIZE\")\n\n\t\t// there is some difference between nerdctl and docker in calculating the size of the container\n\t\texpectedSize := \"72.0 MiB (virtual \"\n\n\t\tif !strings.Contains(size, expectedSize) {\n\t\t\treturn fmt.Errorf(\"expect container size %s, but got %s\", expectedSize, size)\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc TestListProcessContainerWideMode(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\tbase, testContainer := preparePsTestContainer(t, \"listWithMode\", true, false)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"--format\", \"wide\").AssertOutWithFunc(func(stdout string) error {\n\n\t\t// An example of nerdctl ps --format wide\n\t\t// CONTAINER ID    IMAGE                               PLATFORM       COMMAND    CREATED              STATUS    PORTS    NAMES            RUNTIME                  SIZE\n\t\t// 17181f208b61    docker.io/library/busybox:latest    linux/amd64    \"top\"      About an hour ago    Up                 busybox-17181    io.containerd.runc.v2    16.0 KiB (virtual 1.3 MiB)\n\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) < 2 {\n\t\t\treturn fmt.Errorf(\"expected at least 2 lines, got %d\", len(lines))\n\t\t}\n\n\t\ttab := tabutil.NewReader(\"CONTAINER ID\\tIMAGE\\tCOMMAND\\tCREATED\\tSTATUS\\tPORTS\\tNAMES\\tRUNTIME\\tPLATFORM\\tSIZE\")\n\t\terr := tab.ParseHeader(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse header: %v\", err)\n\t\t}\n\n\t\tcontainer, _ := tab.ReadRow(lines[1], \"NAMES\")\n\t\tassert.Equal(t, container, testContainer.name)\n\n\t\timage, _ := tab.ReadRow(lines[1], \"IMAGE\")\n\t\tassert.Equal(t, image, testutil.NginxAlpineImage)\n\n\t\truntime, _ := tab.ReadRow(lines[1], \"RUNTIME\")\n\t\tassert.Equal(t, runtime, \"io.containerd.runhcs.v1\")\n\n\t\tsize, _ := tab.ReadRow(lines[1], \"SIZE\")\n\t\texpectedSize := \"36.0 MiB (virtual \"\n\t\tif !strings.Contains(size, expectedSize) {\n\t\t\treturn fmt.Errorf(\"expect container size %s, but got %s\", expectedSize, size)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestListProcessContainerWithLabels(t *testing.T) {\n\tbase, testContainer := preparePsTestContainer(t, \"listWithLabels\", true, false)\n\n\t// hope there are no tests running parallel\n\tbase.Cmd(\"ps\", \"-n\", \"1\", \"--format\", \"{{.Labels}}\").AssertOutWithFunc(func(stdout string) error {\n\n\t\t// An example of nerdctl ps --format \"{{.Labels}}\"\n\t\t// key1=value1,key2=value2,key3=value3\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 1 {\n\t\t\treturn fmt.Errorf(\"expected 1 line, got %d\", len(lines))\n\t\t}\n\n\t\t// check labels using map\n\t\t// 1. the results has no guarantee to show the same order.\n\t\t// 2. the results has no guarantee to show only configured labels.\n\t\tlabelsMap, err := strutil.ParseCSVMap(lines[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse labels: %v\", err)\n\t\t}\n\n\t\tfor i := range testContainer.labels {\n\t\t\tif value, ok := labelsMap[i]; ok {\n\t\t\t\tassert.Equal(t, value, testContainer.labels[i])\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_logs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc LogsCommand() *cobra.Command {\n\tconst shortUsage = \"Fetch the logs of a container. Expected to be used with 'nerdctl run -d'.\"\n\tconst longUsage = `Fetch the logs of a container.\n\nThe following containers are supported:\n- Containers created with 'nerdctl run -d'. The log is currently empty for containers created without '-d'.\n- Containers created with 'nerdctl compose'.\n- Containers created with Kubernetes (EXPERIMENTAL).\n`\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"logs [flags] CONTAINER\",\n\t\tArgs:              helpers.IsExactArgs(1),\n\t\tShort:             shortUsage,\n\t\tLong:              longUsage,\n\t\tRunE:              logsAction,\n\t\tValidArgsFunction: logsShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().BoolP(\"follow\", \"f\", false, \"Follow log output\")\n\tcmd.Flags().BoolP(\"timestamps\", \"t\", false, \"Show timestamps\")\n\tcmd.Flags().StringP(\"tail\", \"n\", \"all\", \"Number of lines to show from the end of the logs\")\n\tcmd.Flags().String(\"since\", \"\", \"Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\")\n\tcmd.Flags().String(\"until\", \"\", \"Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\")\n\tcmd.Flags().Bool(\"details\", false, \"Show extra details provided to logs\")\n\treturn cmd\n}\n\nfunc logsOptions(cmd *cobra.Command) (types.ContainerLogsOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\tfollow, err := cmd.Flags().GetBool(\"follow\")\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\ttailArg, err := cmd.Flags().GetString(\"tail\")\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\tvar tail uint\n\tif tailArg != \"\" {\n\t\ttail, err = getTailArgAsUint(tailArg)\n\t\tif err != nil {\n\t\t\treturn types.ContainerLogsOptions{}, err\n\t\t}\n\t}\n\ttimestamps, err := cmd.Flags().GetBool(\"timestamps\")\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\tsince, err := cmd.Flags().GetString(\"since\")\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\tuntil, err := cmd.Flags().GetString(\"until\")\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\tdetails, err := cmd.Flags().GetBool(\"details\")\n\tif err != nil {\n\t\treturn types.ContainerLogsOptions{}, err\n\t}\n\treturn types.ContainerLogsOptions{\n\t\tStdout:     cmd.OutOrStdout(),\n\t\tStderr:     cmd.OutOrStderr(),\n\t\tGOptions:   globalOptions,\n\t\tFollow:     follow,\n\t\tTimestamps: timestamps,\n\t\tTail:       tail,\n\t\tSince:      since,\n\t\tUntil:      until,\n\t\tDetails:    details,\n\t}, nil\n}\n\nfunc logsAction(cmd *cobra.Command, args []string) error {\n\toptions, err := logsOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Logs(ctx, client, args[0], options)\n}\n\nfunc logsShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show container names (TODO: only show containers with logs)\n\treturn completion.ContainerNames(cmd, nil)\n}\n\n// Attempts to parse the argument given to `-n/--tail` as a uint.\nfunc getTailArgAsUint(arg string) (uint, error) {\n\tif arg == \"all\" {\n\t\treturn 0, nil\n\t}\n\tnum, err := strconv.Atoi(arg)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to parse `-n/--tail` argument %q: %w\", arg, err)\n\t}\n\tif num < 0 {\n\t\treturn 0, fmt.Errorf(\"`-n/--tail` argument must be positive, got: %d\", num)\n\t}\n\treturn uint(num), nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_logs_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestLogs(t *testing.T) {\n\tconst expected = `foo\nbar\n`\n\n\ttestCase := nerdtest.Setup()\n\n\tif runtime.GOOS == \"windows\" {\n\t\ttestCase.Require = nerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/4237\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--quiet\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo; echo bar;\")\n\t\tdata.Labels().Set(\"cID\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"since 1s\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Ensure at least 2 seconds have elapsed since the container ran,\n\t\t\t\t// so that --since 1s does not include the container's output.\n\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"--since\", \"1s\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.DoesNotContain(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"since 60s\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"--since\", \"60s\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"until 60s\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"--until\", \"60s\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.DoesNotContain(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"until 1s\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Ensure at least 2 seconds have elapsed since the container ran,\n\t\t\t\t// so that --until 1s includes the container's output.\n\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"--until\", \"1s\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"follow\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"-f\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"timestamp\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"-t\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(time.Now().UTC().Format(\"2006-01-02\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"tail flag\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"-n\", \"all\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"tail flag\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"-n\", \"1\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\t// FIXME: why?\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"^(?:bar\\n|)$\"))),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\n// Tests whether `nerdctl logs` properly separates stdout/stderr output\n// streams for containers using the jsonfile logging driver:\nfunc TestLogsOutStreamsSeparated(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tif runtime.GOOS == \"windows\" {\n\t\t// Logging seems broken on windows.\n\t\ttestCase.Require = nerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/4237\")\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-euc\", \"echo stdout1; echo stderr1 >&2; echo stdout2; echo stderr2 >&2\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, []error{\n\t\t//revive:disable:error-strings\n\t\terrors.New(\"stderr1\\nstderr2\\n\"),\n\t}, expect.Equals(\"stdout1\\nstdout2\\n\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsWithInheritedFlags(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"-n=\"+testutil.Namespace, \"run\", \"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-euxc\", \"echo foo; echo bar\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"-n=\"+testutil.Namespace, \"logs\", \"-n\", \"1\", data.Identifier())\n\t}\n\n\t// FIXME: why?\n\ttestCase.Expected = test.Expects(0, nil, expect.Match(regexp.MustCompile(\"^(?:bar\\n|)$\")))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsOfJournaldDriver(t *testing.T) {\n\tconst expected = `foo\nbar\n`\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Binary(\"journalctl\"),\n\t\t&test.Requirement{\n\t\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\t\tworks := false\n\t\t\t\tcmd := helpers.Custom(\"journalctl\", \"-xe\")\n\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tif stdout != \"\" {\n\t\t\t\t\t\t\tworks = true\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn works, \"Journactl to return data for the current user\"\n\t\t\t},\n\t\t},\n\t)\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--network\", \"none\", \"--log-driver\", \"journald\", \"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-euxc\", \"echo foo; echo bar\")\n\t\tdata.Labels().Set(\"cID\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"logs\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(expected)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"logs --since 60s\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"--since\", \"60s\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.DoesNotContain(\"foo\", \"bar\")),\n\t\t},\n\t}\n}\n\nfunc TestLogsWithFailingContainer(t *testing.T) {\n\tconst expected = `foo\nbar\n`\n\n\ttestCase := nerdtest.Setup()\n\n\tif runtime.GOOS == \"windows\" {\n\t\t// Logging seems broken on windows.\n\t\ttestCase.Require = nerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/4237\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"run\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo; echo bar; exit 42; echo baz\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(expected))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsWithRunningContainer(t *testing.T) {\n\texpected := make([]string, 10)\n\tfor i := 0; i < 10; i++ {\n\t\texpected[i] = fmt.Sprint(i + 1)\n\t}\n\n\ttestCase := nerdtest.Setup()\n\n\tif runtime.GOOS == \"windows\" {\n\t\t// Logging seems broken on windows.\n\t\ttestCase.Require = nerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/4237\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euc\", \"for i in `seq 1 10`; do echo $i; sleep 1; done\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(expected[0], expected[1:]...))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsWithoutNewlineOrEOF(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// FIXME: test does not work on Windows yet because containerd doesn't send an exit event appropriately after task exit on Windows\")\n\t// FIXME: nerdctl behavior does not match docker - test disabled for nerdctl until we fix\n\ttestCase.Require = require.All(\n\t\trequire.Linux,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage, \"printf\", \"'Hello World!\\nThere is no newline'\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"logs\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(\"'Hello World!\\nThere is no newline'\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsAfterRestartingContainer(t *testing.T) {\n\tif runtime.GOOS != \"linux\" {\n\t\tt.Skip(\"FIXME: test does not work on Windows yet. Restarting a container fails with: failed to create shim task: hcs::CreateComputeSystem <id>: The requested operation for attach namespace failed.: unknown\")\n\t}\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"printf\", \"'Hello World!\\nThere is no newline'\")\n\t\tdata.Labels().Set(\"cID\", data.Identifier())\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"logs -f works\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"-f\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"'Hello World!\\nThere is no newline'\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"logs -f works after restart\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"start\", data.Labels().Get(\"cID\"))\n\t\t\t\t// FIXME: this is inherently flaky\n\t\t\t\ttime.Sleep(5 * time.Second)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", \"-f\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"'Hello World!\\nThere is no newline''Hello World!\\nThere is no newline'\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsWithForegroundContainers(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\t// dual logging is not supported on Windows\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.Run(t)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"foreground\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo; echo bar\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foo\\nbar\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"interactive\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-i\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo; echo bar\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foo\\nbar\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"PTY\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-t\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo; echo bar\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foo\\nbar\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"interactivePTY\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-i\", \"-t\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo; echo bar\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foo\\nbar\\n\")),\n\t\t},\n\t}\n}\n\nfunc TestLogsTailFollowRotate(t *testing.T) {\n\t// FIXME this is flaky by nature... the number of lines is arbitrary, the wait is arbitrary,\n\t// and both are some sort of educated guess that things will mostly always kinda work maybe...\n\tconst sampleJSONLog = `{\"log\":\"A\\n\",\"stream\":\"stdout\",\"time\":\"2024-04-11T12:01:09.800288974Z\"}`\n\tconst linesPerFile = 200\n\n\ttestCase := nerdtest.Setup()\n\n\t// tail log is not supported on Windows\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--log-driver\", \"json-file\",\n\t\t\t\"--log-opt\", fmt.Sprintf(\"max-size=%d\", len(sampleJSONLog)*linesPerFile),\n\t\t\t\"--log-opt\", \"max-file=10\",\n\t\t\t\"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-euc\", \"while true; do echo A; usleep 100; done\")\n\t\t// FIXME: ... inherently racy...\n\t\ttime.Sleep(5 * time.Second)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcmd := helpers.Command(\"logs\", \"-f\", data.Identifier())\n\t\t// FIXME: this is flaky by nature. We assume that the container has started and will output enough in 5 seconds.\n\t\tcmd.WithTimeout(5 * time.Second)\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeTimeout, nil, func(stdout string, t tig.T) {\n\t\ttailLogs := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tfor _, line := range tailLogs {\n\t\t\tif line != \"\" {\n\t\t\t\tassert.Equal(t, \"A\", line)\n\t\t\t}\n\t\t}\n\n\t\tassert.Assert(t, len(tailLogs) > linesPerFile, fmt.Sprintf(\"expected %d lines or more, found %d\", linesPerFile, len(tailLogs)))\n\t})\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsNoneLoggerHasNoLogURI(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), \"--log-driver\", \"none\", testutil.CommonImage, \"sh\", \"-euxc\", \"echo foo\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(1, nil, nil)\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsWithDetails(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// FIXME: this is not working on windows. There is some deep issue with windows logs:\n\t// https://github.com/containerd/nerdctl/issues/4237\n\tif runtime.GOOS == \"windows\" {\n\t\ttestCase.Require = nerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/4237\")\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--log-driver\", \"json-file\",\n\t\t\t\"--log-opt\", \"max-size=10m\",\n\t\t\t\"--log-opt\", \"max-file=3\",\n\t\t\t\"--log-opt\", \"env=ENV\",\n\t\t\t\"--env\", \"ENV=foo\",\n\t\t\t\"--log-opt\", \"labels=LABEL\",\n\t\t\t\"--label\", \"LABEL=bar\",\n\t\t\t\"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-ec\", \"echo baz\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"logs\", \"--details\", data.Identifier())\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(\"ENV=foo\", \"LABEL=bar\", \"baz\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsFollowNoExtraneousLineFeed(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\t// This test verifies that `nerdctl logs -f` does not add extraneous line feeds\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// Create a container that outputs a message without a trailing newline\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(), testutil.CommonImage,\n\t\t\t\"sh\", \"-c\", \"printf 'Hello without newline'\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// Use logs -f to follow the logs\n\t\treturn helpers.Command(\"logs\", \"-f\", data.Identifier())\n\t}\n\n\t// Verify that the output is exactly \"Hello without newline\" without any additional line feeds\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(\"Hello without newline\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestLogsWithStartContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Windows does not support dual logging.\n\ttestCase.Require = require.Not(require.Windows)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Test logs are directed correctly for container start of a interactive container\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-it\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\tcmd.Feed(strings.NewReader(\"echo foo\\nexit\\n\"))\n\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t})\n\n\t\t\t\tcmd = helpers.Command(\"start\", \"-ia\", data.Identifier())\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\tcmd.Feed(strings.NewReader(\"echo bar\\nexit\\n\"))\n\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t})\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"foo\", \"bar\")),\n\t\t},\n\t\t{\n\t\t\t// FIXME: is this test safe or could it be racy?\n\t\t\tDescription: \"Test logs are captured after stopping and starting a non-interactive container and continue capturing new logs\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-c\", \"while true; do echo foo; sleep 1; done\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\thelpers.Ensure(\"stop\", data.Identifier())\n\t\t\t\tinitialLogs := helpers.Capture(\"logs\", data.Identifier())\n\t\t\t\tinitialFooCount := strings.Count(initialLogs, \"foo\")\n\t\t\t\tdata.Labels().Set(\"initialFooCount\", strconv.Itoa(initialFooCount))\n\t\t\t\thelpers.Ensure(\"start\", data.Identifier())\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t\treturn helpers.Command(\"logs\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tfinalLogsCount := strings.Count(stdout, \"foo\")\n\t\t\t\t\t\tinitialFooCount, _ := strconv.Atoi(data.Labels().Get(\"initialFooCount\"))\n\t\t\t\t\t\tassert.Assert(t, finalLogsCount > initialFooCount, \"Expected 'foo' count to increase after restart\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_pause.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc PauseCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"pause [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Pause all processes within one or more containers\",\n\t\tRunE:              pauseAction,\n\t\tValidArgsFunction: pauseShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc pauseOptions(cmd *cobra.Command) (types.ContainerPauseOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerPauseOptions{}, err\n\t}\n\treturn types.ContainerPauseOptions{\n\t\tGOptions: globalOptions,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc pauseAction(cmd *cobra.Command, args []string) error {\n\toptions, err := pauseOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Pause(ctx, client, args, options)\n}\n\nfunc pauseShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show running container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st == containerd.Running\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_port.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n)\n\nfunc PortCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"port [flags] CONTAINER [PRIVATE_PORT[/PROTO]]\",\n\t\tArgs:              cobra.RangeArgs(1, 2),\n\t\tShort:             \"List port mappings or a specific mapping for the container\",\n\t\tRunE:              portAction,\n\t\tValidArgsFunction: portShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc portAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\targPort := -1\n\targProto := \"\"\n\tportProto := \"\"\n\tif len(args) == 2 {\n\t\tportProto = args[1]\n\t}\n\n\tif portProto != \"\" {\n\t\tsplitBySlash := strings.Split(portProto, \"/\")\n\t\tvar err error\n\t\targPort, err = strconv.Atoi(splitBySlash[0])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif argPort <= 0 {\n\t\t\treturn fmt.Errorf(\"unexpected port %d\", argPort)\n\t\t}\n\t\tswitch len(splitBySlash) {\n\t\tcase 1:\n\t\t\targProto = \"tcp\"\n\t\tcase 2:\n\t\t\targProto = strings.ToLower(splitBySlash[1])\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"failed to parse %q\", portProto)\n\t\t}\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tdataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tcontainerLabels, err := found.Container.Labels(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tports, err := portutil.LoadPortMappings(dataStore, globalOptions.Namespace, found.Container.ID(), containerLabels)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn containerutil.PrintHostPort(ctx, cmd.OutOrStdout(), found.Container, argPort, argProto, ports)\n\t\t},\n\t}\n\treq := args[0]\n\tn, err := walker.Walk(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", req)\n\t}\n\treturn nil\n}\n\nfunc portShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc pruneCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"prune [flags]\",\n\t\tShort:         \"Remove all stopped containers\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          pruneAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\treturn cmd\n}\n\nfunc pruneOptions(cmd *cobra.Command) (types.ContainerPruneOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerPruneOptions{}, err\n\t}\n\n\treturn types.ContainerPruneOptions{\n\t\tGOptions: globalOptions,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc grantPrunePermission(cmd *cobra.Command) (bool, error) {\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif !force {\n\t\treturn helpers.Confirm(cmd, \"WARNING! This will remove all stopped containers.\")\n\t}\n\treturn true, nil\n}\n\nfunc pruneAction(cmd *cobra.Command, _ []string) error {\n\toptions, err := pruneOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := grantPrunePermission(cmd); err != nil {\n\t\treturn err\n\t} else if !ok {\n\t\treturn nil\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Prune(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_prune_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestPruneContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Private\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"1\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"2\"))\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"1\"), \"-v\", \"/anonymous\", testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\thelpers.Ensure(\"exec\", data.Identifier(\"1\"), \"touch\", \"/anonymous/foo\")\n\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(\"2\"), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\thelpers.Ensure(\"container\", \"prune\", \"-f\")\n\t\thelpers.Ensure(\"inspect\", data.Identifier(\"1\"))\n\t\thelpers.Fail(\"inspect\", data.Identifier(\"2\"))\n\t\t// https://github.com/containerd/nerdctl/issues/3134\n\t\thelpers.Ensure(\"exec\", data.Identifier(\"1\"), \"ls\", \"-lA\", \"/anonymous/foo\")\n\t\thelpers.Ensure(\"kill\", data.Identifier(\"1\"))\n\t\thelpers.Ensure(\"container\", \"prune\", \"-f\")\n\t\treturn helpers.Command(\"inspect\", data.Identifier(\"1\"))\n\t}\n\n\ttestCase.Expected = test.Expects(1, nil, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc RemoveCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"rm [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Remove one or more containers\",\n\t\tRunE:              removeAction,\n\t\tValidArgsFunction: rmShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Aliases = []string{\"remove\"}\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Force the removal of a running|paused|unknown container (uses SIGKILL)\")\n\tcmd.Flags().BoolP(\"volumes\", \"v\", false, \"Remove volumes associated with the container\")\n\treturn cmd\n}\n\nfunc removeAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tremoveAnonVolumes, err := cmd.Flags().GetBool(\"volumes\")\n\tif err != nil {\n\t\treturn err\n\t}\n\toptions := types.ContainerRemoveOptions{\n\t\tGOptions: globalOptions,\n\t\tForce:    force,\n\t\tVolumes:  removeAnonVolumes,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Remove(ctx, client, args, options)\n}\n\nfunc rmShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show container names\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_remove_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\n// iptablesCheckCommand is the shell command to check iptables rules\nconst iptablesCheckCommand = \"iptables -t nat -S && iptables -t filter -S && iptables -t mangle -S\"\n\n// testContainerRmIptablesExecutor is a common executor function for testing iptables rules cleanup\nfunc testContainerRmIptablesExecutor(data test.Data, helpers test.Helpers) test.TestableCommand {\n\tt := helpers.T()\n\n\t// Get the container ID from the label\n\tcontainerID := data.Labels().Get(\"containerID\")\n\n\t// Remove the container\n\thelpers.Ensure(\"rm\", \"-f\", containerID)\n\n\ttime.Sleep(1 * time.Second)\n\n\t// Create a TestableCommand using helpers.Custom\n\tif rootlessutil.IsRootless() {\n\t\t// In rootless mode, we need to enter the rootlesskit network namespace\n\t\tif netns, err := rootlessutil.DetachedNetNS(); err != nil {\n\t\t\tt.Log(fmt.Sprintf(\"Failed to get detached network namespace: %v\", err))\n\t\t\tt.FailNow()\n\t\t} else {\n\t\t\tif netns != \"\" {\n\t\t\t\t// Use containerd-rootless-setuptool.sh to enter the RootlessKit namespace\n\t\t\t\treturn helpers.Custom(\"containerd-rootless-setuptool.sh\", \"nsenter\", \"--\", \"nsenter\", \"--net=\"+netns, \"sh\", \"-ec\", iptablesCheckCommand)\n\t\t\t}\n\t\t\t// Enter into :RootlessKit namespace using containerd-rootless-setuptool.sh\n\t\t\treturn helpers.Custom(\"containerd-rootless-setuptool.sh\", \"nsenter\", \"--\", \"sh\", \"-ec\", iptablesCheckCommand)\n\t\t}\n\t}\n\n\t// In non-rootless mode, check iptables rules directly on the host\n\treturn helpers.Custom(\"sh\", \"-ec\", iptablesCheckCommand)\n}\n\n// TestContainerRmIptables tests that iptables rules are cleared after container deletion\nfunc TestContainerRmIptables(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Require iptables and containerd-rootless-setuptool.sh commands to be available\n\ttestCase.Require = require.All(\n\t\trequire.Binary(\"iptables\"),\n\t\trequire.Binary(\"containerd-rootless-setuptool.sh\"),\n\t\trequire.Not(require.Windows),\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Test iptables rules are cleared after container deletion\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Get a free port using portlock\n\t\t\t\tport, err := portlock.Acquire(0)\n\t\t\t\tif err != nil {\n\t\t\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to acquire port: %v\", err))\n\t\t\t\t\thelpers.T().FailNow()\n\t\t\t\t}\n\t\t\t\tdata.Labels().Set(\"port\", strconv.Itoa(port))\n\n\t\t\t\t// Create a container with port mapping to ensure iptables rules are created\n\t\t\t\tcontainerID := helpers.Capture(\"run\", \"-d\", \"--name\", data.Identifier(), \"-p\", fmt.Sprintf(\"%d:80\", port), testutil.NginxAlpineImage)\n\t\t\t\tdata.Labels().Set(\"containerID\", containerID)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Make sure container is removed even if test fails\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\n\t\t\t\t// Release the acquired port\n\t\t\t\tif portStr := data.Labels().Get(\"port\"); portStr != \"\" {\n\t\t\t\t\tport, _ := strconv.Atoi(portStr)\n\t\t\t\t\t_ = portlock.Release(port)\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: testContainerRmIptablesExecutor,\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t// Get the container ID from the label\n\t\t\t\tcontainerID := data.Labels().Get(\"containerID\")\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\t// Verify that the iptables output does not contain the container ID\n\t\t\t\t\tOutput: expect.DoesNotContain(containerID),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_remove_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRemoveContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcontainerID := data.Identifier()\n\t\thelpers.Fail(\"rm\", containerID)\n\n\t\t// FIXME: should (re-)evaluate this\n\t\t// `kill` seems to return before the container actually stops\n\t\thelpers.Ensure(\"stop\", containerID)\n\n\t\treturn helpers.Command(\"rm\", containerID)\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_remove_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRemoveHyperVContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.HyperV\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--isolation\", \"hyperv\", \"--name\", testutil.Identifier(t), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, testutil.Identifier(t))\n\n\t\tinspect := nerdtest.InspectContainer(helpers, testutil.Identifier(t))\n\t\t//check with HCS if the container is ineed a VM\n\t\tisHypervContainer, err := testutil.HyperVContainer(inspect)\n\t\tassert.NilError(t, err)\n\t\tassert.Assert(t, isHypervContainer, true)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", testutil.Identifier(t))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"should fail to remove when still running\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand:     test.Command(\"rm\", testutil.Identifier(t)),\n\t\t\tExpected:    test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should kill the container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand:     test.Command(\"kill\", testutil.Identifier(t)),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should remove the container when terminated\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand:     test.Command(\"rm\", testutil.Identifier(t)),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_rename.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc RenameCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"rename [flags] CONTAINER NEW_NAME\",\n\t\tArgs:              helpers.IsExactArgs(2),\n\t\tShort:             \"rename a container\",\n\t\tRunE:              renameAction,\n\t\tValidArgsFunction: renameShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc renameOptions(cmd *cobra.Command) (types.ContainerRenameOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerRenameOptions{}, err\n\t}\n\treturn types.ContainerRenameOptions{\n\t\tGOptions: globalOptions,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc renameAction(cmd *cobra.Command, args []string) error {\n\toptions, err := renameOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\treturn container.Rename(ctx, client, args[0], args[1], options)\n}\nfunc renameShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_rename_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRename(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := data.Identifier()\n\t\tdata.Labels().Set(\"containerName\", testContainerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", testContainerName, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName)\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName+\"_new\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"`rename` should work\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should have updated container name\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(testContainerName+\"_new\"))(data, helpers)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should fail to rename not existing container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should fail to rename to existing name\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName+\"_new\", testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRenameUpdateHosts(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := data.Identifier()\n\t\tdata.Labels().Set(\"containerName\", testContainerName)\n\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", testContainerName, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", testContainerName+\"_1\", testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainerName+\"_1\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName)\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName+\"_1\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName+\"_new\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"check '/etc/hosts' for sibling container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"exec\", testContainerName, \"cat\", \"/etc/hosts\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(testContainerName+\"_1\"))(data, helpers)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"rename container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"check '/etc/hosts' for renamed container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"exec\", testContainerName+\"_new\", \"cat\", \"/etc/hosts\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(testContainerName+\"_new\"))(data, helpers)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"check sibling's '/etc/hosts' for renamed container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"exec\", testContainerName+\"_1\", \"cat\", \"/etc/hosts\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(testContainerName+\"_new\"))(data, helpers)\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_rename_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRenameProcessContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := testutil.Identifier(t)\n\t\tdata.Labels().Set(\"containerName\", testContainerName)\n\n\t\thelpers.Ensure(\"run\", \"--isolation\", \"process\", \"-d\", \"--name\", testContainerName, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName)\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName+\"_new\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"`rename` should work\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should have updated container name\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(testContainerName+\"_new\"))(data, helpers)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should fail to rename not existing container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should fail to rename to existing name\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName+\"_new\", testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRenameHyperVContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = nerdtest.HyperV\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := testutil.Identifier(t)\n\t\tdata.Labels().Set(\"containerName\", testContainerName)\n\n\t\thelpers.Ensure(\"run\", \"--isolation\", \"hyperv\", \"-d\", \"--name\", testContainerName, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, testContainerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName)\n\t\thelpers.Anyhow(\"rm\", \"-f\", testContainerName+\"_new\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"`rename` should work\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should have updated container name\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"ps\", \"-a\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(testContainerName+\"_new\"))(data, helpers)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should fail to rename not existing container\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName, testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"`rename` should fail to rename to existing name\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\ttestContainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"rename\", testContainerName+\"_new\", testContainerName+\"_new\")\n\t\t\t},\n\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_restart.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc RestartCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"restart [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Restart one or more running containers\",\n\t\tRunE:              restartAction,\n\t\tValidArgsFunction: startShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().UintP(\"time\", \"t\", 10, \"Seconds to wait for stop before killing it\")\n\tcmd.Flags().StringP(\"signal\", \"s\", \"\", \"Signal to send to stop the container, before killing it\")\n\treturn cmd\n}\n\nfunc restartOptions(cmd *cobra.Command) (types.ContainerRestartOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerRestartOptions{}, err\n\t}\n\n\t// Call GlobalFlags function here\n\tnerdctlCmd, nerdctlArgs := helpers.GlobalFlags(cmd)\n\n\tvar timeout *time.Duration\n\tif cmd.Flags().Changed(\"time\") {\n\t\t// Seconds to wait for stop before killing it\n\t\ttimeValue, err := cmd.Flags().GetUint(\"time\")\n\t\tif err != nil {\n\t\t\treturn types.ContainerRestartOptions{}, err\n\t\t}\n\t\tt := time.Duration(timeValue) * time.Second\n\t\ttimeout = &t\n\t}\n\n\tvar signal string\n\tif cmd.Flags().Changed(\"signal\") {\n\t\t// Signal to send to stop the container, before killing it\n\t\tsig, err := cmd.Flags().GetString(\"signal\")\n\t\tif err != nil {\n\t\t\treturn types.ContainerRestartOptions{}, err\n\t\t}\n\t\tsignal = sig\n\t}\n\n\treturn types.ContainerRestartOptions{\n\t\tStdout:      cmd.OutOrStdout(),\n\t\tGOption:     globalOptions,\n\t\tTimeout:     timeout,\n\t\tSignal:      signal,\n\t\tNerdctlCmd:  nerdctlCmd,\n\t\tNerdctlArgs: nerdctlArgs,\n\t}, err\n}\n\nfunc restartAction(cmd *cobra.Command, args []string) error {\n\toptions, err := restartOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOption.Namespace, options.GOption.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Restart(ctx, client, args, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_restart_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRestart(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", testutil.Identifier(t), testutil.NginxAlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, testutil.Identifier(t))\n\n\t\tinspect := nerdtest.InspectContainer(helpers, testutil.Identifier(t))\n\t\tdata.Labels().Set(\"pid\", strconv.Itoa(inspect.State.Pid))\n\n\t\thelpers.Ensure(\"restart\", testutil.Identifier(t))\n\t\tnerdtest.EnsureContainerStarted(helpers, testutil.Identifier(t))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", testutil.Identifier(t))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"inspect\", testutil.Identifier(t))\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Container\n\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, 1, len(dc))\n\n\t\t\t\tassert.Assert(t, data.Labels().Get(\"pid\") != strconv.Itoa(dc[0].State.Pid))\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRestartPIDContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tbaseContainerName := testutil.Identifier(t)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", baseContainerName, testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, baseContainerName)\n\n\t\tsharedContainerName := fmt.Sprintf(\"%s-shared\", baseContainerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", sharedContainerName, fmt.Sprintf(\"--pid=container:%s\", baseContainerName), testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, sharedContainerName)\n\n\t\thelpers.Ensure(\"restart\", baseContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, baseContainerName)\n\t\thelpers.Ensure(\"restart\", sharedContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, sharedContainerName)\n\n\t\t// output format : <inode number> /proc/1/ns/pid\n\t\t// example output: 4026532581 /proc/1/ns/pid\n\t\tbasePSResult := helpers.Capture(\"exec\", baseContainerName, \"ls\", \"-Li\", \"/proc/1/ns/pid\")\n\t\tbaseOutput := strings.TrimSpace(basePSResult)\n\n\t\tdata.Labels().Set(\"baseContainerName\", baseContainerName)\n\t\tdata.Labels().Set(\"sharedContainerName\", sharedContainerName)\n\t\tdata.Labels().Set(\"baseOutput\", baseOutput)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"baseContainerName\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"sharedContainerName\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"sharedContainerName\"), \"ls\", \"-Li\", \"/proc/1/ns/pid\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Equal(t, strings.TrimSpace(stdout), data.Labels().Get(\"baseOutput\"))\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRestartIPCContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tconst shmSize = \"32m\"\n\t\tbaseContainerName := testutil.Identifier(t)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--shm-size\", shmSize, \"--ipc\", \"shareable\", \"--name\", baseContainerName, testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, baseContainerName)\n\n\t\tsharedContainerName := fmt.Sprintf(\"%s-shared\", baseContainerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", sharedContainerName, fmt.Sprintf(\"--ipc=container:%s\", baseContainerName), testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, sharedContainerName)\n\n\t\thelpers.Ensure(\"stop\", baseContainerName)\n\t\thelpers.Ensure(\"stop\", sharedContainerName)\n\n\t\thelpers.Ensure(\"restart\", baseContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, baseContainerName)\n\t\thelpers.Ensure(\"restart\", sharedContainerName)\n\t\tnerdtest.EnsureContainerStarted(helpers, sharedContainerName)\n\n\t\tbaseShmSizeResult := helpers.Capture(\"exec\", baseContainerName, \"/bin/grep\", \"shm\", \"/proc/self/mounts\")\n\t\tbaseOutput := strings.TrimSpace(baseShmSizeResult)\n\n\t\tdata.Labels().Set(\"baseContainerName\", baseContainerName)\n\t\tdata.Labels().Set(\"sharedContainerName\", sharedContainerName)\n\t\tdata.Labels().Set(\"baseOutput\", baseOutput)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"baseContainerName\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"sharedContainerName\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"sharedContainerName\"), \"/bin/grep\", \"shm\", \"/proc/self/mounts\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tassert.Equal(t, strings.TrimSpace(stdout), data.Labels().Get(\"baseOutput\"))\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRestartWithTime(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := testutil.Identifier(t)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", containerName, testutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\n\t\tinspect := nerdtest.InspectContainer(helpers, containerName)\n\t\tpid := inspect.State.Pid\n\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\tdata.Labels().Set(\"pid\", strconv.Itoa(pid))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"containerName\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tdata.Labels().Set(\"timePreRestart\", time.Now().Format(time.RFC3339))\n\t\treturn helpers.Command(\"restart\", \"-t\", \"5\", data.Labels().Get(\"containerName\"))\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\ttimePostRestart := time.Now()\n\t\t\t\ttimePreRestart, err := time.Parse(time.RFC3339, data.Labels().Get(\"timePreRestart\"))\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\t// ensure that stop took at least 5 seconds\n\t\t\t\tassert.Assert(t, timePostRestart.Sub(timePreRestart) >= time.Second*5)\n\n\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Labels().Get(\"containerName\"))\n\t\t\t\tassert.Assert(t, strconv.Itoa(inspect.State.Pid) != data.Labels().Get(\"pid\"))\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRestartWithSignal(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// FIXME: gomodjail signal handling is not working yet: https://github.com/AkihiroSuda/gomodjail/issues/51\n\ttestCase.Require = require.Not(nerdtest.Gomodjail)\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcmd := nerdtest.RunSigProxyContainer(nerdtest.SigUsr1, false, nil, data, helpers)\n\t\t// Capture the current pid\n\t\tdata.Labels().Set(\"oldpid\", strconv.Itoa(nerdtest.InspectContainer(helpers, data.Identifier()).State.Pid))\n\t\t// Send the signal\n\t\thelpers.Ensure(\"restart\", \"--signal\", \"SIGUSR1\", data.Identifier())\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\t// Check the container did indeed exit\n\t\t\tExitCode: 137,\n\t\t\tOutput: expect.All(\n\t\t\t\t// Check that we saw SIGUSR1 inside the container\n\t\t\t\texpect.Contains(nerdtest.SignalCaught),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t// Ensure the container was restarted\n\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t\t\t// Check the new pid is different\n\t\t\t\t\tnewpid := strconv.Itoa(nerdtest.InspectContainer(helpers, data.Identifier()).State.Pid)\n\t\t\t\t\tassert.Assert(helpers.T(), newpid != data.Labels().Get(\"oldpid\"))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/console\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/pkg/annotations\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signalutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/taskutil\"\n)\n\nconst (\n\ttiniInitBinary = \"tini\"\n)\n\nfunc RunCommand() *cobra.Command {\n\tshortHelp := \"Run a command in a new container. Optionally specify \\\"ipfs://\\\" or \\\"ipns://\\\" scheme to pull image from IPFS.\"\n\tlongHelp := shortHelp\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\tlongHelp += \"\\n\"\n\t\tlongHelp += \"WARNING: `nerdctl run` is experimental on Windows and currently broken (https://github.com/containerd/nerdctl/issues/28)\"\n\tcase \"freebsd\":\n\t\tlongHelp += \"\\n\"\n\t\tlongHelp += \"WARNING: `nerdctl run` is experimental on FreeBSD and currently requires `--net=none` (https://github.com/containerd/nerdctl/blob/main/docs/freebsd.md)\"\n\t}\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"run [flags] IMAGE [COMMAND] [ARG...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             shortHelp,\n\t\tLong:              longHelp,\n\t\tRunE:              runAction,\n\t\tValidArgsFunction: runShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\tcmd.Flags().SetInterspersed(false)\n\tsetCreateFlags(cmd)\n\n\tcmd.Flags().BoolP(\"detach\", \"d\", false, \"Run container in background and print container ID\")\n\tcmd.Flags().StringSliceP(\"attach\", \"a\", []string{}, \"Attach STDIN, STDOUT, or STDERR\")\n\n\treturn cmd\n}\n\nfunc setCreateFlags(cmd *cobra.Command) {\n\n\t// No \"-h\" alias for \"--help\", because \"-h\" for \"--hostname\".\n\tcmd.Flags().Bool(\"help\", false, \"show help\")\n\n\tcmd.Flags().BoolP(\"tty\", \"t\", false, \"Allocate a pseudo-TTY\")\n\tcmd.Flags().Bool(\"sig-proxy\", true, \"Proxy received signals to the process (default true)\")\n\tcmd.Flags().BoolP(\"interactive\", \"i\", false, \"Keep STDIN open even if not attached\")\n\tcmd.Flags().String(\"restart\", \"no\", `Restart policy to apply when a container exits (implemented values: \"no\"|\"always|on-failure:n|unless-stopped\")`)\n\tcmd.RegisterFlagCompletionFunc(\"restart\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"no\", \"always\", \"on-failure\", \"unless-stopped\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().Bool(\"rm\", false, \"Automatically remove the container when it exits\")\n\tcmd.Flags().String(\"pull\", \"missing\", `Pull image before running (\"always\"|\"missing\"|\"never\")`)\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Suppress the pull output\")\n\tcmd.RegisterFlagCompletionFunc(\"pull\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"always\", \"missing\", \"never\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"stop-signal\", \"SIGTERM\", \"Signal to stop a container\")\n\tcmd.Flags().Int(\"stop-timeout\", 0, \"Timeout (in seconds) to stop a container\")\n\tcmd.Flags().String(\"detach-keys\", consoleutil.DefaultDetachKeys, \"Override the default detach keys\")\n\n\t// #region for init process\n\tcmd.Flags().Bool(\"init\", false, \"Run an init process inside the container, Default to use tini\")\n\tcmd.Flags().String(\"init-binary\", tiniInitBinary, \"The custom binary to use as the init process\")\n\t// #endregion\n\n\t// #region platform flags\n\tcmd.Flags().String(\"platform\", \"\", \"Set platform (e.g. \\\"amd64\\\", \\\"arm64\\\")\") // not a slice, and there is no --all-platforms\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\t// #endregion\n\n\t// #region network flags\n\t// network (net) is defined as StringSlice, not StringArray, to allow specifying \"--network=cni1,cni2\"\n\tcmd.Flags().StringSlice(\"network\", []string{netutil.DefaultNetworkName}, `Connect a container to a network (\"bridge\"|\"host\"|\"none\"|\"container:<container>\"|\"ns:<path>\"|<CNI>)`)\n\tcmd.RegisterFlagCompletionFunc(\"network\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn completion.NetworkNames(cmd, []string{})\n\t})\n\tcmd.Flags().StringSlice(\"net\", []string{netutil.DefaultNetworkName}, `Connect a container to a network (\"bridge\"|\"host\"|\"none\"|\"container:<container>\"|\"ns:<path>\"|<CNI>)`)\n\tcmd.RegisterFlagCompletionFunc(\"net\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn completion.NetworkNames(cmd, []string{})\n\t})\n\t// dns is defined as StringSlice, not StringArray, to allow specifying \"--dns=1.1.1.1,8.8.8.8\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"dns\", nil, \"Set custom DNS servers\")\n\tcmd.Flags().StringSlice(\"dns-search\", nil, \"Set custom DNS search domains\")\n\t// We allow for both \"--dns-opt\" and \"--dns-option\", although the latter is the recommended way.\n\tcmd.Flags().StringSlice(\"dns-opt\", nil, \"Set DNS options\")\n\tcmd.Flags().StringSlice(\"dns-option\", nil, \"Set DNS options\")\n\t// publish is defined as StringSlice, not StringArray, to allow specifying \"--publish=80:80,443:443\" (compatible with Podman)\n\tcmd.Flags().StringSliceP(\"publish\", \"p\", nil, \"Publish a container's port(s) to the host\")\n\tcmd.Flags().String(\"ip\", \"\", \"IPv4 address to assign to the container\")\n\tcmd.Flags().String(\"ip6\", \"\", \"IPv6 address to assign to the container\")\n\tcmd.Flags().StringP(\"hostname\", \"h\", \"\", \"Container host name\")\n\tcmd.Flags().String(\"domainname\", \"\", \"Container domain name\")\n\tcmd.Flags().String(\"mac-address\", \"\", \"MAC address to assign to the container\")\n\t// #endregion\n\n\tcmd.Flags().String(\"ipc\", \"\", `IPC namespace to use (\"host\"|\"private\"|\"shareable\"|\"container:<container>\")`)\n\tcmd.RegisterFlagCompletionFunc(\"ipc\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\tif strings.HasPrefix(toComplete, \"container:\") {\n\t\t\tnames, directive := completion.ContainerNames(cmd, func(st containerd.ProcessStatus) bool {\n\t\t\t\treturn st == containerd.Running\n\t\t\t})\n\t\t\tvar candidates []string\n\t\t\tfor _, name := range names {\n\t\t\t\tcandidates = append(candidates, \"container:\"+name)\n\t\t\t}\n\t\t\treturn candidates, directive\n\t\t}\n\t\treturn []string{\"host\", \"private\", \"shareable\", \"container:\"}, cobra.ShellCompDirectiveNoSpace\n\t})\n\t// #region cgroups, namespaces, and ulimits flags\n\tcmd.Flags().Float64(\"cpus\", 0.0, \"Number of CPUs\")\n\tcmd.Flags().StringP(\"memory\", \"m\", \"\", \"Memory limit\")\n\tcmd.Flags().String(\"memory-reservation\", \"\", \"Memory soft limit\")\n\tcmd.Flags().String(\"memory-swap\", \"\", \"Swap limit equal to memory plus swap: '-1' to enable unlimited swap\")\n\tcmd.Flags().Int64(\"memory-swappiness\", -1, \"Tune container memory swappiness (0 to 100) (default -1)\")\n\tcmd.Flags().String(\"kernel-memory\", \"\", \"Kernel memory limit (deprecated)\")\n\tcmd.Flags().Bool(\"oom-kill-disable\", false, \"Disable OOM Killer\")\n\tcmd.Flags().Int(\"oom-score-adj\", 0, \"Tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)\")\n\tcmd.Flags().String(\"pid\", \"\", \"PID namespace to use\")\n\tcmd.Flags().String(\"uts\", \"\", \"UTS namespace to use\")\n\tcmd.RegisterFlagCompletionFunc(\"pid\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"host\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().Int64(\"pids-limit\", -1, \"Tune container pids limit (set -1 for unlimited)\")\n\tcmd.Flags().StringSlice(\"cgroup-conf\", nil, \"Configure cgroup v2 (key=value)\")\n\tcmd.Flags().String(\"cgroupns\", defaults.CgroupnsMode(), `Cgroup namespace to use, the default depends on the cgroup version (\"host\"|\"private\")`)\n\tcmd.Flags().String(\"cgroup-parent\", \"\", \"Optional parent cgroup for the container\")\n\tcmd.RegisterFlagCompletionFunc(\"cgroupns\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"host\", \"private\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"cpuset-cpus\", \"\", \"CPUs in which to allow execution (0-3, 0,1)\")\n\tcmd.Flags().String(\"cpuset-mems\", \"\", \"MEMs in which to allow execution (0-3, 0,1)\")\n\tcmd.Flags().Uint64(\"cpu-shares\", 0, \"CPU shares (relative weight)\")\n\tcmd.Flags().Int64(\"cpu-quota\", -1, \"Limit CPU CFS (Completely Fair Scheduler) quota\")\n\tcmd.Flags().Uint64(\"cpu-period\", 0, \"Limit CPU CFS (Completely Fair Scheduler) period\")\n\tcmd.Flags().Uint64(\"cpu-rt-period\", 0, \"Limit CPU real-time period in microseconds\")\n\tcmd.Flags().Uint64(\"cpu-rt-runtime\", 0, \"Limit CPU real-time runtime in microseconds\")\n\t// device is defined as StringSlice, not StringArray, to allow specifying \"--device=DEV1,DEV2\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"device\", nil, \"Add a host device to the container\")\n\t// ulimit is defined as StringSlice, not StringArray, to allow specifying \"--ulimit=ULIMIT1,ULIMIT2\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"ulimit\", nil, \"Ulimit options\")\n\tcmd.Flags().String(\"rdt-class\", \"\", \"Name of the RDT class (or CLOS) to associate the container with\")\n\t// #endregion\n\n\t// #region blkio flags\n\tcmd.Flags().Uint16(\"blkio-weight\", 0, \"Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)\")\n\tcmd.Flags().StringArray(\"blkio-weight-device\", []string{}, \"Block IO weight (relative device weight) (default [])\")\n\tcmd.Flags().StringArray(\"device-read-bps\", []string{}, \"Limit read rate (bytes per second) from a device (default [])\")\n\tcmd.Flags().StringArray(\"device-read-iops\", []string{}, \"Limit read rate (IO per second) from a device (default [])\")\n\tcmd.Flags().StringArray(\"device-write-bps\", []string{}, \"Limit write rate (bytes per second) to a device (default [])\")\n\tcmd.Flags().StringArray(\"device-write-iops\", []string{}, \"Limit write rate (IO per second) to a device (default [])\")\n\t// #endregion\n\n\t// user flags\n\tcmd.Flags().StringP(\"user\", \"u\", \"\", \"Username or UID (format: <name|uid>[:<group|gid>])\")\n\tcmd.Flags().String(\"umask\", \"\", \"Set the umask inside the container. Defaults to 0022\")\n\tcmd.Flags().StringSlice(\"group-add\", []string{}, \"Add additional groups to join\")\n\n\t// #region security flags\n\tcmd.Flags().StringArray(\"security-opt\", []string{}, \"Security options\")\n\tcmd.RegisterFlagCompletionFunc(\"security-opt\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\n\t\t\t\"seccomp=\", \"seccomp=\" + defaults.SeccompProfileName, \"seccomp=unconfined\",\n\t\t\t\"apparmor=\", \"apparmor=\" + defaults.AppArmorProfileName, \"apparmor=unconfined\",\n\t\t\t\"no-new-privileges\",\n\t\t\t\"systempaths=unconfined\",\n\t\t\t\"privileged-without-host-devices\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\t// cap-add and cap-drop are defined as StringSlice, not StringArray, to allow specifying \"--cap-add=CAP_SYS_ADMIN,CAP_NET_ADMIN\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"cap-add\", []string{}, \"Add Linux capabilities\")\n\tcmd.RegisterFlagCompletionFunc(\"cap-add\", capShellComplete)\n\tcmd.Flags().StringSlice(\"cap-drop\", []string{}, \"Drop Linux capabilities\")\n\tcmd.RegisterFlagCompletionFunc(\"cap-drop\", capShellComplete)\n\tcmd.Flags().Bool(\"privileged\", false, \"Give extended privileges to this container\")\n\tcmd.Flags().String(\"systemd\", \"false\", \"Allow running systemd in this container (default: false)\")\n\t// #endregion\n\n\t// #region runtime flags\n\tcmd.Flags().String(\"runtime\", defaults.Runtime, \"Runtime to use for this container, e.g. \\\"crun\\\", or \\\"io.containerd.runsc.v1\\\"\")\n\t// sysctl needs to be StringArray, not StringSlice, to prevent \"foo=foo1,foo2\" from being split to {\"foo=foo1\", \"foo2\"}\n\tcmd.Flags().StringArray(\"sysctl\", nil, \"Sysctl options\")\n\t// gpus needs to be StringArray, not StringSlice, to prevent \"capabilities=utility,device=DEV\" from being split to {\"capabilities=utility\", \"device=DEV\"}\n\tcmd.Flags().StringArray(\"gpus\", nil, \"GPU devices to add to the container ('all' to pass all GPUs)\")\n\tcmd.RegisterFlagCompletionFunc(\"gpus\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"all\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\t// #endregion\n\n\t// #region mount flags\n\t// volume needs to be StringArray, not StringSlice, to prevent \"/foo:/foo:ro,Z\" from being split to {\"/foo:/foo:ro\", \"Z\"}\n\tcmd.Flags().StringArrayP(\"volume\", \"v\", nil, \"Bind mount a volume\")\n\t// tmpfs needs to be StringArray, not StringSlice, to prevent \"/foo:size=64m,exec\" from being split to {\"/foo:size=64m\", \"exec\"}\n\tcmd.Flags().StringArray(\"tmpfs\", nil, \"Mount a tmpfs directory\")\n\tcmd.Flags().StringArray(\"mount\", nil, \"Attach a filesystem mount to the container\")\n\t// volumes-from needs to be StringArray, not StringSlice, to prevent \"id1,id2\" from being split to {\"id1\", \"id2\"} (compatible with Docker)\n\tcmd.Flags().StringArray(\"volumes-from\", nil, \"Mount volumes from the specified container(s)\")\n\t// #endregion\n\n\t// rootfs flags\n\tcmd.Flags().Bool(\"read-only\", false, \"Mount the container's root filesystem as read only\")\n\t// rootfs flags (from Podman)\n\tcmd.Flags().Bool(\"rootfs\", false, \"The first argument is not an image but the rootfs to the exploded container\")\n\n\t// Health check flags\n\tcmd.Flags().String(\"health-cmd\", \"\", \"Command to run to check health\")\n\tcmd.Flags().Duration(\"health-interval\", 0, \"Time between running the check (default: 30s)\")\n\tcmd.Flags().Duration(\"health-timeout\", 0, \"Maximum time to allow one check to run (default: 30s)\")\n\tcmd.Flags().Int(\"health-retries\", 0, \"Consecutive failures needed to report unhealthy (default: 3)\")\n\tcmd.Flags().Duration(\"health-start-period\", 0, \"Start period for the container to initialize before starting health-retries countdown\")\n\tcmd.Flags().Bool(\"no-healthcheck\", false, \"Disable any container-specified HEALTHCHECK\")\n\n\t// #region env flags\n\t// entrypoint needs to be StringArray, not StringSlice, to prevent \"FOO=foo1,foo2\" from being split to {\"FOO=foo1\", \"foo2\"}\n\t// entrypoint StringArray is an internal implementation to support `nerdctl compose` entrypoint yaml filed with multiple strings\n\t// users are not expected to specify multiple --entrypoint flags manually.\n\tcmd.Flags().StringArray(\"entrypoint\", nil, \"Overwrite the default ENTRYPOINT of the image\")\n\tcmd.Flags().StringP(\"workdir\", \"w\", \"\", \"Working directory inside the container\")\n\t// env needs to be StringArray, not StringSlice, to prevent \"FOO=foo1,foo2\" from being split to {\"FOO=foo1\", \"foo2\"}\n\tcmd.Flags().StringArrayP(\"env\", \"e\", nil, \"Set environment variables\")\n\t// add-host is defined as StringSlice, not StringArray, to allow specifying \"--add-host=HOST1:IP1,HOST2:IP2\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"add-host\", nil, \"Add a custom host-to-IP mapping (host:ip)\")\n\t// env-file is defined as StringSlice, not StringArray, to allow specifying \"--env-file=FILE1,FILE2\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"env-file\", nil, \"Set environment variables from file\")\n\n\t// #region metadata flags\n\tcmd.Flags().String(\"name\", \"\", \"Assign a name to the container\")\n\t// label needs to be StringArray, not StringSlice, to prevent \"foo=foo1,foo2\" from being split to {\"foo=foo1\", \"foo2\"}\n\tcmd.Flags().StringArrayP(\"label\", \"l\", nil, \"Set metadata on container\")\n\t// annotation needs to be StringArray, not StringSlice, to prevent \"foo=foo1,foo2\" from being split to {\"foo=foo1\", \"foo2\"}\n\tcmd.Flags().StringArray(\"annotation\", nil, \"Add an annotation to the container (passed through to the OCI runtime)\")\n\tcmd.RegisterFlagCompletionFunc(\"annotation\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn annotations.ShellCompletions, cobra.ShellCompDirectiveNoFileComp\n\t})\n\n\t// label-file is defined as StringSlice, not StringArray, to allow specifying \"--env-file=FILE1,FILE2\" (compatible with Podman)\n\tcmd.Flags().StringSlice(\"label-file\", nil, \"Set metadata on container from file\")\n\tcmd.Flags().String(\"cidfile\", \"\", \"Write the container ID to the file\")\n\t// #endregion\n\n\t// #region logging flags\n\t// log-opt needs to be StringArray, not StringSlice, to prevent \"env=os,customer\" from being split to {\"env=os\", \"customer\"}\n\tcmd.Flags().String(\"log-driver\", \"json-file\", \"Logging driver for the container. Default is json-file. It also supports logURI (eg: --log-driver binary://<path>)\")\n\tcmd.RegisterFlagCompletionFunc(\"log-driver\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn logging.Drivers(), cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringArray(\"log-opt\", nil, \"Log driver options\")\n\t// #endregion\n\n\t// shared memory flags\n\tcmd.Flags().String(\"shm-size\", \"\", \"Size of /dev/shm\")\n\tcmd.Flags().String(\"pidfile\", \"\", \"file path to write the task's pid\")\n\n\t// #region verify flags\n\tcmd.Flags().String(\"verify\", \"none\", \"Verify the image (none|cosign|notation)\")\n\tcmd.RegisterFlagCompletionFunc(\"verify\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"none\", \"cosign\", \"notation\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"cosign-key\", \"\", \"Path to the public key file, KMS, URI or Kubernetes Secret for --verify=cosign\")\n\tcmd.Flags().String(\"cosign-certificate-identity\", \"\", \"The identity expected in a valid Fulcio certificate for --verify=cosign. Valid values include email address, DNS names, IP addresses, and URIs. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\")\n\tcmd.Flags().String(\"cosign-certificate-identity-regexp\", \"\", \"A regular expression alternative to --cosign-certificate-identity for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\")\n\tcmd.Flags().String(\"cosign-certificate-oidc-issuer\", \"\", \"The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\")\n\tcmd.Flags().String(\"cosign-certificate-oidc-issuer-regexp\", \"\", \"A regular expression alternative to --certificate-oidc-issuer for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\")\n\t// #endregion\n\n\tcmd.Flags().String(\"ipfs-address\", \"\", \"multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\")\n\tcmd.Flags().String(\"isolation\", \"default\", \"Specify isolation technology for container. On Linux the only valid value is default. Windows options are host, process and hyperv with process isolation as the default\")\n\tcmd.RegisterFlagCompletionFunc(\"isolation\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\treturn []string{\"default\", \"host\", \"process\", \"hyperv\"}, cobra.ShellCompDirectiveNoFileComp\n\t\t}\n\t\treturn []string{\"default\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"userns\", \"\", \"Specify host to disable userns-remap\")\n\n}\n\nfunc processCreateCommandFlagsInRun(cmd *cobra.Command) (types.ContainerCreateOptions, error) {\n\topt, err := createOptions(cmd)\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\n\topt.InRun = true\n\n\topt.SigProxy, err = cmd.Flags().GetBool(\"sig-proxy\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Interactive, err = cmd.Flags().GetBool(\"interactive\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Detach, err = cmd.Flags().GetBool(\"detach\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.DetachKeys, err = cmd.Flags().GetString(\"detach-keys\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\topt.Attach, err = cmd.Flags().GetStringSlice(\"attach\")\n\tif err != nil {\n\t\treturn opt, err\n\t}\n\n\tvalidAttachFlag := true\n\tfor i, str := range opt.Attach {\n\t\topt.Attach[i] = strings.ToUpper(str)\n\n\t\tif opt.Attach[i] != \"STDIN\" && opt.Attach[i] != \"STDOUT\" && opt.Attach[i] != \"STDERR\" {\n\t\t\tvalidAttachFlag = false\n\t\t}\n\t}\n\tif !validAttachFlag {\n\t\treturn opt, fmt.Errorf(\"invalid stream specified with -a flag. Valid streams are STDIN, STDOUT, and STDERR\")\n\t}\n\n\treturn opt, nil\n}\n\n// runAction is heavily based on ctr implementation:\n// https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/run/run.go\nfunc runAction(cmd *cobra.Command, args []string) error {\n\tvar isDetached bool\n\n\tcreateOpt, err := processCreateCommandFlagsInRun(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClientWithPlatform(cmd.Context(), createOpt.GOptions.Namespace, createOpt.GOptions.Address, createOpt.Platform)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tif createOpt.Rm && createOpt.Detach {\n\t\treturn errors.New(\"flags -d and --rm cannot be specified together\")\n\t}\n\n\tif len(createOpt.Attach) > 0 && createOpt.Detach {\n\t\treturn errors.New(\"flags -d and -a cannot be specified together\")\n\t}\n\n\tnetFlags, err := loadNetworkFlags(cmd, createOpt.GOptions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load networking flags: %w\", err)\n\t}\n\n\tnetManager, err := containerutil.NewNetworkingOptionsManager(createOpt.GOptions, netFlags, client)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc, gc, err := container.Create(ctx, client, args, netManager, createOpt)\n\tif err != nil {\n\t\tif gc != nil {\n\t\t\tdefer gc()\n\t\t}\n\t\treturn err\n\t}\n\t// defer setting `nerdctl/error` label in case of error\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tcontainerutil.UpdateErrorLabel(ctx, c, err)\n\t\t}\n\t}()\n\n\tid := c.ID()\n\tif createOpt.Rm {\n\t\tdefer func() {\n\t\t\tif isDetached {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := container.RemoveContainer(ctx, c, createOpt.GOptions, true, true, client); err != nil {\n\t\t\t\tlog.L.WithError(err).Warnf(\"failed to remove container %s\", id)\n\t\t\t}\n\t\t}()\n\t}\n\n\tvar con console.Console\n\tif createOpt.TTY && !createOpt.Detach {\n\t\tcon, err = consoleutil.Current()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer con.Reset()\n\t\tif _, err := term.MakeRaw(int(con.Fd())); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlab, err := c.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogURI := lab[labels.LogURI]\n\tdetachC := make(chan struct{})\n\ttask, err := taskutil.NewTask(ctx, client, c, taskutil.TaskOptions{\n\t\tAttachStreamOpt: createOpt.Attach,\n\t\tIsInteractive:   createOpt.Interactive,\n\t\tIsTerminal:      createOpt.TTY,\n\t\tIsDetach:        createOpt.Detach,\n\t\tCon:             con,\n\t\tLogURI:          logURI,\n\t\tDetachKeys:      createOpt.DetachKeys,\n\t\tNamespace:       createOpt.GOptions.Namespace,\n\t\tDetachC:         detachC,\n\t\tCheckpointDir:   \"\",\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatusC, err := task.Wait(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := task.Start(ctx); err != nil {\n\t\treturn err\n\t}\n\n\t// Setup container healthchecks.\n\tif err := healthcheck.CreateTimer(ctx, c, (*config.Config)(&createOpt.GOptions), createOpt.NerdctlCmd, createOpt.NerdctlArgs); err != nil {\n\t\treturn fmt.Errorf(\"failed to create healthcheck timer: %w\", err)\n\t}\n\tif err := healthcheck.StartTimer(ctx, c, (*config.Config)(&createOpt.GOptions)); err != nil {\n\t\treturn fmt.Errorf(\"failed to start healthcheck timer: %w\", err)\n\t}\n\n\tif createOpt.Detach {\n\t\tfmt.Fprintln(createOpt.Stdout, id)\n\t\treturn nil\n\t}\n\tif createOpt.TTY {\n\t\tif err := consoleutil.HandleConsoleResize(ctx, task, con); err != nil {\n\t\t\tlog.L.WithError(err).Error(\"console resize\")\n\t\t}\n\t} else {\n\t\tif createOpt.SigProxy {\n\t\t\tsigC := signalutil.ForwardAllSignals(ctx, task)\n\t\t\tdefer signalutil.StopCatch(sigC)\n\t\t}\n\t}\n\n\tselect {\n\t// io.Wait() would return when either 1) the user detaches from the container OR 2) the container is about to exit.\n\t//\n\t// If we replace the `select` block with io.Wait() and\n\t// directly use task.Status() to check the status of the container after io.Wait() returns,\n\t// it can still be running even though the container is about to exit (somehow especially for Windows).\n\t//\n\t// As a result, we need a separate detachC to distinguish from the 2 cases mentioned above.\n\tcase <-detachC:\n\t\tio := task.IO()\n\t\tif io == nil {\n\t\t\treturn errors.New(\"got a nil IO from the task\")\n\t\t}\n\t\tio.Wait()\n\t\tisDetached = true\n\tcase status := <-statusC:\n\t\tif createOpt.Rm {\n\t\t\tif _, taskDeleteErr := task.Delete(ctx); taskDeleteErr != nil {\n\t\t\t\tlog.L.Error(taskDeleteErr)\n\t\t\t}\n\t\t}\n\t\tcode, _, err := status.Result()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif code != 0 {\n\t\t\treturn errutil.NewExitCoderErr(int(code))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tif len(args) == 0 {\n\t\treturn completion.ImageNames(cmd)\n\t}\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_cgroup_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/cgroups/v3\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/continuity/testutil/loopback\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunCgroupV2(t *testing.T) {\n\tt.Parallel()\n\tif cgroups.Mode() != cgroups.Unified {\n\t\tt.Skip(\"test requires cgroup v2\")\n\t}\n\tbase := testutil.NewBase(t)\n\tinfo := base.Info()\n\tswitch info.CgroupDriver {\n\tcase \"none\", \"\":\n\t\tt.Skip(\"test requires cgroup driver\")\n\t}\n\n\tif !info.MemoryLimit {\n\t\tt.Skip(\"test requires MemoryLimit\")\n\t}\n\tif !info.SwapLimit {\n\t\tt.Skip(\"test requires SwapLimit\")\n\t}\n\tif !info.CPUSet {\n\t\tt.Skip(\"test requires CPUSet\")\n\t}\n\tif !info.PidsLimit {\n\t\tt.Skip(\"test requires PidsLimit\")\n\t}\n\tconst expected1 = `42000 100000\n44040192\n44040192\n42\n0-1\n0\n`\n\tconst expected2 = `42000 100000\n44040192\n60817408\n6291456\n42\n0-1\n0\n`\n\n\tbase.Cmd(\"run\", \"--rm\",\n\t\t\"--cpus\", \"0.42\", \"--cpuset-mems\", \"0\",\n\t\t\"--memory\", \"42m\",\n\t\t\"--pids-limit\", \"42\",\n\t\t\"--cpuset-cpus\", \"0-1\",\n\t\t\"-w\", \"/sys/fs/cgroup\", testutil.AlpineImage,\n\t\t\"cat\", \"cpu.max\", \"memory.max\", \"memory.swap.max\",\n\t\t\"pids.max\", \"cpuset.cpus\", \"cpuset.mems\").AssertOutExactly(expected1)\n\tbase.Cmd(\"run\", \"--rm\",\n\t\t\"--cpu-quota\", \"42000\", \"--cpuset-mems\", \"0\",\n\t\t\"--cpu-period\", \"100000\", \"--memory\", \"42m\", \"--memory-reservation\", \"6m\", \"--memory-swap\", \"100m\",\n\t\t\"--pids-limit\", \"42\", \"--cpuset-cpus\", \"0-1\",\n\t\t\"-w\", \"/sys/fs/cgroup\", testutil.AlpineImage,\n\t\t\"cat\", \"cpu.max\", \"memory.max\", \"memory.swap.max\", \"memory.low\", \"pids.max\",\n\t\t\"cpuset.cpus\", \"cpuset.mems\").AssertOutExactly(expected2)\n\n\tbase.Cmd(\"run\", \"--name\", testutil.Identifier(t)+\"-testUpdate1\", \"-w\", \"/sys/fs/cgroup\", \"-d\",\n\t\ttestutil.AlpineImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", testutil.Identifier(t)+\"-testUpdate1\").Run()\n\tupdate := []string{\"update\", \"--cpu-quota\", \"42000\", \"--cpuset-mems\", \"0\", \"--cpu-period\", \"100000\",\n\t\t\"--memory\", \"42m\",\n\t\t\"--pids-limit\", \"42\", \"--cpuset-cpus\", \"0-1\"}\n\tif nerdtest.IsDocker() && info.CgroupVersion == \"2\" && info.SwapLimit {\n\t\t// Workaround for Docker with cgroup v2:\n\t\t// > Error response from daemon: Cannot update container 67c13276a13dd6a091cdfdebb355aa4e1ecb15fbf39c2b5c9abee89053e88fce:\n\t\t// > Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time\n\t\tupdate = append(update, \"--memory-swap=84m\")\n\t}\n\tupdate = append(update, testutil.Identifier(t)+\"-testUpdate1\")\n\tbase.Cmd(update...).AssertOK()\n\tbase.Cmd(\"exec\", testutil.Identifier(t)+\"-testUpdate1\",\n\t\t\"cat\", \"cpu.max\", \"memory.max\", \"memory.swap.max\",\n\t\t\"pids.max\", \"cpuset.cpus\", \"cpuset.mems\").AssertOutExactly(expected1)\n\n\tdefer base.Cmd(\"rm\", \"-f\", testutil.Identifier(t)+\"-testUpdate2\").Run()\n\tbase.Cmd(\"run\", \"--name\", testutil.Identifier(t)+\"-testUpdate2\", \"-w\", \"/sys/fs/cgroup\", \"-d\",\n\t\ttestutil.AlpineImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tbase.EnsureContainerStarted(testutil.Identifier(t) + \"-testUpdate2\")\n\n\tbase.Cmd(\"update\", \"--cpu-quota\", \"42000\", \"--cpuset-mems\", \"0\", \"--cpu-period\", \"100000\",\n\t\t\"--memory\", \"42m\", \"--memory-reservation\", \"6m\", \"--memory-swap\", \"100m\",\n\t\t\"--pids-limit\", \"42\", \"--cpuset-cpus\", \"0-1\",\n\t\ttestutil.Identifier(t)+\"-testUpdate2\").AssertOK()\n\tbase.Cmd(\"exec\", testutil.Identifier(t)+\"-testUpdate2\",\n\t\t\"cat\", \"cpu.max\", \"memory.max\", \"memory.swap.max\", \"memory.low\",\n\t\t\"pids.max\", \"cpuset.cpus\", \"cpuset.mems\").AssertOutExactly(expected2)\n\tbase.Cmd(\"run\", \"--rm\", \"--security-opt\", \"writable-cgroups=true\", testutil.AlpineImage, \"mkdir\", \"/sys/fs/cgroup/foo\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"--security-opt\", \"writable-cgroups=false\", testutil.AlpineImage, \"mkdir\", \"/sys/fs/cgroup/foo\").AssertFail()\n\tbase.Cmd(\"run\", \"--rm\", testutil.AlpineImage, \"mkdir\", \"/sys/fs/cgroup/foo\").AssertFail()\n}\n\nfunc TestRunCgroupV1(t *testing.T) {\n\tt.Parallel()\n\tswitch cgroups.Mode() {\n\tcase cgroups.Legacy, cgroups.Hybrid:\n\tdefault:\n\t\tt.Skip(\"test requires cgroup v1\")\n\t}\n\tbase := testutil.NewBase(t)\n\tinfo := base.Info()\n\tswitch info.CgroupDriver {\n\tcase \"none\", \"\":\n\t\tt.Skip(\"test requires cgroup driver\")\n\t}\n\tif !info.MemoryLimit {\n\t\tt.Skip(\"test requires MemoryLimit\")\n\t}\n\tif !info.CPUShares {\n\t\tt.Skip(\"test requires CPUShares\")\n\t}\n\tif !info.CPUSet {\n\t\tt.Skip(\"test requires CPUSet\")\n\t}\n\tif !info.PidsLimit {\n\t\tt.Skip(\"test requires PidsLimit\")\n\t}\n\tquota := \"/sys/fs/cgroup/cpu/cpu.cfs_quota_us\"\n\tperiod := \"/sys/fs/cgroup/cpu/cpu.cfs_period_us\"\n\tcpusetMems := \"/sys/fs/cgroup/cpuset/cpuset.mems\"\n\tmemoryLimit := \"/sys/fs/cgroup/memory/memory.limit_in_bytes\"\n\tmemoryReservation := \"/sys/fs/cgroup/memory/memory.soft_limit_in_bytes\"\n\tmemorySwap := \"/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes\"\n\tmemorySwappiness := \"/sys/fs/cgroup/memory/memory.swappiness\"\n\tpidsLimit := \"/sys/fs/cgroup/pids/pids.max\"\n\tcpuShare := \"/sys/fs/cgroup/cpu/cpu.shares\"\n\tcpusetCpus := \"/sys/fs/cgroup/cpuset/cpuset.cpus\"\n\n\tconst expected = \"42000\\n100000\\n0\\n44040192\\n6291456\\n104857600\\n0\\n42\\n2000\\n0-1\\n\"\n\tbase.Cmd(\"run\", \"--rm\", \"--cpus\", \"0.42\", \"--cpuset-mems\", \"0\", \"--memory\", \"42m\", \"--memory-reservation\", \"6m\", \"--memory-swap\", \"100m\", \"--memory-swappiness\", \"0\", \"--pids-limit\", \"42\", \"--cpu-shares\", \"2000\", \"--cpuset-cpus\", \"0-1\", testutil.AlpineImage, \"cat\", quota, period, cpusetMems, memoryLimit, memoryReservation, memorySwap, memorySwappiness, pidsLimit, cpuShare, cpusetCpus).AssertOutExactly(expected)\n\tbase.Cmd(\"run\", \"--rm\", \"--cpu-quota\", \"42000\", \"--cpu-period\", \"100000\", \"--cpuset-mems\", \"0\", \"--memory\", \"42m\", \"--memory-reservation\", \"6m\", \"--memory-swap\", \"100m\", \"--memory-swappiness\", \"0\", \"--pids-limit\", \"42\", \"--cpu-shares\", \"2000\", \"--cpuset-cpus\", \"0-1\", testutil.AlpineImage, \"cat\", quota, period, cpusetMems, memoryLimit, memoryReservation, memorySwap, memorySwappiness, pidsLimit, cpuShare, cpusetCpus).AssertOutExactly(expected)\n\tbase.Cmd(\"run\", \"--rm\", \"--security-opt\", \"writable-cgroups=true\", testutil.AlpineImage, \"mkdir\", \"/sys/fs/cgroup/pids/foo\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"--security-opt\", \"writable-cgroups=false\", testutil.AlpineImage, \"mkdir\", \"/sys/fs/cgroup/pids/foo\").AssertFail()\n\tbase.Cmd(\"run\", \"--rm\", testutil.AlpineImage, \"mkdir\", \"/sys/fs/cgroup/pids/foo\").AssertFail()\n}\n\n// TestIssue3781 tests https://github.com/containerd/nerdctl/issues/3781\nfunc TestIssue3781(t *testing.T) {\n\tt.Parallel()\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\tbase := testutil.NewBase(t)\n\tinfo := base.Info()\n\tswitch info.CgroupDriver {\n\tcase \"none\", \"\":\n\t\tt.Skip(\"test requires cgroup driver\")\n\t}\n\tcontainerName := testutil.Identifier(t)\n\tbase.Cmd(\"run\", \"-d\", \"--name\", containerName, testutil.AlpineImage, \"sleep\", \"infinity\").AssertOK()\n\tdefer func() {\n\t\tbase.Cmd(\"rm\", \"-f\", containerName)\n\t}()\n\tbase.Cmd(\"update\", \"--cpuset-cpus\", \"0-1\", containerName).AssertOK()\n\taddr := base.ContainerdAddress()\n\tclient, err := containerd.New(addr, containerd.WithDefaultNamespace(testutil.Namespace))\n\tassert.NilError(base.T, err)\n\tctx := context.Background()\n\n\t// get container id by container name.\n\tvar cid string\n\tvar args []string\n\targs = append(args, containerName)\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tcid = found.Container.ID()\n\t\t\treturn nil\n\t\t},\n\t}\n\terr = walker.WalkAll(ctx, args, true)\n\tassert.NilError(base.T, err)\n\n\tcontainer, err := client.LoadContainer(ctx, cid)\n\tassert.NilError(base.T, err)\n\tspec, err := container.Spec(ctx)\n\tassert.NilError(base.T, err)\n\tassert.Equal(t, spec.Linux.Resources.Pids == nil, true)\n}\n\nfunc TestRunDevice(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Rootful\n\n\tconst n = 3\n\tlo := make([]*loopback.Loopback, n)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\tvar err error\n\t\t\tlo[i], err = loopback.New(4096)\n\t\t\tassert.NilError(t, err)\n\t\t\tt.Logf(\"lo[%d] = %+v\", i, lo[i])\n\t\t\tloContent := fmt.Sprintf(\"lo%d-content\", i)\n\t\t\tassert.NilError(t, os.WriteFile(lo[i].Device, []byte(loContent), 0o700))\n\t\t\tdata.Labels().Set(\"loContent\"+strconv.Itoa(i), loContent)\n\t\t}\n\n\t\t// lo0 is readable but not writable.\n\t\t// lo1 is readable and writable\n\t\t// lo2 is not accessible.\n\t\thelpers.Ensure(\"run\",\n\t\t\t\"-d\",\n\t\t\t\"--name\", data.Identifier(),\n\t\t\t\"--device\", lo[0].Device+\":r\",\n\t\t\t\"--device\", lo[1].Device,\n\t\t\ttestutil.AlpineImage, \"sleep\", nerdtest.Infinity)\n\t\tdata.Labels().Set(\"id\", data.Identifier())\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tfor i := 0; i < n; i++ {\n\t\t\tif lo[i] != nil {\n\t\t\t\t_ = lo[i].Close()\n\t\t\t}\n\t\t}\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"can read lo0\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"id\"), \"cat\", lo[0].Device)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"locontent0\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"cannot write lo0\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"id\"), \"sh\", \"-ec\", \"echo -n \\\"overwritten-lo1-content\\\">\"+lo[0].Device)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"cannot read lo2\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"id\"), \"cat\", lo[2].Device)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"can read lo1\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"id\"), \"cat\", lo[1].Device)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"locontent1\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"can write lo1 and read back updated value\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"id\"), \"sh\", \"-ec\", \"echo -n \\\"overwritten-lo1-content\\\">\"+lo[1].Device)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, func(stdout string, t tig.T) {\n\t\t\t\tlo1Read, err := os.ReadFile(lo[1].Device)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, string(bytes.Trim(lo1Read, \"\\x00\")), \"overwritten-lo1-content\")\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestParseDevice(t *testing.T) {\n\tt.Parallel()\n\ttype testCase struct {\n\t\ts                     string\n\t\texpectedDevPath       string\n\t\texpectedContainerPath string\n\t\texpectedMode          string\n\t\terr                   string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\ts:                     \"/dev/sda1\",\n\t\t\texpectedDevPath:       \"/dev/sda1\",\n\t\t\texpectedContainerPath: \"/dev/sda1\",\n\t\t\texpectedMode:          \"rwm\",\n\t\t},\n\t\t{\n\t\t\ts:                     \"/dev/sda2:r\",\n\t\t\texpectedDevPath:       \"/dev/sda2\",\n\t\t\texpectedContainerPath: \"/dev/sda2\",\n\t\t\texpectedMode:          \"r\",\n\t\t},\n\t\t{\n\t\t\ts:                     \"/dev/sda3:rw\",\n\t\t\texpectedDevPath:       \"/dev/sda3\",\n\t\t\texpectedContainerPath: \"/dev/sda3\",\n\t\t\texpectedMode:          \"rw\",\n\t\t},\n\t\t{\n\t\t\ts:   \"sda4\",\n\t\t\terr: \"not an absolute path\",\n\t\t},\n\t\t{\n\t\t\ts:                     \"/dev/sda5:/dev/sda5\",\n\t\t\texpectedDevPath:       \"/dev/sda5\",\n\t\t\texpectedContainerPath: \"/dev/sda5\",\n\t\t\texpectedMode:          \"rwm\",\n\t\t},\n\t\t{\n\t\t\ts:                     \"/dev/sda6:/dev/foo6\",\n\t\t\texpectedDevPath:       \"/dev/sda6\",\n\t\t\texpectedContainerPath: \"/dev/foo6\",\n\t\t\texpectedMode:          \"rwm\",\n\t\t},\n\t\t{\n\t\t\ts:   \"/dev/sda7:/dev/sda7:rwmx\",\n\t\t\terr: \"unexpected rune\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Log(tc.s)\n\t\tdevPath, containerPath, mode, err := container.ParseDevice(tc.s)\n\t\tif tc.err == \"\" {\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, tc.expectedDevPath, devPath)\n\t\t\tassert.Equal(t, tc.expectedContainerPath, containerPath)\n\t\t\tassert.Equal(t, tc.expectedMode, mode)\n\t\t} else {\n\t\t\tassert.ErrorContains(t, err, tc.err)\n\t\t}\n\t}\n}\n\nfunc TestRunCgroupConf(t *testing.T) {\n\tt.Parallel()\n\tif cgroups.Mode() != cgroups.Unified {\n\t\tt.Skip(\"test requires cgroup v2\")\n\t}\n\ttestutil.DockerIncompatible(t) // Docker lacks --cgroup-conf\n\tbase := testutil.NewBase(t)\n\tinfo := base.Info()\n\tswitch info.CgroupDriver {\n\tcase \"none\", \"\":\n\t\tt.Skip(\"test requires cgroup driver\")\n\t}\n\tif !info.MemoryLimit {\n\t\tt.Skip(\"test requires MemoryLimit\")\n\t}\n\tbase.Cmd(\"run\", \"--rm\", \"--cgroup-conf\", \"memory.high=33554432\", \"-w\", \"/sys/fs/cgroup\", testutil.AlpineImage,\n\t\t\"cat\", \"memory.high\").AssertOutExactly(\"33554432\\n\")\n}\n\nfunc TestRunCgroupParent(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tinfo := base.Info()\n\tswitch info.CgroupDriver {\n\tcase \"none\", \"\":\n\t\tt.Skip(\"test requires cgroup driver\")\n\t}\n\n\tcontainerName := testutil.Identifier(t)\n\tt.Logf(\"Using %q cgroup driver\", info.CgroupDriver)\n\n\tparent := \"/foobarbaz\"\n\tif info.CgroupDriver == \"systemd\" {\n\t\t// Path separators aren't allowed in systemd path. runc\n\t\t// explicitly checks for this.\n\t\t// https://github.com/opencontainers/runc/blob/016a0d29d1750180b2a619fc70d6fe0d80111be0/libcontainer/cgroups/systemd/common.go#L65-L68\n\t\tparent = \"foobarbaz.slice\"\n\t}\n\n\ttearDown := func() {\n\t\tbase.Cmd(\"rm\", \"-f\", containerName).Run()\n\t}\n\n\ttearDown()\n\tt.Cleanup(tearDown)\n\n\t// cgroup2 without host cgroup ns will just output 0::/ which doesn't help much to verify\n\t// we got our expected path. This approach should work for both cgroup1 and 2, there will\n\t// just be many more entries for cgroup1 as there'll be an entry per controller.\n\tbase.Cmd(\n\t\t\"run\",\n\t\t\"-d\",\n\t\t\"--name\",\n\t\tcontainerName,\n\t\t\"--cgroupns=host\",\n\t\t\"--cgroup-parent\", parent,\n\t\ttestutil.AlpineImage,\n\t\t\"sleep\",\n\t\t\"infinity\",\n\t).AssertOK()\n\n\tid := base.InspectContainer(containerName).ID\n\texpected := filepath.Join(parent, id)\n\tif info.CgroupDriver == \"systemd\" {\n\t\texpected = filepath.Join(parent, fmt.Sprintf(\"nerdctl-%s\", id))\n\t\tif nerdtest.IsDocker() {\n\t\t\texpected = filepath.Join(parent, fmt.Sprintf(\"docker-%s\", id))\n\t\t}\n\t}\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"/proc/self/cgroup\").AssertOutContains(expected)\n}\n\nfunc TestRunBlkioWeightCgroupV2(t *testing.T) {\n\tt.Parallel()\n\tif cgroups.Mode() != cgroups.Unified {\n\t\tt.Skip(\"test requires cgroup v2\")\n\t}\n\tif _, err := os.Stat(\"/sys/module/bfq\"); err != nil {\n\t\tt.Skipf(\"test requires \\\"bfq\\\" module to be loaded: %v\", err)\n\t}\n\tbase := testutil.NewBase(t)\n\tinfo := base.Info()\n\tswitch info.CgroupDriver {\n\tcase \"none\", \"\":\n\t\tt.Skip(\"test requires cgroup driver\")\n\t}\n\tcontainerName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\t// when bfq io scheduler is used, the io.weight knob is exposed as io.bfq.weight\n\tbase.Cmd(\"run\", \"--name\", containerName, \"--blkio-weight\", \"300\", \"-w\", \"/sys/fs/cgroup\", testutil.AlpineImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"io.bfq.weight\").AssertOutExactly(\"default 300\\n\")\n\tbase.Cmd(\"update\", containerName, \"--blkio-weight\", \"400\").AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"io.bfq.weight\").AssertOutExactly(\"default 400\\n\")\n}\n\nfunc TestRunBlkioSettingCgroupV2(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = nerdtest.Rootful\n\n\t// See https://github.com/containerd/nerdctl/issues/4185\n\t// It is unclear if this is truly a kernel version problem, a runc issue, or a distro (EL9) issue.\n\t// For now, disable the test unless on a recent kernel.\n\ttestutil.RequireKernelVersion(t, \">= 6.0.0-0\")\n\n\tconst (\n\t\tweight       = \"150\"\n\t\tdeviceWeight = \"100\"\n\t\treadBps      = \"1048576\"\n\t\treadIops     = \"1000\"\n\t\twriteBps     = \"2097152\"\n\t\twriteIops    = \"2000\"\n\t)\n\tvar lo *loopback.Loopback\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar err error\n\t\tlo, err = loopback.New(4096)\n\t\tassert.NilError(t, err)\n\t\tt.Logf(\"loopback device: %+v\", lo)\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif lo != nil {\n\t\t\t_ = lo.Close()\n\t\t}\n\t}\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"blkio-weight\",\n\t\t\tRequire:     nerdtest.CGroupV2,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--blkio-weight\", weight,\n\t\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"{{.HostConfig.BlkioWeight}}\", data.Identifier()), weight))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"blkio-weight-device\",\n\t\t\tRequire:     nerdtest.CGroupV2,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--blkio-weight-device\", fmt.Sprintf(\"%s:%s\", lo.Device, deviceWeight),\n\t\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioWeightDevice}}{{.Weight}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, deviceWeight))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioWeightDevice}}{{.Path}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, lo.Device))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"device-read-bps\",\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.CGroupV2,\n\t\t\t\t// Docker cli (v26.1.3) available in github runners has a bug where some of the blkio options\n\t\t\t\t// do not work https://github.com/docker/cli/issues/5321. The fix has been merged to the latest releases\n\t\t\t\t// but not currently available in the v26 release.\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--device-read-bps\", fmt.Sprintf(\"%s:%s\", lo.Device, readBps),\n\t\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceReadBps}}{{.Rate}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, readBps))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceReadBps}}{{.Path}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, lo.Device))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"device-write-bps\",\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.CGroupV2,\n\t\t\t\t// Docker cli (v26.1.3) available in github runners has a bug where some of the blkio options\n\t\t\t\t// do not work https://github.com/docker/cli/issues/5321. The fix has been merged to the latest releases\n\t\t\t\t// but not currently available in the v26 release.\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--device-write-bps\", fmt.Sprintf(\"%s:%s\", lo.Device, writeBps),\n\t\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceWriteBps}}{{.Rate}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, writeBps))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceWriteBps}}{{.Path}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, lo.Device))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"device-read-iops\",\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.CGroupV2,\n\t\t\t\t// Docker cli (v26.1.3) available in github runners has a bug where some of the blkio options\n\t\t\t\t// do not work https://github.com/docker/cli/issues/5321. The fix has been merged to the latest releases\n\t\t\t\t// but not currently available in the v26 release.\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--device-read-iops\", fmt.Sprintf(\"%s:%s\", lo.Device, readIops),\n\t\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceReadIOps}}{{.Rate}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, readIops))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceReadIOps}}{{.Path}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, lo.Device))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"device-write-iops\",\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.CGroupV2,\n\t\t\t\t// Docker cli (v26.1.3) available in github runners has a bug where some of the blkio options\n\t\t\t\t// do not work https://github.com/docker/cli/issues/5321. The fix has been merged to the latest releases\n\t\t\t\t// but not currently available in the v26 release.\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\"--device-write-iops\", fmt.Sprintf(\"%s:%s\", lo.Device, writeIops),\n\t\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceWriteIOps}}{{.Rate}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, writeIops))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspectOut := helpers.Capture(\"inspect\", \"--format\", \"{{range .HostConfig.BlkioDeviceWriteIOps}}{{.Path}}{{end}}\", data.Identifier())\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(inspectOut, lo.Device))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunCPURealTimeSettingCgroupV1(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"cpu-rt-runtime-and-period\",\n\t\tRequire: require.All(\n\t\t\trequire.Not(nerdtest.CGroupV2),\n\t\t\tnerdtest.Rootful,\n\t\t),\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"create\", \"--name\", data.Identifier(),\n\t\t\t\t\"--cpu-rt-runtime\", \"950000\",\n\t\t\t\t\"--cpu-rt-period\", \"1000000\",\n\t\t\t\ttestutil.AlpineImage, \"sleep\", \"infinity\")\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tExitCode: 0,\n\t\t\t\tOutput: expect.All(\n\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\trtRuntime := helpers.Capture(\"inspect\", \"--format\", \"{{.HostConfig.CPURealtimeRuntime}}\", data.Identifier())\n\t\t\t\t\t\trtPeriod := helpers.Capture(\"inspect\", \"--format\", \"{{.HostConfig.CPURealtimePeriod}}\", data.Identifier())\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(rtRuntime, \"950000\"))\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(rtPeriod, \"1000000\"))\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunCPUSharesCgroupV2(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.CGroupV2,\n\t\t\tnerdtest.Info(\n\t\t\t\tfunc(info dockercompat.Info) error {\n\t\t\t\t\tif !info.CPUShares {\n\t\t\t\t\t\treturn fmt.Errorf(\"test requires CPUShares\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t),\n\t\t),\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--cpu-shares\", \"2000\",\n\t\t\t\ttestutil.AlpineImage, \"cat\", \"/sys/fs/cgroup/cpu.weight\")\n\t\t},\n\t\t// The value was historically 77, but with runc v1.4.0-rc.1 it became 170.\n\t\t// https://github.com/opencontainers/runc/issues/4896#issuecomment-3301825811\n\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"^(77|170)\\n$\"))),\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_gpus_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\tis \"gotest.tools/v3/assert/cmp\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc TestParseGpusOptAll(t *testing.T) {\n\tt.Parallel()\n\tfor _, testcase := range []string{\n\t\t\"all\",\n\t\t\"-1\",\n\t\t\"count=all\",\n\t\t\"count=-1\",\n\t} {\n\t\treq, err := container.ParseGPUOptCSV(testcase)\n\t\tassert.NilError(t, err)\n\t\tassert.Equal(t, req.Count, -1)\n\t\tassert.Equal(t, len(req.DeviceIDs), 0)\n\t\tassert.Equal(t, len(req.Capabilities), 0)\n\t}\n}\n\nfunc TestParseGpusOpts(t *testing.T) {\n\tt.Parallel()\n\tfor _, testcase := range []string{\n\t\t\"driver=nvidia,\\\"capabilities=compute,utility\\\"\",\n\t\t\"1,driver=nvidia,\\\"capabilities=compute,utility\\\"\",\n\t\t\"count=1,driver=nvidia,\\\"capabilities=compute,utility\\\"\",\n\t\t\"driver=nvidia,\\\"capabilities=compute,utility\\\",count=1\",\n\t\t\"\\\"capabilities=compute,utility\\\",count=1\",\n\t} {\n\t\treq, err := container.ParseGPUOptCSV(testcase)\n\t\tassert.NilError(t, err)\n\t\tassert.Equal(t, req.Count, 1)\n\t\tassert.Equal(t, len(req.DeviceIDs), 0)\n\t\tassert.Check(t, is.DeepEqual(req.Capabilities, []string{\"compute\", \"utility\"}))\n\t}\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/containerd/v2/pkg/cap\"\n)\n\nfunc capShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates := []string{}\n\tfor _, c := range cap.Known() {\n\t\t// \"CAP_SYS_ADMIN\" -> \"sys_admin\"\n\t\ts := strings.ToLower(strings.TrimPrefix(c, \"CAP_\"))\n\t\tcandidates = append(candidates, s)\n\t}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\nfunc TestRunCustomRootfs(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\t// FIXME: root issue is undiagnosed and this is very likely a containerd bug\n\t// It appears that in certain conditions, the proxy content store info method will fail on the layer of the image\n\t// Search for func (pcs *proxyContentStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {\n\t// Note that:\n\t// - the problem is still here with containerd and nerdctl v2\n\t// - it seems to affect images that are tagged multiple times, or that share a layer with another image\n\t// - this test is not parallelized - but the fact that namespacing it solves the problem suggest that something\n\t// happening in the default namespace BEFORE this test is run is SOMETIMES setting conditions that will make this fail\n\t// Possible suspects would be concurrent pulls somehow effing things up w. namespaces.\n\tbase := testutil.NewBaseWithNamespace(t, testutil.Identifier(t))\n\trootfs := prepareCustomRootfs(base, testutil.AlpineImage)\n\tt.Cleanup(func() {\n\t\tbase.Cmd(\"namespace\", \"remove\", testutil.Identifier(t)).Run()\n\t})\n\tdefer os.RemoveAll(rootfs)\n\tbase.Cmd(\"run\", \"--rm\", \"--rootfs\", rootfs, \"/bin/cat\", \"/proc/self/environ\").AssertOutContains(\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\")\n\tbase.Cmd(\"run\", \"--rm\", \"--entrypoint\", \"/bin/echo\", \"--rootfs\", rootfs, \"echo\", \"foo\").AssertOutExactly(\"echo foo\\n\")\n}\n\nfunc prepareCustomRootfs(base *testutil.Base, imageName string) string {\n\tbase.Cmd(\"pull\", \"--quiet\", imageName).AssertOK()\n\ttmpDir, err := os.MkdirTemp(base.T.TempDir(), \"test-save\")\n\tassert.NilError(base.T, err)\n\tdefer os.RemoveAll(tmpDir)\n\tarchiveTarPath := filepath.Join(tmpDir, \"a.tar\")\n\tbase.Cmd(\"save\", \"-o\", archiveTarPath, imageName).AssertOK()\n\trootfs, err := os.MkdirTemp(base.T.TempDir(), \"rootfs\")\n\tassert.NilError(base.T, err)\n\terr = helpers.ExtractDockerArchive(archiveTarPath, rootfs)\n\tassert.NilError(base.T, err)\n\treturn rootfs\n}\n\nfunc TestRunShmSize(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tconst shmSize = \"32m\"\n\n\tbase.Cmd(\"run\", \"--rm\", \"--shm-size\", shmSize, testutil.AlpineImage, \"/bin/grep\", \"shm\", \"/proc/self/mounts\").AssertOutContains(\"size=32768k\")\n}\n\nfunc TestRunShmSizeIPCShareable(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tconst shmSize = \"32m\"\n\n\tcontainer := testutil.Identifier(t)\n\tbase.Cmd(\"run\", \"--rm\", \"--name\", container, \"--ipc\", \"shareable\", \"--shm-size\", shmSize, testutil.AlpineImage, \"/bin/grep\", \"shm\", \"/proc/self/mounts\").AssertOutContains(\"size=32768k\")\n\tdefer base.Cmd(\"rm\", \"-f\", container)\n}\n\nfunc TestRunIPCShareableRemoveMount(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tcontainer := testutil.Identifier(t)\n\n\tbase.Cmd(\"run\", \"--name\", container, \"--ipc\", \"shareable\", testutil.AlpineImage, \"sleep\", \"0\").AssertOK()\n\tbase.Cmd(\"rm\", container).AssertOK()\n}\n\nfunc TestRunIPCContainerNotExists(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\tcontainer := testutil.Identifier(t)\n\tresult := base.Cmd(\"run\", \"--name\", container, \"--ipc\", \"container:abcd1234\", testutil.AlpineImage, \"sleep\", nerdtest.Infinity).Run()\n\tdefer base.Cmd(\"rm\", \"-f\", container)\n\tcombined := result.Combined()\n\tif !strings.Contains(strings.ToLower(combined), \"no such container: abcd1234\") {\n\t\tt.Fatalf(\"unexpected output: %s\", combined)\n\t}\n}\n\nfunc TestRunShmSizeIPCContainer(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\tconst shmSize = \"32m\"\n\tsharedContainerResult := base.Cmd(\"run\", \"-d\", \"--ipc\", \"shareable\", \"--shm-size\", shmSize, testutil.AlpineImage, \"sleep\", nerdtest.Infinity).Run()\n\tbaseContainerID := strings.TrimSpace(sharedContainerResult.Stdout())\n\tdefer base.Cmd(\"rm\", \"-f\", baseContainerID).Run()\n\n\tbase.Cmd(\"run\", \"--rm\", fmt.Sprintf(\"--ipc=container:%s\", baseContainerID),\n\t\ttestutil.AlpineImage, \"/bin/grep\", \"shm\", \"/proc/self/mounts\").AssertOutContains(\"size=32768k\")\n}\n\nfunc TestRunIPCContainer(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\tconst shmSize = \"32m\"\n\tvictimContainerResult := base.Cmd(\"run\", \"-d\", \"--ipc\", \"shareable\", \"--shm-size\", shmSize, testutil.AlpineImage, \"sleep\", nerdtest.Infinity).Run()\n\tvictimContainerID := strings.TrimSpace(victimContainerResult.Stdout())\n\tdefer base.Cmd(\"rm\", \"-f\", victimContainerID).Run()\n\n\tbase.Cmd(\"run\", \"--rm\", fmt.Sprintf(\"--ipc=container:%s\", victimContainerID),\n\t\ttestutil.AlpineImage, \"/bin/grep\", \"shm\", \"/proc/self/mounts\").AssertOutContains(\"size=32768k\")\n}\n\nfunc TestRunPidHost(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tpid := os.Getpid()\n\n\tbase.Cmd(\"run\", \"--rm\", \"--pid=host\", testutil.AlpineImage, \"ps\", \"auxw\").AssertOutContains(strconv.Itoa(pid))\n}\n\nfunc TestRunUtsHost(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\t// Was thinking of os.ReadLink(\"/proc/1/ns/uts\")\n\t// but you'd get EPERM for rootless. Just validate the\n\t// hostname is the same.\n\thostName, err := os.Hostname()\n\tassert.NilError(base.T, err)\n\n\tbase.Cmd(\"run\", \"--rm\", \"--uts=host\", testutil.AlpineImage, \"hostname\").AssertOutContains(hostName)\n\t// Validate we can't provide a hostname with uts=host\n\tbase.Cmd(\"run\", \"--rm\", \"--uts=host\", \"--hostname=foobar\", testutil.AlpineImage, \"hostname\").AssertFail()\n\t// Validate we can't provide a domainname with uts=host\n\tbase.Cmd(\"run\", \"--rm\", \"--uts=host\", \"--domainname=example.com\", testutil.AlpineImage, \"hostname\").AssertFail()\n}\n\nfunc TestRunPidContainer(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\tsharedContainerResult := base.Cmd(\"run\", \"-d\", testutil.AlpineImage, \"sleep\", nerdtest.Infinity).Run()\n\tbaseContainerID := strings.TrimSpace(sharedContainerResult.Stdout())\n\tdefer base.Cmd(\"rm\", \"-f\", baseContainerID).Run()\n\n\tbase.Cmd(\"run\", \"--rm\", fmt.Sprintf(\"--pid=container:%s\", baseContainerID),\n\t\ttestutil.AlpineImage, \"ps\", \"ax\").AssertOutContains(\"sleep \" + nerdtest.Infinity)\n}\n\nfunc TestRunIpcHost(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttestFilePath := filepath.Join(\"/dev/shm\",\n\t\tfmt.Sprintf(\"%s-%d-%s\", testutil.Identifier(t), os.Geteuid(), base.Target))\n\terr := os.WriteFile(testFilePath, []byte(\"\"), 0o644)\n\tassert.NilError(base.T, err)\n\tdefer os.Remove(testFilePath)\n\n\tbase.Cmd(\"run\", \"--rm\", \"--ipc=host\", testutil.AlpineImage, \"ls\", testFilePath).AssertOK()\n}\n\nfunc TestRunAddHost(t *testing.T) {\n\t// Not parallelizable (https://github.com/containerd/nerdctl/issues/1127)\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"--add-host\", \"testing.example.com:10.0.0.1\", testutil.AlpineImage, \"cat\", \"/etc/hosts\").AssertOutWithFunc(func(stdout string) error {\n\t\tvar found bool\n\t\tsc := bufio.NewScanner(bytes.NewBufferString(stdout))\n\t\tfor sc.Scan() {\n\t\t\t// removing spaces and tabs separating items\n\t\t\tline := strings.ReplaceAll(sc.Text(), \" \", \"\")\n\t\t\tline = strings.ReplaceAll(line, \"\\t\", \"\")\n\t\t\tif strings.Contains(line, \"10.0.0.1testing.example.com\") {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\treturn errors.New(\"host was not added\")\n\t\t}\n\t\treturn nil\n\t})\n\tbase.Cmd(\"run\", \"--rm\", \"--add-host\", \"test:10.0.0.1\", \"--add-host\", \"test1:10.0.0.1\", testutil.AlpineImage, \"cat\", \"/etc/hosts\").AssertOutWithFunc(func(stdout string) error {\n\t\tvar found int\n\t\tsc := bufio.NewScanner(bytes.NewBufferString(stdout))\n\t\tfor sc.Scan() {\n\t\t\t// removing spaces and tabs separating items\n\t\t\tline := strings.ReplaceAll(sc.Text(), \" \", \"\")\n\t\t\tline = strings.ReplaceAll(line, \"\\t\", \"\")\n\t\t\tif strutil.InStringSlice([]string{\"10.0.0.1test\", \"10.0.0.1test1\"}, line) {\n\t\t\t\tfound++\n\t\t\t}\n\t\t}\n\t\tif found != 2 {\n\t\t\treturn fmt.Errorf(\"host was not added, found %d\", found)\n\t\t}\n\t\treturn nil\n\t})\n\tbase.Cmd(\"run\", \"--rm\", \"--add-host\", \"10.0.0.1:testing.example.com\", testutil.AlpineImage, \"cat\", \"/etc/hosts\").AssertFail()\n\n\tresponse := \"This is the expected response for --add-host special IP test.\"\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tio.WriteString(w, response)\n\t})\n\tconst hostPort = 8081\n\ts := http.Server{Addr: fmt.Sprintf(\":%d\", hostPort), Handler: nil, ReadTimeout: 30 * time.Second}\n\tgo s.ListenAndServe()\n\tdefer s.Shutdown(context.Background())\n\tbase.Cmd(\"run\", \"--rm\", \"--add-host\", \"test:host-gateway\", testutil.NginxAlpineImage, \"curl\", fmt.Sprintf(\"test:%d\", hostPort)).AssertOutExactly(response)\n}\n\nfunc TestRunAddHostWithCustomHostGatewayIP(t *testing.T) {\n\t// Not parallelizable (https://github.com/containerd/nerdctl/issues/1127)\n\tbase := testutil.NewBase(t)\n\ttestutil.DockerIncompatible(t)\n\tbase.Cmd(\"run\", \"--rm\", \"--host-gateway-ip\", \"192.168.5.2\", \"--add-host\", \"test:host-gateway\", testutil.AlpineImage, \"cat\", \"/etc/hosts\").AssertOutWithFunc(func(stdout string) error {\n\t\tvar found bool\n\t\tsc := bufio.NewScanner(bytes.NewBufferString(stdout))\n\t\tfor sc.Scan() {\n\t\t\t// removing spaces and tabs separating items\n\t\t\tline := strings.ReplaceAll(sc.Text(), \" \", \"\")\n\t\t\tline = strings.ReplaceAll(line, \"\\t\", \"\")\n\t\t\tif strings.Contains(line, \"192.168.5.2test\") {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\treturn errors.New(\"host was not added\")\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestRunUlimit(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tulimit := \"nofile=622:622\"\n\tulimit2 := \"nofile=622:722\"\n\n\tbase.Cmd(\"run\", \"--rm\", \"--ulimit\", ulimit, testutil.AlpineImage, \"sh\", \"-c\", \"ulimit -Sn\").AssertOutExactly(\"622\\n\")\n\tbase.Cmd(\"run\", \"--rm\", \"--ulimit\", ulimit, testutil.AlpineImage, \"sh\", \"-c\", \"ulimit -Hn\").AssertOutExactly(\"622\\n\")\n\n\tbase.Cmd(\"run\", \"--rm\", \"--ulimit\", ulimit2, testutil.AlpineImage, \"sh\", \"-c\", \"ulimit -Sn\").AssertOutExactly(\"622\\n\")\n\tbase.Cmd(\"run\", \"--rm\", \"--ulimit\", ulimit2, testutil.AlpineImage, \"sh\", \"-c\", \"ulimit -Hn\").AssertOutExactly(\"722\\n\")\n}\n\nfunc TestRunWithInit(t *testing.T) {\n\tt.Parallel()\n\ttestutil.DockerIncompatible(t)\n\ttestutil.RequireExecutable(t, \"tini-custom\")\n\tbase := testutil.NewBase(t)\n\n\tcontainer := testutil.Identifier(t)\n\tbase.Cmd(\"run\", \"-d\", \"--name\", container, testutil.AlpineImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", container).Run()\n\n\tbase.Cmd(\"stop\", \"--time=3\", container).AssertOK()\n\t// Unable to handle TERM signal, be killed when timeout\n\tassert.Equal(t, base.InspectContainer(container).State.ExitCode, 137)\n\n\t// Test with --init-path\n\tcontainer1 := container + \"-1\"\n\tbase.Cmd(\"run\", \"-d\", \"--name\", container1, \"--init-binary\", \"tini-custom\",\n\t\ttestutil.AlpineImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", container1).Run()\n\n\tbase.Cmd(\"stop\", \"--time=3\", container1).AssertOK()\n\tassert.Equal(t, base.InspectContainer(container1).State.ExitCode, 143)\n\n\t// Test with --init\n\tcontainer2 := container + \"-2\"\n\tbase.Cmd(\"run\", \"-d\", \"--name\", container2, \"--init\",\n\t\ttestutil.AlpineImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", container2).Run()\n\n\tbase.Cmd(\"stop\", \"--time=3\", container2).AssertOK()\n\tassert.Equal(t, base.InspectContainer(container2).State.ExitCode, 143)\n}\n\nfunc TestRunTTY(t *testing.T) {\n\tconst sttyPartialOutput = \"speed 38400 baud\"\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"stty with -it\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-it\", data.Identifier(), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(sttyPartialOutput)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty with -t\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-t\", data.Identifier(), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(sttyPartialOutput)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty with -i\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-i\", data.Identifier(), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty without params\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", data.Identifier(), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"stty with -td\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"-td\", data.Identifier(), \"stty\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t}\n}\n\nfunc TestRunSigProxy(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// FIXME: gomodjail signal handling is not working yet: https://github.com/AkihiroSuda/gomodjail/issues/51\n\ttestCase.Require = require.Not(nerdtest.Gomodjail)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"SigProxyDefault\",\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// FIXME: os.Interrupt will likely not work on Windows\n\t\t\t\tcmd := nerdtest.RunSigProxyContainer(os.Interrupt, true, nil, data, helpers)\n\t\t\t\terr := cmd.Signal(os.Interrupt)\n\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\treturn cmd\n\t\t\t},\n\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(nerdtest.SignalCaught)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"SigProxyTrue\",\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := nerdtest.RunSigProxyContainer(os.Interrupt, true, []string{\"--sig-proxy=true\"}, data, helpers)\n\t\t\t\terr := cmd.Signal(os.Interrupt)\n\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\treturn cmd\n\t\t\t},\n\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(nerdtest.SignalCaught)),\n\t\t},\n\t\t{\n\t\t\tDescription: \"SigProxyFalse\",\n\n\t\t\t// Docker behavior changed sometimes with Docker 27\n\t\t\t// See https://github.com/containerd/nerdctl/issues/4219 for details\n\t\t\tRequire: require.Not(nerdtest.Docker),\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := nerdtest.RunSigProxyContainer(os.Interrupt, true, []string{\"--sig-proxy=false\"}, data, helpers)\n\t\t\t\terr := cmd.Signal(os.Interrupt)\n\t\t\t\tassert.NilError(helpers.T(), err)\n\t\t\t\treturn cmd\n\t\t\t},\n\n\t\t\tExpected: test.Expects(expect.ExitCodeSignaled, nil, expect.DoesNotContain(nerdtest.SignalCaught)),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithFluentdLogDriver(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\ttempDirectory := t.TempDir()\n\terr := os.Chmod(tempDirectory, 0o777)\n\tassert.NilError(t, err)\n\n\tcontainerName := testutil.Identifier(t)\n\tbase.Cmd(\"run\", \"-d\", \"--name\", containerName, \"-p\", \"24224:24224\",\n\t\t\"-v\", fmt.Sprintf(\"%s:/fluentd/log\", tempDirectory), testutil.FluentdImage).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\ttime.Sleep(3 * time.Second)\n\n\ttestContainerName := containerName + \"test\"\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", \"fluentd\", \"--name\", testContainerName, testutil.CommonImage,\n\t\t\"sh\", \"-c\", \"echo test\").AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).AssertOK()\n\n\tinspectedContainer := base.InspectContainer(testContainerName)\n\tmatches, err := filepath.Glob(tempDirectory + \"/\" + \"data.*.log\")\n\tassert.NilError(t, err)\n\tassert.Equal(t, 1, len(matches))\n\n\tdata, err := os.ReadFile(matches[0])\n\tassert.NilError(t, err)\n\tlogData := string(data)\n\tassert.Equal(t, true, strings.Contains(logData, \"test\"))\n\tassert.Equal(t, true, strings.Contains(logData, inspectedContainer.ID))\n}\n\nfunc TestRunWithFluentdLogDriverWithLogOpt(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\ttempDirectory := t.TempDir()\n\terr := os.Chmod(tempDirectory, 0o777)\n\tassert.NilError(t, err)\n\n\tcontainerName := testutil.Identifier(t)\n\tbase.Cmd(\"run\", \"-d\", \"--name\", containerName, \"-p\", \"24225:24224\",\n\t\t\"-v\", fmt.Sprintf(\"%s:/fluentd/log\", tempDirectory), testutil.FluentdImage).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\ttime.Sleep(3 * time.Second)\n\n\ttestContainerName := containerName + \"test\"\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", \"fluentd\", \"--log-opt\", \"fluentd-address=127.0.0.1:24225\",\n\t\t\"--name\", testContainerName, testutil.CommonImage, \"sh\", \"-c\", \"echo test2\").AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).AssertOK()\n\n\tinspectedContainer := base.InspectContainer(testContainerName)\n\tmatches, err := filepath.Glob(tempDirectory + \"/\" + \"data.*.log\")\n\tassert.NilError(t, err)\n\tassert.Equal(t, 1, len(matches))\n\n\tdata, err := os.ReadFile(matches[0])\n\tassert.NilError(t, err)\n\tlogData := string(data)\n\tassert.Equal(t, true, strings.Contains(logData, \"test2\"))\n\tassert.Equal(t, true, strings.Contains(logData, inspectedContainer.ID))\n}\n\nfunc TestRunWithOOMScoreAdj(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"test skipped for rootless containers.\")\n\t}\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tscore := \"-42\"\n\n\tbase.Cmd(\"run\", \"--rm\", \"--oom-score-adj\", score, testutil.AlpineImage, \"cat\", \"/proc/self/oom_score_adj\").AssertOutContains(score)\n}\n\nfunc TestRunWithDetachKeys(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// Run interactively and detach\n\t\tcmd := helpers.Command(\"run\", \"-it\", \"--detach-keys=ctrl-a,ctrl-b\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"echo mark${NON}mark\\n\"))\n\t\tcmd.WithFeeder(func() io.Reader {\n\t\t\t// Because of the way we proxy stdin, we have to wait here, otherwise we detach before\n\t\t\t// the rest of the input ever reaches the container\n\t\t\t// Note that this only concerns nerdctl, as docker seems to behave ok LOCALLY.\n\t\t\t// But then, it fails for docker as well ON THE CI. It is unclear why at this point.\n\t\t\t// Arbitrary time pauses would not work: what matters is that the container has started.\n\t\t\t// if !nerdtest.IsDocker() {\n\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t// }\n\t\t\t// ctrl+a and ctrl+b (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\t\treturn bytes.NewReader([]byte{1, 2})\n\t\t})\n\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   []error{errors.New(\"detach keys\")},\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(\"markmark\"),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithTtyAndDetached(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\timageName := testutil.CommonImage\n\twithoutTtyContainerName := \"without-terminal-\" + testutil.Identifier(t)\n\twithTtyContainerName := \"with-terminal-\" + testutil.Identifier(t)\n\n\t// without -t, fail\n\tbase.Cmd(\"run\", \"-d\", \"--name\", withoutTtyContainerName, imageName, \"stty\").AssertOK()\n\tdefer base.Cmd(\"container\", \"rm\", \"-f\", withoutTtyContainerName).AssertOK()\n\tbase.Cmd(\"logs\", withoutTtyContainerName).AssertCombinedOutContains(\"stty: standard input: Not a tty\")\n\twithoutTtyContainer := base.InspectContainer(withoutTtyContainerName)\n\tassert.Equal(base.T, 1, withoutTtyContainer.State.ExitCode)\n\n\t// with -t, success\n\tbase.Cmd(\"run\", \"-d\", \"-t\", \"--name\", withTtyContainerName, imageName, \"stty\").AssertOK()\n\tdefer base.Cmd(\"container\", \"rm\", \"-f\", withTtyContainerName).AssertOK()\n\tbase.Cmd(\"logs\", withTtyContainerName).AssertCombinedOutContains(\"speed 38400 baud; line = 0;\")\n\twithTtyContainer := base.InspectContainer(withTtyContainerName)\n\tassert.Equal(base.T, 0, withTtyContainer.State.ExitCode)\n}\n\n// TestIssue3568 tests https://github.com/containerd/nerdctl/issues/3568\nfunc TestIssue3568(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Description = \"Issue #3568 - Detaching from a container started by using --rm option causes the container to be deleted.\"\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// Run interactively and detach\n\t\tcmd := helpers.Command(\"run\", \"--rm\", \"-it\", \"--detach-keys=ctrl-a,ctrl-b\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"echo mark${NON}mark\\n\"))\n\t\tcmd.WithFeeder(func() io.Reader {\n\t\t\t// Because of the way we proxy stdin, we have to wait here, otherwise we detach before\n\t\t\t// the rest of the input ever reaches the container\n\t\t\t// Note that this only concerns nerdctl, as docker seems to behave ok LOCALLY.\n\t\t\t// But then, it fails for docker as well ON THE CI. It is unclear why at this point.\n\t\t\t// Arbitrary time pauses would not work: what matters is that the container has started.\n\t\t\t// if !nerdtest.IsDocker() {\n\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t// }\n\t\t\t// ctrl+a and ctrl+b (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\t\treturn bytes.NewReader([]byte{1, 2})\n\t\t})\n\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   []error{errors.New(\"detach keys\")},\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Contains(\"markmark\"),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestPortBindingWithCustomHost tests https://github.com/containerd/nerdctl/issues/3539\nfunc TestPortBindingWithCustomHost(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst (\n\t\thost     = \"127.0.0.2\"\n\t\thostPort = 8080\n\t)\n\taddress := fmt.Sprintf(\"%s:%d\", host, hostPort)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Issue #3539 - Access to a container running when 127.0.0.2 is specified in -p in rootless mode.\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), \"-p\", fmt.Sprintf(\"%s:80\", address), testutil.NginxAlpineImage)\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tresp, err := nettestutil.HTTPGet(address, 5, false)\n\t\t\t\t\t\t\tassert.NilError(t, err)\n\n\t\t\t\t\t\t\trespBody, err := io.ReadAll(resp.Body)\n\t\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunDeviceCDI(t *testing.T) {\n\tt.Parallel()\n\t// Although CDI injection is supported by Docker, specifying the --cdi-spec-dirs on the command line is not.\n\ttestutil.DockerIncompatible(t)\n\tcdiSpecDir := filepath.Join(t.TempDir(), \"cdi\")\n\tconst testCDIVendor1 = `\ncdiVersion: \"0.3.0\"\nkind: \"vendor1.com/device\"\ndevices:\n- name: foo\n  containerEdits:\n    env:\n    - FOO=injected\n`\n\twriteTestCDISpec(t, testCDIVendor1, \"vendor1.yaml\", cdiSpecDir)\n\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"--cdi-spec-dirs\", cdiSpecDir, \"run\",\n\t\t\"--rm\",\n\t\t\"--device\", \"vendor1.com/device=foo\",\n\t\ttestutil.AlpineImage, \"env\",\n\t).AssertOutContains(\"FOO=injected\")\n}\n\nfunc TestRunDeviceCDIWithNerdctlConfig(t *testing.T) {\n\tt.Parallel()\n\t// Although CDI injection is supported by Docker, specifying the --cdi-spec-dirs on the command line is not.\n\ttestutil.DockerIncompatible(t)\n\tcdiSpecDir := filepath.Join(t.TempDir(), \"cdi\")\n\tconst testCDIVendor1 = `\ncdiVersion: \"0.3.0\"\nkind: \"vendor1.com/device\"\ndevices:\n- name: foo\n  containerEdits:\n    env:\n    - FOO=injected\n`\n\twriteTestCDISpec(t, testCDIVendor1, \"vendor1.yaml\", cdiSpecDir)\n\n\ttomlPath := filepath.Join(t.TempDir(), \"nerdctl.toml\")\n\terr := os.WriteFile(tomlPath, []byte(fmt.Sprintf(`\ncdi_spec_dirs = [\"%s\"]\n`, cdiSpecDir)), 0o400)\n\tassert.NilError(t, err)\n\n\tbase := testutil.NewBase(t)\n\tbase.Env = append(base.Env, \"NERDCTL_TOML=\"+tomlPath)\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"--device\", \"vendor1.com/device=foo\",\n\t\ttestutil.AlpineImage, \"env\",\n\t).AssertOutContains(\"FOO=injected\")\n}\n\n// TestRunGPU tests GPU injection using the --gpus flag.\nfunc TestRunGPU(t *testing.T) {\n\tt.Parallel()\n\t// Although CDI injection is supported by Docker, specifying the --cdi-spec-dirs on the command line is not.\n\ttestutil.DockerIncompatible(t)\n\tconst nvidiaSpec = `\ncdiVersion: \"0.5.0\"\nkind: \"nvidia.com/gpu\"\ndevices:\n- name: \"0\"\n  containerEdits:\n    env:\n    - NVIDIA_GPU_0=injected\n- name: \"1\"\n  containerEdits:\n    env:\n    - NVIDIA_GPU_1=injected\n`\n\tconst amdSpec = `\ncdiVersion: \"0.5.0\"\nkind: \"amd.com/gpu\"\ndevices:\n- name: \"0\"\n  containerEdits:\n    env:\n    - AMD_GPU_0=injected\n- name: \"1\"\n  containerEdits:\n    env:\n    - AMD_GPU_1=injected\n`\n\tconst unknownSpec = `\ncdiVersion: \"0.5.0\"\nkind: \"unknown.com/gpu\"\ndevices:\n- name: \"0\"\n  containerEdits:\n    env:\n    - UNKNOWN_GPU_0=injected\n`\n\n\ttestCases := []struct {\n\t\tname         string\n\t\tspecs        map[string]string\n\t\tgpuFlags     []string\n\t\texpectedEnvs []string\n\t\texpectFail   bool\n\t}{\n\t\t{\n\t\t\tname:         \"nvidia device injection\",\n\t\t\tspecs:        map[string]string{\"nvidia.yaml\": nvidiaSpec},\n\t\t\tgpuFlags:     []string{\"--gpus\", \"2\"},\n\t\t\texpectedEnvs: []string{\"NVIDIA_GPU_0=injected\", \"NVIDIA_GPU_1=injected\"},\n\t\t},\n\t\t{\n\t\t\tname:         \"amd device injection\",\n\t\t\tspecs:        map[string]string{\"amd.yaml\": amdSpec},\n\t\t\tgpuFlags:     []string{\"--gpus\", \"2\"},\n\t\t\texpectedEnvs: []string{\"AMD_GPU_0=injected\", \"AMD_GPU_1=injected\"},\n\t\t},\n\t\t{\n\t\t\tname:         \"multiple vendors\",\n\t\t\tspecs:        map[string]string{\"nvidia.yaml\": nvidiaSpec, \"amd.yaml\": amdSpec},\n\t\t\tgpuFlags:     []string{\"--gpus\", \"1\"},\n\t\t\texpectedEnvs: []string{\"NVIDIA_GPU_0=injected\"},\n\t\t},\n\t\t{\n\t\t\tname:       \"unknown vendor fails\",\n\t\t\tspecs:      map[string]string{\"unknown.yaml\": unknownSpec},\n\t\t\tgpuFlags:   []string{\"--gpus\", \"1\"},\n\t\t\texpectFail: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttmpDir := t.TempDir()\n\t\t\tfor fileName, spec := range tc.specs {\n\t\t\t\twriteTestCDISpec(t, spec, fileName, tmpDir)\n\t\t\t}\n\n\t\t\tbase := testutil.NewBase(t)\n\t\t\targs := []string{\"--cdi-spec-dirs\", tmpDir, \"run\", \"--rm\"}\n\t\t\targs = append(args, tc.gpuFlags...)\n\t\t\targs = append(args, testutil.AlpineImage, \"env\")\n\n\t\t\tif tc.expectFail {\n\t\t\t\tbase.Cmd(args...).AssertFail()\n\t\t\t} else {\n\t\t\t\tbase.Cmd(args...).AssertOutWithFunc(func(stdout string) error {\n\t\t\t\t\tfor _, expectedEnv := range tc.expectedEnvs {\n\t\t\t\t\t\tif !strings.Contains(stdout, expectedEnv) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"%s not found\", expectedEnv)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestRunGPUWithOtherCDIDevices tests GPU CDI injection along with other CDI devices.\nfunc TestRunGPUWithOtherCDIDevices(t *testing.T) {\n\tt.Parallel()\n\t// Although CDI injection is supported by Docker, specifying the --cdi-spec-dirs on the command line is not.\n\ttestutil.DockerIncompatible(t)\n\tconst amdSpec = `\ncdiVersion: \"0.5.0\"\nkind: \"amd.com/gpu\"\ndevices:\n- name: \"0\"\n  containerEdits:\n    env:\n    - AMD_GPU_0=injected\n- name: \"1\"\n  containerEdits:\n    env:\n    - AMD_GPU_1=injected\n`\n\tconst vendor1Spec = `\ncdiVersion: \"0.3.0\"\nkind: \"vendor1.com/device\"\ndevices:\n- name: foo\n  containerEdits:\n    env:\n    - FOO=injected\n`\n\n\ttmpDir := t.TempDir()\n\twriteTestCDISpec(t, amdSpec, \"amd.yaml\", tmpDir)\n\twriteTestCDISpec(t, vendor1Spec, \"vendor1.yaml\", tmpDir)\n\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"--cdi-spec-dirs\", tmpDir, \"run\", \"--rm\",\n\t\t\"--gpus\", \"2\",\n\t\t\"--device\", \"vendor1.com/device=foo\",\n\t\ttestutil.AlpineImage, \"env\",\n\t).AssertOutWithFunc(func(stdout string) error {\n\t\tif !strings.Contains(stdout, \"AMD_GPU_0=injected\") {\n\t\t\treturn errors.New(\"AMD_GPU_0=injected not found\")\n\t\t}\n\t\tif !strings.Contains(stdout, \"AMD_GPU_1=injected\") {\n\t\t\treturn errors.New(\"AMD_GPU_1=injected not found\")\n\t\t}\n\t\tif !strings.Contains(stdout, \"FOO=injected\") {\n\t\t\treturn errors.New(\"FOO=injected not found\")\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc writeTestCDISpec(t *testing.T, spec string, fileName string, cdiSpecDir string) {\n\terr := os.MkdirAll(cdiSpecDir, 0o700)\n\tassert.NilError(t, err)\n\tcdiSpecPath := filepath.Join(cdiSpecDir, fileName)\n\terr = os.WriteFile(cdiSpecPath, []byte(spec), 0o400)\n\tassert.NilError(t, err)\n}\n\nfunc TestSharedIpcSetup(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Labels().Set(\"container1\", data.Identifier(\"container1\"))\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container1\"), \"--ipc=shareable\",\n\t\t\t\ttestutil.CommonImage, \"sleep\", \"inf\")\n\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"container1\"))\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container1\"))\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Test ipc is shared\",\n\t\t\t\tNoParallel:  true, // The validation involves starting of the main container: container1\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container2\"))\n\t\t\t\t},\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\n\t\t\t\t\t\t\"run\", \"-d\", \"--name\", data.Identifier(\"container2\"),\n\t\t\t\t\t\t\"--ipc=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.NginxAlpineImage)\n\t\t\t\t\tdata.Labels().Set(\"container2\", data.Identifier(\"container2\"))\n\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"container2\"))\n\t\t\t\t},\n\t\t\t\tSubTests: []*test.Case{\n\t\t\t\t\t{\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tDescription: \"Test ipc is shared\",\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container2\"), \"readlink\", \"/proc/1/ns/ipc\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\t\t\tcontainer1IPC := strings.TrimSpace(helpers.Capture(\"exec\", data.Labels().Get(\"container1\"), \"readlink\", \"/proc/1/ns/ipc\"))\n\t\t\t\t\t\t\t\t\t\tcontainer2IPC := strings.TrimSpace(stdout)\n\t\t\t\t\t\t\t\t\t\tassert.Equal(t, container1IPC, container2IPC)\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tDescription: \"Test ipc is shared after restart\",\n\t\t\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t\t\thelpers.Ensure(\"restart\", data.Labels().Get(\"container1\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"stop\", \"--time=1\", data.Labels().Get(\"container2\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"start\", data.Labels().Get(\"container2\"))\n\t\t\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"container2\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container2\"), \"readlink\", \"/proc/1/ns/ipc\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\t\t\tcontainer1IPC := strings.TrimSpace(helpers.Capture(\"exec\", data.Labels().Get(\"container1\"), \"readlink\", \"/proc/1/ns/ipc\"))\n\t\t\t\t\t\t\t\t\t\tcontainer2IPC := strings.TrimSpace(stdout)\n\t\t\t\t\t\t\t\t\t\tassert.Equal(t, container1IPC, container2IPC)\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_log_driver_syslog_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tsyslog \"github.com/yuchanns/srslog\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testca\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testsyslog\"\n)\n\nfunc runSyslogTest(t *testing.T, networks []string, syslogFacilities map[string]syslog.Priority, fmtValidFuncs map[string]func(string, string, string, string, syslog.Priority, bool) error) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"syslog container logging is not officially supported on Windows\")\n\t}\n\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"pull\", \"--quiet\", testutil.CommonImage).AssertOK()\n\thostname, err := os.Hostname()\n\tif err != nil {\n\t\tt.Fatalf(\"Error retrieving hostname\")\n\t}\n\tca := testca.New(base.T)\n\tcert := ca.NewCert(\"127.0.0.1\")\n\tt.Cleanup(func() {\n\t\tcert.Close()\n\t\tca.Close()\n\t})\n\trI := 0\n\tfor _, network := range networks {\n\t\tfor rFK, rFV := range syslogFacilities {\n\t\t\tfPriV := rFV\n\t\t\t// test both string and number facility\n\t\t\tfor _, fPriK := range []string{rFK, strconv.Itoa(int(fPriV) >> 3)} {\n\t\t\t\tfor fmtK, fmtValidFunc := range fmtValidFuncs {\n\t\t\t\t\tfmtKT := \"empty\"\n\t\t\t\t\tif fmtK != \"\" {\n\t\t\t\t\t\tfmtKT = fmtK\n\t\t\t\t\t}\n\t\t\t\t\tsubTestName := fmt.Sprintf(\"%s_%s_%s\", strings.ReplaceAll(network, \"+\", \"_\"), fPriK, fmtKT)\n\t\t\t\t\ti := rI\n\t\t\t\t\trI++\n\t\t\t\t\tt.Run(subTestName, func(t *testing.T) {\n\t\t\t\t\t\ttID := testutil.Identifier(t)\n\t\t\t\t\t\ttag := tID + \"_syslog_driver\"\n\t\t\t\t\t\tmsg := \"hello, \" + tID + \"_syslog_driver\"\n\t\t\t\t\t\tif !testsyslog.TestableNetwork(network) {\n\t\t\t\t\t\t\tif rootlessutil.IsRootless() {\n\t\t\t\t\t\t\t\tt.Skipf(\"skipping on %s/%s; '%s' for rootless containers are not supported\", runtime.GOOS, runtime.GOARCH, network)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tt.Skipf(\"skipping on %s/%s; '%s' is not supported\", runtime.GOOS, runtime.GOARCH, network)\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttestContainerName := fmt.Sprintf(\"%s-%d-%s\", tID, i, fPriK)\n\t\t\t\t\t\tdone := make(chan string)\n\t\t\t\t\t\taddr, closer := testsyslog.StartServer(network, \"\", done, cert)\n\t\t\t\t\t\targs := []string{\n\t\t\t\t\t\t\t\"run\",\n\t\t\t\t\t\t\t\"-d\",\n\t\t\t\t\t\t\t\"--name\",\n\t\t\t\t\t\t\ttestContainerName,\n\t\t\t\t\t\t\t\"--restart=no\",\n\t\t\t\t\t\t\t\"--log-driver=syslog\",\n\t\t\t\t\t\t\t\"--log-opt=syslog-facility=\" + fPriK,\n\t\t\t\t\t\t\t\"--log-opt=tag=\" + tag,\n\t\t\t\t\t\t\t\"--log-opt=syslog-format=\" + fmtK,\n\t\t\t\t\t\t\t\"--log-opt=syslog-address=\" + fmt.Sprintf(\"%s://%s\", network, addr),\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif network == \"tcp+tls\" {\n\t\t\t\t\t\t\targs = append(args,\n\t\t\t\t\t\t\t\t\"--log-opt=syslog-tls-cert=\"+cert.CertPath,\n\t\t\t\t\t\t\t\t\"--log-opt=syslog-tls-key=\"+cert.KeyPath,\n\t\t\t\t\t\t\t\t\"--log-opt=syslog-tls-ca-cert=\"+ca.CertPath,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\targs = append(args, testutil.CommonImage, \"echo\", msg)\n\t\t\t\t\t\tbase.Cmd(args...).AssertOK()\n\t\t\t\t\t\tt.Cleanup(func() {\n\t\t\t\t\t\t\tbase.Cmd(\"rm\", \"-f\", testContainerName).AssertOK()\n\t\t\t\t\t\t})\n\t\t\t\t\t\tdefer closer.Close()\n\t\t\t\t\t\tdefer close(done)\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase rcvd := <-done:\n\t\t\t\t\t\t\tif err := fmtValidFunc(rcvd, msg, tag, hostname, fPriV, network == \"tcp+tls\"); err != nil {\n\t\t\t\t\t\t\t\tt.Error(err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase <-time.Tick(time.Second * 3):\n\t\t\t\t\t\t\tt.Errorf(\"timeout with %s\", subTestName)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestSyslogNetwork(t *testing.T) {\n\tvar syslogFacilities = map[string]syslog.Priority{\n\t\t\"user\": syslog.LOG_USER,\n\t}\n\n\tnetworks := []string{\n\t\t\"udp\",\n\t\t\"tcp\",\n\t\t\"tcp+tls\",\n\t\t\"unix\",\n\t\t\"unixgram\",\n\t}\n\tfmtValidFuncs := map[string]func(string, string, string, string, syslog.Priority, bool) error{\n\t\t\"rfc5424\": func(rcvd, msg, tag, hostname string, pri syslog.Priority, isTLS bool) error {\n\t\t\tvar parsedHostname, timestamp string\n\t\t\tvar length, version, pid int\n\t\t\tif !isTLS {\n\t\t\t\texp := fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &version, &timestamp, &parsedHostname, &pid); n != 4 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texp := \"%d \" + fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &length, &version, &timestamp, &parsedHostname, &pid); n != 5 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\trunSyslogTest(t, networks, syslogFacilities, fmtValidFuncs)\n}\n\nfunc TestSyslogFacilities(t *testing.T) {\n\tvar syslogFacilities = map[string]syslog.Priority{\n\t\t\"kern\":     syslog.LOG_KERN,\n\t\t\"user\":     syslog.LOG_USER,\n\t\t\"mail\":     syslog.LOG_MAIL,\n\t\t\"daemon\":   syslog.LOG_DAEMON,\n\t\t\"auth\":     syslog.LOG_AUTH,\n\t\t\"syslog\":   syslog.LOG_SYSLOG,\n\t\t\"lpr\":      syslog.LOG_LPR,\n\t\t\"news\":     syslog.LOG_NEWS,\n\t\t\"uucp\":     syslog.LOG_UUCP,\n\t\t\"cron\":     syslog.LOG_CRON,\n\t\t\"authpriv\": syslog.LOG_AUTHPRIV,\n\t\t\"ftp\":      syslog.LOG_FTP,\n\t\t\"local0\":   syslog.LOG_LOCAL0,\n\t\t\"local1\":   syslog.LOG_LOCAL1,\n\t\t\"local2\":   syslog.LOG_LOCAL2,\n\t\t\"local3\":   syslog.LOG_LOCAL3,\n\t\t\"local4\":   syslog.LOG_LOCAL4,\n\t\t\"local5\":   syslog.LOG_LOCAL5,\n\t\t\"local6\":   syslog.LOG_LOCAL6,\n\t\t\"local7\":   syslog.LOG_LOCAL7,\n\t}\n\n\tnetworks := []string{\"unix\"}\n\tfmtValidFuncs := map[string]func(string, string, string, string, syslog.Priority, bool) error{\n\t\t\"rfc5424\": func(rcvd, msg, tag, hostname string, pri syslog.Priority, isTLS bool) error {\n\t\t\tvar parsedHostname, timestamp string\n\t\t\tvar length, version, pid int\n\t\t\tif !isTLS {\n\t\t\t\texp := fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &version, &timestamp, &parsedHostname, &pid); n != 4 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texp := \"%d \" + fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &length, &version, &timestamp, &parsedHostname, &pid); n != 5 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\trunSyslogTest(t, networks, syslogFacilities, fmtValidFuncs)\n}\n\nfunc TestSyslogFormat(t *testing.T) {\n\tvar syslogFacilities = map[string]syslog.Priority{\n\t\t\"user\": syslog.LOG_USER,\n\t}\n\n\tnetworks := []string{\"unix\"}\n\tfmtValidFuncs := map[string]func(string, string, string, string, syslog.Priority, bool) error{\n\t\t\"\": func(rcvd, msg, tag, hostname string, pri syslog.Priority, isSTLS bool) error {\n\t\t\tvar mon, day, hrs string\n\t\t\tvar pid int\n\t\t\texp := fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%s %s %s \" + tag + \"[%d]: \" + msg + \"\\n\"\n\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &mon, &day, &hrs, &pid); n != 4 || err != nil {\n\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\t\"rfc3164\": func(rcvd, msg, tag, hostname string, pri syslog.Priority, isTLS bool) error {\n\t\t\tvar parsedHostname, mon, day, hrs string\n\t\t\tvar pid int\n\t\t\texp := fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%s %s %s %s \" + tag + \"[%d]: \" + msg + \"\\n\"\n\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &mon, &day, &hrs, &parsedHostname, &pid); n != 5 || err != nil || hostname != parsedHostname {\n\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\t\"rfc5424\": func(rcvd, msg, tag, hostname string, pri syslog.Priority, isTLS bool) error {\n\t\t\tvar parsedHostname, timestamp string\n\t\t\tvar length, version, pid int\n\t\t\tif !isTLS {\n\t\t\t\texp := fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &version, &timestamp, &parsedHostname, &pid); n != 4 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texp := \"%d \" + fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &length, &version, &timestamp, &parsedHostname, &pid); n != 5 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\t\"rfc5424micro\": func(rcvd, msg, tag, hostname string, pri syslog.Priority, isTLS bool) error {\n\t\t\tvar parsedHostname, timestamp string\n\t\t\tvar length, version, pid int\n\t\t\tif !isTLS {\n\t\t\t\texp := fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &version, &timestamp, &parsedHostname, &pid); n != 4 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texp := \"%d \" + fmt.Sprintf(\"<%d>\", pri|syslog.LOG_INFO) + \"%d %s %s \" + tag + \" %d \" + tag + \" - \" + msg + \"\\n\"\n\t\t\t\tif n, err := fmt.Sscanf(rcvd, exp, &length, &version, &timestamp, &parsedHostname, &pid); n != 5 || err != nil || hostname != parsedHostname {\n\t\t\t\t\treturn fmt.Errorf(\"s.Info() = '%q', didn't match '%q' (%d %s)\", rcvd, exp, n, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\trunSyslogTest(t, networks, syslogFacilities, fmtValidFuncs)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_mount_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\tmobymount \"github.com/moby/sys/mount\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunVolume(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\trwDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\troDir, err := os.MkdirTemp(t.TempDir(), \"ro\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trwVolName := tID + \"-rw\"\n\troVolName := tID + \"-ro\"\n\tfor _, v := range []string{rwVolName, roVolName} {\n\t\tdefer base.Cmd(\"volume\", \"rm\", \"-f\", v).Run()\n\t\tbase.Cmd(\"volume\", \"create\", v).AssertOK()\n\t}\n\n\tcontainerName := tID\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\",\n\t\t\"-d\",\n\t\t\"--name\", containerName,\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt2:ro\", roDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt3\", rwVolName),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt4:ro\", roVolName),\n\t\ttestutil.AlpineImage,\n\t\t\"top\",\n\t).AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"sh\", \"-exc\", \"echo -n str1 > /mnt1/file1\").AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"sh\", \"-exc\", \"echo -n str2 > /mnt2/file2\").AssertFail()\n\tbase.Cmd(\"exec\", containerName, \"sh\", \"-exc\", \"echo -n str3 > /mnt3/file3\").AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"sh\", \"-exc\", \"echo -n str4 > /mnt4/file4\").AssertFail()\n\tbase.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt3\", rwVolName),\n\t\ttestutil.AlpineImage,\n\t\t\"cat\", \"/mnt1/file1\", \"/mnt3/file3\",\n\t).AssertOutExactly(\"str1str3\")\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt3/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt3\", rwVolName),\n\t\ttestutil.AlpineImage,\n\t\t\"cat\", \"/mnt3/mnt1/file1\", \"/mnt3/file3\",\n\t).AssertOutExactly(\"str1str3\")\n}\n\nfunc TestRunAnonymousVolume(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/foo\", testutil.AlpineImage).AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"TestVolume2:/foo\", testutil.AlpineImage).AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"TestVolume\", testutil.AlpineImage).AssertOK()\n\n\t// Destination must be an absolute path not named volume\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"TestVolume2:TestVolumes\", testutil.AlpineImage).AssertFail()\n}\n\nfunc TestRunVolumeRelativePath(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Dir = t.TempDir()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"./foo:/mnt/foo\", testutil.AlpineImage).AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"./foo\", testutil.AlpineImage).AssertOK()\n\n\t// Destination must be an absolute path not a relative path\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"./foo:./foo\", testutil.AlpineImage).AssertFail()\n}\n\nfunc TestRunAnonymousVolumeWithTypeMountFlag(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"--mount\", \"type=volume,dst=/foo\", testutil.AlpineImage,\n\t\t\"mountpoint\", \"-q\", \"/foo\").AssertOK()\n}\n\nfunc TestRunAnonymousVolumeWithBuild(t *testing.T) {\n\tt.Parallel()\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rmi\", imageName).Run()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nVOLUME /foo\n        `, testutil.AlpineImage)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, buildCtx).AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/foo\", testutil.AlpineImage,\n\t\t\"mountpoint\", \"-q\", \"/foo\").AssertOK()\n}\n\nfunc TestRunCopyingUpInitialContentsOnVolume(t *testing.T) {\n\tt.Parallel()\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rmi\", imageName).Run()\n\tvolName := testutil.Identifier(t) + \"-vol\"\n\tdefer base.Cmd(\"volume\", \"rm\", volName).Run()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN mkdir -p /mnt && echo hi > /mnt/initial_file\nCMD [\"cat\", \"/mnt/initial_file\"]\n        `, testutil.AlpineImage)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, buildCtx).AssertOK()\n\n\t//AnonymousVolume\n\tbase.Cmd(\"run\", \"--rm\", imageName).AssertOutExactly(\"hi\\n\")\n\tbase.Cmd(\"run\", \"-v\", \"/mnt\", \"--rm\", imageName).AssertOutExactly(\"hi\\n\")\n\n\t//NamedVolume should be automatically created\n\tbase.Cmd(\"run\", \"-v\", volName+\":/mnt\", \"--rm\", imageName).AssertOutExactly(\"hi\\n\")\n}\n\nfunc TestRunCopyingUpInitialContentsOnDockerfileVolume(t *testing.T) {\n\tt.Parallel()\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rmi\", imageName).Run()\n\tvolName := testutil.Identifier(t) + \"-vol\"\n\tdefer base.Cmd(\"volume\", \"rm\", volName).Run()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN mkdir -p /mnt && echo hi > /mnt/initial_file\nVOLUME /mnt\nCMD [\"cat\", \"/mnt/initial_file\"]\n        `, testutil.AlpineImage)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, buildCtx).AssertOK()\n\t//AnonymousVolume\n\tbase.Cmd(\"run\", \"--rm\", imageName).AssertOutExactly(\"hi\\n\")\n\tbase.Cmd(\"run\", \"-v\", \"/mnt\", \"--rm\", imageName).AssertOutExactly(\"hi\\n\")\n\n\t//NamedVolume\n\tbase.Cmd(\"volume\", \"create\", volName).AssertOK()\n\tbase.Cmd(\"run\", \"-v\", volName+\":/mnt\", \"--rm\", imageName).AssertOutExactly(\"hi\\n\")\n\n\t//mount bind\n\ttmpDir, err := os.MkdirTemp(t.TempDir(), \"hostDir\")\n\tassert.NilError(t, err)\n\n\tbase.Cmd(\"run\", \"-v\", fmt.Sprintf(\"%s:/mnt\", tmpDir), \"--rm\", imageName).AssertFail()\n}\n\nfunc TestRunCopyingUpInitialContentsOnVolumeShouldRetainSymlink(t *testing.T) {\n\tt.Parallel()\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rmi\", imageName).Run()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN ln -s ../../../../../../../../../../../../../../../../../../etc/passwd /mnt/passwd\nVOLUME /mnt\nCMD [\"readlink\", \"/mnt/passwd\"]\n        `, testutil.AlpineImage)\n\tconst expected = \"../../../../../../../../../../../../../../../../../../etc/passwd\\n\"\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, buildCtx).AssertOK()\n\n\tbase.Cmd(\"run\", \"--rm\", imageName).AssertOutExactly(expected)\n\tbase.Cmd(\"run\", \"-v\", \"/mnt\", \"--rm\", imageName).AssertOutExactly(expected)\n}\n\nfunc TestRunCopyingUpInitialContentsShouldNotResetTheCopiedContents(t *testing.T) {\n\tt.Parallel()\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\timageName := tID + \"-img\"\n\tvolumeName := tID + \"-vol\"\n\tcontainerName := tID\n\tdefer func() {\n\t\tbase.Cmd(\"rm\", \"-f\", containerName).Run()\n\t\tbase.Cmd(\"volume\", \"rm\", volumeName).Run()\n\t\tbase.Cmd(\"rmi\", imageName).Run()\n\t}()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN echo -n \"rev0\" > /mnt/file\n`, testutil.AlpineImage)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, buildCtx).AssertOK()\n\n\tbase.Cmd(\"volume\", \"create\", volumeName)\n\trunContainer := func() {\n\t\tbase.Cmd(\"run\", \"-d\", \"--name\", containerName, \"-v\", volumeName+\":/mnt\", imageName, \"sleep\", nerdtest.Infinity).AssertOK()\n\t}\n\trunContainer()\n\tbase.EnsureContainerStarted(containerName)\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt/file\").AssertOutExactly(\"rev0\")\n\tbase.Cmd(\"exec\", containerName, \"sh\", \"-euc\", \"echo -n \\\"rev1\\\" >/mnt/file\").AssertOK()\n\tbase.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\trunContainer()\n\tbase.EnsureContainerStarted(containerName)\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt/file\").AssertOutExactly(\"rev1\")\n}\n\nfunc TestRunTmpfs(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tf := func(allow, deny []string) func(stdout string) error {\n\t\treturn func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) != 1 {\n\t\t\t\treturn fmt.Errorf(\"expected 1 lines, got %q\", stdout)\n\t\t\t}\n\t\t\tfor _, s := range allow {\n\t\t\t\tif !strings.Contains(stdout, s) {\n\t\t\t\t\treturn fmt.Errorf(\"expected stdout to contain %q, got %q\", s, stdout)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, s := range deny {\n\t\t\t\tif strings.Contains(stdout, s) {\n\t\t\t\t\treturn fmt.Errorf(\"expected stdout not to contain %q, got %q\", s, stdout)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\tbase.Cmd(\"run\", \"--rm\", \"--tmpfs\", \"/tmp\", testutil.AlpineImage, \"grep\", \"/tmp\", \"/proc/mounts\").AssertOutWithFunc(f([]string{\"rw\", \"nosuid\", \"nodev\", \"noexec\"}, nil))\n\tbase.Cmd(\"run\", \"--rm\", \"--tmpfs\", \"/tmp:size=64m,exec\", testutil.AlpineImage, \"grep\", \"/tmp\", \"/proc/mounts\").AssertOutWithFunc(f([]string{\"rw\", \"nosuid\", \"nodev\", \"size=65536k\"}, []string{\"noexec\"}))\n\t// for https://github.com/containerd/nerdctl/issues/594\n\tbase.Cmd(\"run\", \"--rm\", \"--tmpfs\", \"/dev/shm:rw,exec,size=1g\", testutil.AlpineImage, \"grep\", \"/dev/shm\", \"/proc/mounts\").AssertOutWithFunc(f([]string{\"rw\", \"nosuid\", \"nodev\", \"size=1048576k\"}, []string{\"noexec\"}))\n}\n\nfunc TestRunBindMountTmpfs(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tf := func(allow []string) func(stdout string) error {\n\t\treturn func(stdout string) error {\n\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\tif len(lines) != 1 {\n\t\t\t\treturn fmt.Errorf(\"expected 1 lines, got %q\", stdout)\n\t\t\t}\n\t\t\tfor _, s := range allow {\n\t\t\t\tif !strings.Contains(stdout, s) {\n\t\t\t\t\treturn fmt.Errorf(\"expected stdout to contain %q, got %q\", s, stdout)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\tbase.Cmd(\"run\", \"--rm\", \"--mount\", \"type=tmpfs,target=/tmp\", testutil.AlpineImage, \"grep\", \"/tmp\", \"/proc/mounts\").AssertOutWithFunc(f([]string{\"rw\", \"nosuid\", \"nodev\", \"noexec\"}))\n\tbase.Cmd(\"run\", \"--rm\", \"--mount\", \"type=tmpfs,target=/tmp,tmpfs-size=64m\", testutil.AlpineImage, \"grep\", \"/tmp\", \"/proc/mounts\").AssertOutWithFunc(f([]string{\"rw\", \"nosuid\", \"nodev\", \"size=65536k\"}))\n}\n\nfunc mountExistsWithOpt(mountPoint, mountOpt string) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tmountOutput := []string{}\n\t\tfor _, line := range lines {\n\t\t\tif strings.Contains(line, mountPoint) {\n\t\t\t\tmountOutput = strings.Split(line, \" \")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.Assert(t, len(mountOutput) > 0, \"we should have found the mount point in /proc/mounts\")\n\t\tassert.Assert(t, len(mountOutput) >= 4, \"invalid format for mount line\")\n\n\t\toptions := strings.Split(mountOutput[3], \",\")\n\n\t\tfound := false\n\t\tfor _, opt := range options {\n\t\t\tif mountOpt == opt {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tassert.Assert(t, found, \"mount option %s not found\", mountOpt)\n\t}\n}\n\nfunc TestRunBindMountBind(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// Run a container with bind mount directories, one rw, the other ro\n\t\trwDir := data.Temp().Dir(\"rw\")\n\t\troDir := data.Temp().Dir(\"ro\")\n\n\t\thelpers.Ensure(\n\t\t\t\"run\",\n\t\t\t\"-d\",\n\t\t\t\"--name\", data.Identifier(\"container\"),\n\t\t\t\"--mount\", fmt.Sprintf(\"type=bind,src=%s,target=/mntrw\", rwDir),\n\t\t\t\"--mount\", fmt.Sprintf(\"type=bind,src=%s,target=/mntro,ro\", roDir),\n\t\t\ttestutil.AlpineImage,\n\t\t\t\"top\",\n\t\t)\n\n\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"container\"))\n\n\t\t// Save host rwDir location and container id for subtests\n\t\tdata.Labels().Set(\"container\", data.Identifier(\"container\"))\n\t\tdata.Labels().Set(\"rwDir\", rwDir)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"ensure we cannot write to ro mount\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container\"), \"sh\", \"-exc\", \"echo -n failure > /mntro/file\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"ensure we can write to rw, and read it back from another container mounting the same target\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"exec\", data.Labels().Get(\"container\"), \"sh\", \"-exc\", \"echo -n success > /mntrw/file\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\"run\",\n\t\t\t\t\t\"--rm\",\n\t\t\t\t\t\"--mount\", fmt.Sprintf(\"type=bind,src=%s,target=/mntrw\", data.Labels().Get(\"rwDir\")),\n\t\t\t\t\ttestutil.AlpineImage,\n\t\t\t\t\t\"cat\", \"/mntrw/file\",\n\t\t\t\t)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"success\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Check that mntrw is seen in /proc/mounts\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container\"), \"cat\", \"/proc/mounts\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t// Ensure we have mntrw in the mount list\n\t\t\t\t\t\tmountExistsWithOpt(\"/mntrw\", \"rw\"),\n\t\t\t\t\t\tmountExistsWithOpt(\"/mntro\", \"ro\"),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunMountBindMode(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"must be superuser to use mount\")\n\t}\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\ttmpDir1, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(tmpDir1)\n\ttmpDir1Mnt := filepath.Join(tmpDir1, \"mnt\")\n\tif err := os.MkdirAll(tmpDir1Mnt, 0700); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttmpDir2, err := os.MkdirTemp(t.TempDir(), \"ro\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(tmpDir2)\n\n\tif err := mobymount.Mount(tmpDir2, tmpDir1Mnt, \"none\", \"bind,ro\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := mobymount.Unmount(tmpDir1Mnt); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}()\n\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"--mount\", fmt.Sprintf(\"type=bind,bind-nonrecursive,src=%s,target=/mnt1\", tmpDir1),\n\t\ttestutil.AlpineImage,\n\t\t\"sh\", \"-euxc\", \"apk add findmnt -q && findmnt -nR /mnt1\",\n\t).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 1 {\n\t\t\treturn fmt.Errorf(\"expected 1 line, got %q\", stdout)\n\t\t}\n\t\tif !strings.HasPrefix(lines[0], \"/mnt1\") {\n\t\t\treturn fmt.Errorf(\"expected mount /mnt1, got %q\", lines[0])\n\t\t}\n\t\treturn nil\n\t})\n\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"--mount\", fmt.Sprintf(\"type=bind,bind-nonrecursive=false,src=%s,target=/mnt1\", tmpDir1),\n\t\ttestutil.AlpineImage,\n\t\t\"sh\", \"-euxc\", \"apk add findmnt -q && findmnt -nR /mnt1\",\n\t).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 2 {\n\t\t\treturn fmt.Errorf(\"expected 2 line, got %q\", stdout)\n\t\t}\n\t\tif !strings.HasPrefix(lines[0], \"/mnt1\") {\n\t\t\treturn fmt.Errorf(\"expected mount /mnt1, got %q\", lines[0])\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestRunVolumeBindMode(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"must be superuser to use mount\")\n\t}\n\ttestutil.DockerIncompatible(t)\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\ttmpDir1, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(tmpDir1)\n\ttmpDir1Mnt := filepath.Join(tmpDir1, \"mnt\")\n\tif err := os.MkdirAll(tmpDir1Mnt, 0700); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttmpDir2, err := os.MkdirTemp(t.TempDir(), \"ro\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(tmpDir2)\n\n\tif err := mobymount.Mount(tmpDir2, tmpDir1Mnt, \"none\", \"bind,ro\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := mobymount.Unmount(tmpDir1Mnt); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}()\n\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt1:bind\", tmpDir1),\n\t\ttestutil.AlpineImage,\n\t\t\"sh\", \"-euxc\", \"apk add findmnt -q && findmnt -nR /mnt1\",\n\t).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 1 {\n\t\t\treturn fmt.Errorf(\"expected 1 line, got %q\", stdout)\n\t\t}\n\t\tif !strings.HasPrefix(lines[0], \"/mnt1\") {\n\t\t\treturn fmt.Errorf(\"expected mount /mnt1, got %q\", lines[0])\n\t\t}\n\t\treturn nil\n\t})\n\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt1:rbind\", tmpDir1),\n\t\ttestutil.AlpineImage,\n\t\t\"sh\", \"-euxc\", \"apk add findmnt -q && findmnt -nR /mnt1\",\n\t).AssertOutWithFunc(func(stdout string) error {\n\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\tif len(lines) != 2 {\n\t\t\treturn fmt.Errorf(\"expected 2 line, got %q\", stdout)\n\t\t}\n\t\tif !strings.HasPrefix(lines[0], \"/mnt1\") {\n\t\t\treturn fmt.Errorf(\"expected mount /mnt1, got %q\", lines[0])\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc TestRunBindMountPropagation(t *testing.T) {\n\tt.Skip(\"This test is currently broken. See https://github.com/containerd/nerdctl/issues/3404\")\n\n\ttID := testutil.Identifier(t)\n\n\tif !isRootfsShareableMount() {\n\t\tt.Skipf(\"rootfs doesn't support shared mount, skip test %s\", tID)\n\t}\n\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\ttestCases := []struct {\n\t\tpropagation string\n\t\tassertFunc  func(containerName, containerNameReplica string)\n\t}{\n\t\t{\n\t\t\tpropagation: \"rshared\",\n\t\t\tassertFunc: func(containerName, containerNameReplica string) {\n\t\t\t\t// replica can get sub-mounts from original\n\t\t\t\tbase.Cmd(\"exec\", containerNameReplica, \"cat\", \"/mnt1/replica/foo.txt\").AssertOutExactly(\"toreplica\")\n\n\t\t\t\t// and sub-mounts from replica will be propagated to the original too\n\t\t\t\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt1/bar/bar.txt\").AssertOutExactly(\"fromreplica\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tpropagation: \"rslave\",\n\t\t\tassertFunc: func(containerName, containerNameReplica string) {\n\t\t\t\t// replica can get sub-mounts from original\n\t\t\t\tbase.Cmd(\"exec\", containerNameReplica, \"cat\", \"/mnt1/replica/foo.txt\").AssertOutExactly(\"toreplica\")\n\n\t\t\t\t// but sub-mounts from replica will not be propagated to the original\n\t\t\t\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt1/bar/bar.txt\").AssertFail()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tpropagation: \"rprivate\",\n\t\t\tassertFunc: func(containerName, containerNameReplica string) {\n\t\t\t\t// replica can't get sub-mounts from original\n\t\t\t\tbase.Cmd(\"exec\", containerNameReplica, \"cat\", \"/mnt1/replica/foo.txt\").AssertFail()\n\t\t\t\t// and sub-mounts from replica will not be propagated to the original too\n\t\t\t\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt1/bar/bar.txt\").AssertFail()\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tpropagation: \"\",\n\t\t\tassertFunc: func(containerName, containerNameReplica string) {\n\t\t\t\t// replica can't get sub-mounts from original\n\t\t\t\tbase.Cmd(\"exec\", containerNameReplica, \"cat\", \"/mnt1/replica/foo.txt\").AssertFail()\n\t\t\t\t// and sub-mounts from replica will not be propagated to the original too\n\t\t\t\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt1/bar/bar.txt\").AssertFail()\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tpropagationName := tc.propagation\n\t\tif propagationName == \"\" {\n\t\t\tpropagationName = \"default\"\n\t\t}\n\n\t\tt.Logf(\"Running test propagation case %s\", propagationName)\n\n\t\trwDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tcontainerName := tID + \"-\" + propagationName\n\t\tcontainerNameReplica := containerName + \"-replica\"\n\n\t\tmountOption := fmt.Sprintf(\"type=bind,src=%s,target=/mnt1,bind-propagation=%s\", rwDir, tc.propagation)\n\t\tif tc.propagation == \"\" {\n\t\t\tmountOption = fmt.Sprintf(\"type=bind,src=%s,target=/mnt1\", rwDir)\n\t\t}\n\n\t\tcontainers := []struct {\n\t\t\tname        string\n\t\t\tmountOption string\n\t\t}{\n\t\t\t{\n\t\t\t\tname:        containerName,\n\t\t\t\tmountOption: fmt.Sprintf(\"type=bind,src=%s,target=/mnt1,bind-propagation=rshared\", rwDir),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:        containerNameReplica,\n\t\t\t\tmountOption: mountOption,\n\t\t\t},\n\t\t}\n\t\tfor _, c := range containers {\n\t\t\tbase.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--privileged\",\n\t\t\t\t\"--name\", c.name,\n\t\t\t\t\"--mount\", c.mountOption,\n\t\t\t\ttestutil.AlpineImage,\n\t\t\t\t\"top\").AssertOK()\n\t\t\tdefer base.Cmd(\"rm\", \"-f\", c.name).Run()\n\t\t}\n\n\t\t// mount in the first container\n\t\tbase.Cmd(\"exec\", containerName, \"sh\", \"-exc\", \"mkdir /app && mkdir /mnt1/replica && mount --bind /app /mnt1/replica && echo -n toreplica > /app/foo.txt\").AssertOK()\n\t\tbase.Cmd(\"exec\", containerName, \"cat\", \"/mnt1/replica/foo.txt\").AssertOutExactly(\"toreplica\")\n\n\t\t// mount in the second container\n\t\tbase.Cmd(\"exec\", containerNameReplica, \"sh\", \"-exc\", \"mkdir /bar && mkdir /mnt1/bar\").AssertOK()\n\t\tbase.Cmd(\"exec\", containerNameReplica, \"sh\", \"-exc\", \"mount --bind /bar /mnt1/bar\").AssertOK()\n\n\t\tbase.Cmd(\"exec\", containerNameReplica, \"sh\", \"-exc\", \"echo -n fromreplica > /bar/bar.txt\").AssertOK()\n\t\tbase.Cmd(\"exec\", containerNameReplica, \"cat\", \"/mnt1/bar/bar.txt\").AssertOutExactly(\"fromreplica\")\n\n\t\t// call case specific assert function\n\t\ttc.assertFunc(containerName, containerNameReplica)\n\n\t\t// umount mount point in the first privileged container\n\t\tbase.Cmd(\"exec\", containerNameReplica, \"sh\", \"-exc\", \"umount /mnt1/bar\").AssertOK()\n\t\tbase.Cmd(\"exec\", containerName, \"sh\", \"-exc\", \"umount /mnt1/replica\").AssertOK()\n\t}\n}\n\n// isRootfsShareableMount will check if /tmp or / support shareable mount\nfunc isRootfsShareableMount() bool {\n\texistFunc := func(mi mount.Info) bool {\n\t\tfor _, opt := range strings.Split(mi.Optional, \" \") {\n\t\t\tif strings.HasPrefix(opt, \"shared:\") {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tmi, err := mount.Lookup(\"/tmp\")\n\tif err == nil {\n\t\treturn existFunc(mi)\n\t}\n\n\tmi, err = mount.Lookup(\"/\")\n\tif err == nil {\n\t\treturn existFunc(mi)\n\t}\n\n\treturn false\n}\n\nfunc TestRunVolumesFrom(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\trwDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\troDir, err := os.MkdirTemp(t.TempDir(), \"ro\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trwVolName := tID + \"-rw\"\n\troVolName := tID + \"-ro\"\n\tfor _, v := range []string{rwVolName, roVolName} {\n\t\tdefer base.Cmd(\"volume\", \"rm\", \"-f\", v).Run()\n\t\tbase.Cmd(\"volume\", \"create\", v).AssertOK()\n\t}\n\n\tfromContainerName := tID + \"-from\"\n\ttoContainerName := tID + \"-to\"\n\tdefer base.Cmd(\"rm\", \"-f\", fromContainerName).AssertOK()\n\tdefer base.Cmd(\"rm\", \"-f\", toContainerName).AssertOK()\n\tbase.Cmd(\"run\",\n\t\t\"-d\",\n\t\t\"--name\", fromContainerName,\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt2:ro\", roDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt3\", rwVolName),\n\t\t\"-v\", fmt.Sprintf(\"%s:/mnt4:ro\", roVolName),\n\t\ttestutil.AlpineImage,\n\t\t\"top\",\n\t).AssertOK()\n\tbase.Cmd(\"run\",\n\t\t\"-d\",\n\t\t\"--name\", toContainerName,\n\t\t\"--volumes-from\", fromContainerName,\n\t\ttestutil.AlpineImage,\n\t\t\"top\",\n\t).AssertOK()\n\tbase.Cmd(\"exec\", toContainerName, \"sh\", \"-exc\", \"echo -n str1 > /mnt1/file1\").AssertOK()\n\tbase.Cmd(\"exec\", toContainerName, \"sh\", \"-exc\", \"echo -n str2 > /mnt2/file2\").AssertFail()\n\tbase.Cmd(\"exec\", toContainerName, \"sh\", \"-exc\", \"echo -n str3 > /mnt3/file3\").AssertOK()\n\tbase.Cmd(\"exec\", toContainerName, \"sh\", \"-exc\", \"echo -n str4 > /mnt4/file4\").AssertFail()\n\tbase.Cmd(\"rm\", \"-f\", toContainerName).AssertOK()\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"--volumes-from\", fromContainerName,\n\t\ttestutil.AlpineImage,\n\t\t\"cat\", \"/mnt1/file1\", \"/mnt3/file3\",\n\t).AssertOutExactly(\"str1str3\")\n}\n\nfunc TestBindMountWhenHostFolderDoesNotExist(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t) + \"-host-dir-not-found\"\n\thostDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(hostDir)\n\thp := filepath.Join(hostDir, \"does-not-exist\")\n\tbase.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\", \"--name\", containerName, \"-d\", \"-v\", fmt.Sprintf(\"%s:/tmp\",\n\t\thp), testutil.AlpineImage).AssertOK()\n\tbase.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\n\t// Host directory should get created\n\t_, err = os.Stat(hp)\n\tassert.NilError(t, err)\n\n\t// Test for --mount\n\tos.RemoveAll(hp)\n\tbase.Cmd(\"run\", \"--name\", containerName, \"-d\", \"--mount\", fmt.Sprintf(\"type=bind, source=%s, target=/tmp\",\n\t\thp), testutil.AlpineImage).AssertFail()\n\t_, err = os.Stat(hp)\n\tassert.ErrorIs(t, err, os.ErrNotExist)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_mount_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestRunMountVolume(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\trwDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\troDir, err := os.MkdirTemp(t.TempDir(), \"ro\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trwVolName := tID + \"-rw\"\n\troVolName := tID + \"-ro\"\n\tfor _, v := range []string{rwVolName, roVolName} {\n\t\tdefer base.Cmd(\"volume\", \"rm\", \"-f\", v).Run()\n\t\tbase.Cmd(\"volume\", \"create\", v).AssertOK()\n\t}\n\n\tcontainerName := tID\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\",\n\t\t\"-d\",\n\t\t\"--name\", containerName,\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt2:ro\", roDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt3\", rwVolName),\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt4:ro\", roVolName),\n\t\ttestutil.CommonImage,\n\t\t\"ping localhost -t\",\n\t).AssertOK()\n\n\tbase.Cmd(\"exec\", containerName, \"cmd\", \"/c\", \"echo -n str1 > C:/mnt1/file1\").AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"cmd\", \"/c\", \"echo -n str2 > C:/mnt2/file2\").AssertFail()\n\tbase.Cmd(\"exec\", containerName, \"cmd\", \"/c\", \"echo -n str3 > C:/mnt3/file3\").AssertOK()\n\tbase.Cmd(\"exec\", containerName, \"cmd\", \"/c\", \"echo -n str4 > C:/mnt4/file4\").AssertFail()\n\tbase.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt3\", rwVolName),\n\t\ttestutil.CommonImage,\n\t\t\"cat\", \"C:/mnt1/file1\", \"C:/mnt3/file3\",\n\t).AssertOutContainsAll(\"str1\", \"str3\")\n\tbase.Cmd(\"run\",\n\t\t\"--rm\",\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt3/mnt1\", rwDir),\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt3\", rwVolName),\n\t\ttestutil.CommonImage,\n\t\t\"cat\", \"C:/mnt3/mnt1/file1\", \"C:/mnt3/file3\",\n\t).AssertOutContainsAll(\"str1\", \"str3\")\n}\n\nfunc TestRunMountVolumeInspect(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\ttestContainer := testutil.Identifier(t)\n\ttestVolume := testutil.Identifier(t)\n\n\tdefer base.Cmd(\"volume\", \"rm\", \"-f\", testVolume).Run()\n\tbase.Cmd(\"volume\", \"create\", testVolume).AssertOK()\n\tinspectVolume := base.InspectVolume(testVolume)\n\tnamedVolumeSource := inspectVolume.Mountpoint\n\n\tbase.Cmd(\n\t\t\"run\", \"-d\", \"--name\", testContainer,\n\t\t\"-v\", \"C:/mnt1\",\n\t\t\"-v\", \"C:/mnt2:C:/mnt2\",\n\t\t\"-v\", \"\\\\\\\\.\\\\pipe\\\\containerd-containerd:\\\\\\\\.\\\\pipe\\\\containerd-containerd\",\n\t\t\"-v\", fmt.Sprintf(\"%s:C:/mnt3\", testVolume),\n\t\ttestutil.CommonImage,\n\t).AssertOK()\n\n\tinspect := base.InspectContainer(testContainer)\n\t// convert array to map to get by key of Destination\n\tactual := make(map[string]dockercompat.MountPoint)\n\tfor i := range inspect.Mounts {\n\t\tactual[inspect.Mounts[i].Destination] = inspect.Mounts[i]\n\t}\n\n\texpected := []struct {\n\t\tdest       string\n\t\tmountPoint dockercompat.MountPoint\n\t}{\n\t\t// anonymous volume\n\t\t{\n\t\t\tdest: \"C:\\\\mnt1\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"volume\",\n\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\tDestination: \"C:\\\\mnt1\",\n\t\t\t},\n\t\t},\n\n\t\t// bind\n\t\t{\n\t\t\tdest: \"C:\\\\mnt2\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"bind\",\n\t\t\t\tSource:      \"C:\\\\mnt2\",\n\t\t\t\tDestination: \"C:\\\\mnt2\",\n\t\t\t},\n\t\t},\n\n\t\t// named pipe\n\t\t{\n\t\t\tdest: \"\\\\\\\\.\\\\pipe\\\\containerd-containerd\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"npipe\",\n\t\t\t\tSource:      \"\\\\\\\\.\\\\pipe\\\\containerd-containerd\",\n\t\t\t\tDestination: \"\\\\\\\\.\\\\pipe\\\\containerd-containerd\",\n\t\t\t},\n\t\t},\n\n\t\t// named volume\n\t\t{\n\t\t\tdest: \"C:\\\\mnt3\",\n\t\t\tmountPoint: dockercompat.MountPoint{\n\t\t\t\tType:        \"volume\",\n\t\t\t\tName:        testVolume,\n\t\t\t\tSource:      namedVolumeSource,\n\t\t\t\tDestination: \"C:\\\\mnt3\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i := range expected {\n\t\ttestCase := expected[i]\n\t\tt.Logf(\"test volume[dest=%q]\", testCase.dest)\n\n\t\tmountPoint, ok := actual[testCase.dest]\n\t\tassert.Assert(base.T, ok)\n\n\t\tassert.Equal(base.T, testCase.mountPoint.Type, mountPoint.Type)\n\t\tassert.Equal(base.T, testCase.mountPoint.Destination, mountPoint.Destination)\n\n\t\tif testCase.mountPoint.Source == \"\" {\n\t\t\t// for anonymous volumes, we want to make sure that the source is not the same as the destination\n\t\t\tassert.Assert(base.T, mountPoint.Source != testCase.mountPoint.Destination)\n\t\t} else {\n\t\t\tassert.Equal(base.T, testCase.mountPoint.Source, mountPoint.Source)\n\t\t}\n\n\t\tif testCase.mountPoint.Name != \"\" {\n\t\t\tassert.Equal(base.T, testCase.mountPoint.Name, mountPoint.Name)\n\t\t}\n\t}\n}\n\nfunc TestRunMountAnonymousVolume(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"TestVolume:C:/mnt\", testutil.CommonImage).AssertOK()\n\n\t// For docker-campatibility, Unrecognised volume spec: invalid volume specification: 'TestVolume'\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"TestVolume\", testutil.CommonImage).AssertFail()\n\n\t// Destination must be an absolute path not named volume\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"TestVolume2:TestVolumes\", testutil.CommonImage).AssertFail()\n}\n\nfunc TestRunMountRelativePath(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"./mnt:C:/mnt1\", testutil.CommonImage, \"cmd\").AssertOK()\n\n\t// Destination cannot be a relative path\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"./mnt\", testutil.CommonImage).AssertFail()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"./mnt:./mnt1\", testutil.CommonImage, \"cmd\").AssertFail()\n}\n\nfunc TestRunMountNamedPipeVolume(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", `\\\\.\\pipe\\containerd-containerd`, testutil.CommonImage).AssertFail()\n}\n\nfunc TestRunMountVolumeSpec(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", `InvalidPathC:\\TestVolume:C:\\Mount`, testutil.CommonImage).AssertFail()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", `C:\\TestVolume:C:\\Mount:ro,rw:boot`, testutil.CommonImage).AssertFail()\n\n\t// If -v is an empty string, it will be ignored\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"\", testutil.CommonImage).AssertOK()\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_network.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc loadNetworkFlags(cmd *cobra.Command, globalOpts types.GlobalCommandOptions) (types.NetworkOptions, error) {\n\tnetOpts := types.NetworkOptions{}\n\n\t// --net/--network=<net name> ...\n\tvar netSlice = []string{}\n\tvar networkSet = false\n\tif cmd.Flags().Lookup(\"network\").Changed {\n\t\tnetwork, err := cmd.Flags().GetStringSlice(\"network\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tnetSlice = append(netSlice, network...)\n\t\tnetworkSet = true\n\t}\n\tif cmd.Flags().Lookup(\"net\").Changed {\n\t\tnet, err := cmd.Flags().GetStringSlice(\"net\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tnetSlice = append(netSlice, net...)\n\t\tnetworkSet = true\n\t}\n\n\tif !networkSet {\n\t\tnetwork, err := cmd.Flags().GetStringSlice(\"network\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tnetSlice = append(netSlice, network...)\n\t}\n\tnetOpts.NetworkSlice = strutil.DedupeStrSlice(netSlice)\n\n\t// --mac-address=<MAC>\n\tmacAddress, err := cmd.Flags().GetString(\"mac-address\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tif macAddress != \"\" {\n\t\tif _, err := net.ParseMAC(macAddress); err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t}\n\tnetOpts.MACAddress = macAddress\n\n\t// --ip=<container static IP>\n\tipAddress, err := cmd.Flags().GetString(\"ip\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tnetOpts.IPAddress = ipAddress\n\n\t// --ip6=<container static IP6>\n\tip6Address, err := cmd.Flags().GetString(\"ip6\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tnetOpts.IP6Address = ip6Address\n\n\t// -h/--hostname=<container hostname>\n\thostName, err := cmd.Flags().GetString(\"hostname\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tnetOpts.Hostname = hostName\n\n\t// --domainname=<container domainname>\n\tdomainname, err := cmd.Flags().GetString(\"domainname\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tnetOpts.Domainname = domainname\n\n\t// --dns=<DNS host> ...\n\t// Use command flags if set, otherwise use global config is set\n\tvar dnsSlice []string\n\tif cmd.Flags().Changed(\"dns\") {\n\t\tvar err error\n\t\tdnsSlice, err = cmd.Flags().GetStringSlice(\"dns\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tif len(dnsSlice) == 0 {\n\t\t\treturn netOpts, errors.New(\"--dns flag was specified but no DNS server was provided\")\n\t\t}\n\t\tfor _, dns := range dnsSlice {\n\t\t\tif _, err := dnsutil.ValidateIPAddress(dns); err != nil {\n\t\t\t\treturn netOpts, fmt.Errorf(\"%w with --dns flag\", err)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tdnsSlice = globalOpts.DNS\n\t}\n\tnetOpts.DNSServers = strutil.DedupeStrSlice(dnsSlice)\n\n\t// --dns-search=<domain name> ...\n\t// Use command flags if set, otherwise use global config is set\n\tvar dnsSearchSlice []string\n\tif cmd.Flags().Changed(\"dns-search\") {\n\t\tvar err error\n\t\tdnsSearchSlice, err = cmd.Flags().GetStringSlice(\"dns-search\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t} else {\n\t\tdnsSearchSlice = globalOpts.DNSSearch\n\t}\n\tnetOpts.DNSSearchDomains = strutil.DedupeStrSlice(dnsSearchSlice)\n\n\t// --dns-opt/--dns-option=<resolv.conf line> ...\n\t// Use command flags if set, otherwise use global config if set\n\tdnsOptions := []string{}\n\n\t// Check if either dns-opt or dns-option flags were set\n\tdnsOptChanged := cmd.Flags().Changed(\"dns-opt\")\n\tdnsOptionChanged := cmd.Flags().Changed(\"dns-option\")\n\n\tif dnsOptChanged || dnsOptionChanged {\n\t\t// Use command flags\n\t\tdnsOptFlags, err := cmd.Flags().GetStringSlice(\"dns-opt\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tdnsOptions = append(dnsOptions, dnsOptFlags...)\n\n\t\tdnsOptionFlags, err := cmd.Flags().GetStringSlice(\"dns-option\")\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tdnsOptions = append(dnsOptions, dnsOptionFlags...)\n\t} else {\n\t\t// Use global config defaults\n\t\tdnsOptions = append(dnsOptions, globalOpts.DNSOpts...)\n\t}\n\n\tnetOpts.DNSResolvConfOptions = strutil.DedupeStrSlice(dnsOptions)\n\n\t// --add-host=<host:IP> ...\n\taddHostFlags, err := cmd.Flags().GetStringSlice(\"add-host\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tnetOpts.AddHost = addHostFlags\n\n\t// --uts=<Unix Time Sharing namespace>\n\tutsNamespace, err := cmd.Flags().GetString(\"uts\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tnetOpts.UTSNamespace = utsNamespace\n\n\t// -p/--publish=127.0.0.1:80:8080/tcp ...\n\tportSlice, err := cmd.Flags().GetStringSlice(\"publish\")\n\tif err != nil {\n\t\treturn netOpts, err\n\t}\n\tportSlice = strutil.DedupeStrSlice(portSlice)\n\tportMappings := []cni.PortMapping{}\n\tfor _, p := range portSlice {\n\t\tpm, err := portutil.ParseFlagP(p)\n\t\tif err != nil {\n\t\t\treturn netOpts, err\n\t\t}\n\t\tportMappings = append(portMappings, pm...)\n\t}\n\tnetOpts.PortMappings = portMappings\n\n\treturn netOpts, nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_network_base_test.go",
    "content": "//go:build linux || windows\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\n// Tests various port mapping argument combinations by starting an nginx container and\n// verifying its connectivity and that its serves its index.html from the external\n// host IP as well as through the loopback interface.\n// `loopbackIsolationEnabled` indicates whether the test should expect connections between\n// the loopback interface and external host interface to succeed or not.\nfunc baseTestRunPort(t *testing.T, nginxImage string, nginxIndexHTMLSnippet string, loopbackIsolationEnabled bool) {\n\texpectedIsolationErr := \"\"\n\tif loopbackIsolationEnabled {\n\t\texpectedIsolationErr = testutil.ExpectedConnectionRefusedError\n\t}\n\n\thostIP, err := nettestutil.NonLoopbackIPv4()\n\tassert.NilError(t, err)\n\ttype testCase struct {\n\t\tlistenIP         net.IP\n\t\tconnectIP        net.IP\n\t\thostPort         string\n\t\tcontainerPort    string\n\t\tconnectURLPort   int\n\t\trunShouldSuccess bool\n\t\terr              string\n\t}\n\tlo := net.ParseIP(\"127.0.0.1\")\n\tzeroIP := net.ParseIP(\"0.0.0.0\")\n\ttestCases := []testCase{\n\t\t{\n\t\t\tlistenIP:         lo,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"8080\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   8080,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\t// for https://github.com/containerd/nerdctl/issues/88\n\t\t\tlistenIP:         hostIP,\n\t\t\tconnectIP:        hostIP,\n\t\t\thostPort:         \"8080\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   8080,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         hostIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"8080\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   8080,\n\t\t\terr:              expectedIsolationErr,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         lo,\n\t\t\tconnectIP:        hostIP,\n\t\t\thostPort:         \"8080\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   8080,\n\t\t\terr:              expectedIsolationErr,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"8080\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   8080,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        hostIP,\n\t\t\thostPort:         \"8080\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   8080,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         lo,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"79-84\",\n\t\t\tconnectURLPort:   7001,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         hostIP,\n\t\t\tconnectIP:        hostIP,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"79-84\",\n\t\t\tconnectURLPort:   7001,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         hostIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"79-84\",\n\t\t\tconnectURLPort:   7001,\n\t\t\terr:              expectedIsolationErr,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         lo,\n\t\t\tconnectIP:        hostIP,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"79-84\",\n\t\t\tconnectURLPort:   7001,\n\t\t\terr:              expectedIsolationErr,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        hostIP,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"79-84\",\n\t\t\tconnectURLPort:   7001,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"80-85\",\n\t\t\tconnectURLPort:   7001,\n\t\t\terr:              \"error after 5 attempts\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   7000,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"80\",\n\t\t\tconnectURLPort:   7005,\n\t\t\terr:              testutil.ExpectedConnectionRefusedError,\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tlistenIP:         zeroIP,\n\t\t\tconnectIP:        lo,\n\t\t\thostPort:         \"7000-7005\",\n\t\t\tcontainerPort:    \"79-85\",\n\t\t\tconnectURLPort:   7005,\n\t\t\terr:              \"invalid ranges specified for container and host Ports\",\n\t\t\trunShouldSuccess: false,\n\t\t},\n\t}\n\n\ttID := testutil.Identifier(t)\n\tfor i, tc := range testCases {\n\t\ti := i\n\t\ttc := tc\n\t\ttcName := fmt.Sprintf(\"%+v\", tc)\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\ttestContainerName := fmt.Sprintf(\"%s-%d\", tID, i)\n\t\t\tbase := testutil.NewBase(t)\n\t\t\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\t\t\tpFlag := fmt.Sprintf(\"%s:%s:%s\", tc.listenIP.String(), tc.hostPort, tc.containerPort)\n\t\t\tconnectURL := fmt.Sprintf(\"http://%s:%d\", tc.connectIP.String(), tc.connectURLPort)\n\t\t\tt.Logf(\"pFlag=%q, connectURL=%q\", pFlag, connectURL)\n\t\t\tcmd := base.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--name\", testContainerName,\n\t\t\t\t\"-p\", pFlag,\n\t\t\t\tnginxImage)\n\t\t\tif tc.runShouldSuccess {\n\t\t\t\tcmd.AssertOK()\n\t\t\t} else {\n\t\t\t\tcmd.AssertFail()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresp, err := nettestutil.HTTPGet(connectURL, 5, false)\n\t\t\tif tc.err != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tc.err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.NilError(t, err)\n\t\t\trespBody, err := io.ReadAll(resp.Body)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Assert(t, strings.Contains(string(respBody), nginxIndexHTMLSnippet))\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_network_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containernetworking/plugins/pkg/ns\"\n\t\"github.com/opencontainers/go-digest\"\n\t\"github.com/vishvananda/netlink\"\n\t\"gotest.tools/v3/assert\"\n\t\"gotest.tools/v3/icmd\"\n\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/containerd/v2/pkg/netns\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\nfunc extractHostPort(portMapping string, port string) (string, error) {\n\t// Regular expression to extract host port from port mapping information\n\tre := regexp.MustCompile(`(?P<containerPort>\\d{1,5})/tcp ->.*?0.0.0.0:(?P<hostPort>\\d{1,5}).*?`)\n\tportMappingLines := strings.Split(portMapping, \"\\n\")\n\tfor _, portMappingLine := range portMappingLines {\n\t\t// Find the matches\n\t\tmatches := re.FindStringSubmatch(portMappingLine)\n\t\t// Check if there is a match\n\t\tif len(matches) >= 3 && matches[1] == port {\n\t\t\t// Extract the host port number\n\t\t\thostPort := matches[2]\n\t\t\treturn hostPort, nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"could not extract host port from port mapping: %s\", portMapping)\n}\n\nfunc valuesOfMapStringString(m map[string]string) map[string]struct{} {\n\tres := make(map[string]struct{})\n\tfor _, v := range m {\n\t\tres[v] = struct{}{}\n\t}\n\treturn res\n}\n\n// TestRunInternetConnectivity tests Internet connectivity with `apk update`\nfunc TestRunInternetConnectivity(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tcustomNet := testutil.Identifier(t)\n\tbase.Cmd(\"network\", \"create\", customNet).AssertOK()\n\tdefer base.Cmd(\"network\", \"rm\", customNet).Run()\n\n\ttype testCase struct {\n\t\targs []string\n\t}\n\tcustomNetID := base.InspectNetwork(customNet).ID\n\ttestCases := []testCase{\n\t\t{\n\t\t\targs: []string{\"--net\", \"bridge\"},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"--net\", customNet},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"--net\", customNetID},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"--net\", customNetID[:12]},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"--net\", \"host\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttc := tc // IMPORTANT\n\t\tname := \"default\"\n\t\tif len(tc.args) > 0 {\n\t\t\tname = strings.Join(tc.args, \"_\")\n\t\t}\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\targs := []string{\"run\", \"--rm\"}\n\t\t\targs = append(args, tc.args...)\n\t\t\targs = append(args, testutil.AlpineImage, \"apk\", \"update\")\n\t\t\tcmd := base.Cmd(args...)\n\t\t\tcmd.AssertOutContains(\"OK\")\n\t\t})\n\t}\n}\n\n// TestRunHostLookup tests hostname lookup\nfunc TestRunHostLookup(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\t// key: container name, val: network name\n\tm := map[string]string{\n\t\t\"c0-in-n0\":     \"n0\",\n\t\t\"c1-in-n0\":     \"n0\",\n\t\t\"c2-in-n1\":     \"n1\",\n\t\t\"c3-in-bridge\": \"bridge\",\n\t}\n\tcustomNets := valuesOfMapStringString(m)\n\tdefer func() {\n\t\tfor name := range m {\n\t\t\tbase.Cmd(\"rm\", \"-f\", name).Run()\n\t\t}\n\t\tfor netName := range customNets {\n\t\t\tif netName == \"bridge\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbase.Cmd(\"network\", \"rm\", netName).Run()\n\t\t}\n\t}()\n\n\t// Create networks\n\tfor netName := range customNets {\n\t\tif netName == \"bridge\" {\n\t\t\tcontinue\n\t\t}\n\t\tbase.Cmd(\"network\", \"create\", netName).AssertOK()\n\t}\n\n\t// Create nginx containers\n\tfor name, netName := range m {\n\t\tcmd := base.Cmd(\"run\",\n\t\t\t\"-d\",\n\t\t\t\"--name\", name,\n\t\t\t\"--hostname\", name+\"-foobar\",\n\t\t\t\"--net\", netName,\n\t\t\ttestutil.NginxAlpineImage,\n\t\t)\n\t\tt.Logf(\"creating host lookup testing container with command: %q\", strings.Join(cmd.Command, \" \"))\n\t\tcmd.AssertOK()\n\t}\n\n\ttestWget := func(srcContainer, targetHostname string, expected bool) {\n\t\tt.Logf(\"resolving %q in container %q (should success: %+v)\", targetHostname, srcContainer, expected)\n\t\tcmd := base.Cmd(\"exec\", srcContainer, \"wget\", \"-qO-\", \"http://\"+targetHostname)\n\t\tif expected {\n\t\t\tcmd.AssertOutContains(testutil.NginxAlpineIndexHTMLSnippet)\n\t\t} else {\n\t\t\tcmd.AssertFail()\n\t\t}\n\t}\n\n\t// Tests begin\n\ttestWget(\"c0-in-n0\", \"c1-in-n0\", true)\n\ttestWget(\"c0-in-n0\", \"c1-in-n0.n0\", true)\n\ttestWget(\"c0-in-n0\", \"c1-in-n0-foobar\", true)\n\ttestWget(\"c0-in-n0\", \"c1-in-n0-foobar.n0\", true)\n\ttestWget(\"c0-in-n0\", \"c2-in-n1\", false)\n\ttestWget(\"c0-in-n0\", \"c2-in-n1.n1\", false)\n\ttestWget(\"c0-in-n0\", \"c3-in-bridge\", false)\n\ttestWget(\"c1-in-n0\", \"c0-in-n0\", true)\n\ttestWget(\"c1-in-n0\", \"c0-in-n0.n0\", true)\n\ttestWget(\"c1-in-n0\", \"c0-in-n0-foobar\", true)\n\ttestWget(\"c1-in-n0\", \"c0-in-n0-foobar.n0\", true)\n}\n\nfunc TestRunPortWithNoHostPort(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"Auto port assign is not supported rootless mode yet\")\n\t}\n\n\ttype testCase struct {\n\t\tcontainerPort    string\n\t\trunShouldSuccess bool\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tcontainerPort:    \"80\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tcontainerPort:    \"80-81\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tcontainerPort:    \"80-81/tcp\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t}\n\ttID := testutil.Identifier(t)\n\tfor i, tc := range testCases {\n\t\ti := i\n\t\ttc := tc\n\t\ttcName := fmt.Sprintf(\"%+v\", tc)\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\ttestContainerName := fmt.Sprintf(\"%s-%d\", tID, i)\n\t\t\tbase := testutil.NewBase(t)\n\t\t\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\t\t\tpFlag := tc.containerPort\n\t\t\tcmd := base.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--name\", testContainerName,\n\t\t\t\t\"-p\", pFlag,\n\t\t\t\ttestutil.NginxAlpineImage)\n\t\t\tvar result *icmd.Result\n\t\t\tstdoutContent := \"\"\n\t\t\tif tc.runShouldSuccess {\n\t\t\t\tcmd.AssertOK()\n\t\t\t} else {\n\t\t\t\tcmd.AssertFail()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tportCmd := base.Cmd(\"port\", testContainerName)\n\t\t\tportCmd.Base.T.Helper()\n\t\t\tresult = portCmd.Run()\n\t\t\tstdoutContent = result.Stdout() + result.Stderr()\n\t\t\tassert.Assert(cmd.Base.T, result.ExitCode == 0, stdoutContent)\n\t\t\tregexExpression := regexp.MustCompile(`80\\/tcp.*?->.*?0.0.0.0:(?P<portNumber>\\d{1,5}).*?`)\n\t\t\tmatch := regexExpression.FindStringSubmatch(stdoutContent)\n\t\t\tparamsMap := make(map[string]string)\n\t\t\tfor i, name := range regexExpression.SubexpNames() {\n\t\t\t\tif i > 0 && i <= len(match) {\n\t\t\t\t\tparamsMap[name] = match[i]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif _, ok := paramsMap[\"portNumber\"]; !ok {\n\t\t\t\tt.Fail()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconnectURL := fmt.Sprintf(\"http://%s:%s\", \"127.0.0.1\", paramsMap[\"portNumber\"])\n\t\t\tresp, err := nettestutil.HTTPGet(connectURL, 5, false)\n\t\t\tassert.NilError(t, err)\n\t\t\trespBody, err := io.ReadAll(resp.Body)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet))\n\t\t})\n\t}\n\n}\n\nfunc TestUniqueHostPortAssignement(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"Auto port assign is not supported rootless mode yet\")\n\t}\n\n\ttype testCase struct {\n\t\tcontainerPort    string\n\t\trunShouldSuccess bool\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tcontainerPort:    \"80\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tcontainerPort:    \"80-81\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t\t{\n\t\t\tcontainerPort:    \"80-81/tcp\",\n\t\t\trunShouldSuccess: true,\n\t\t},\n\t}\n\n\ttID := testutil.Identifier(t)\n\n\tfor i, tc := range testCases {\n\t\ti := i\n\t\ttc := tc\n\t\ttcName := fmt.Sprintf(\"%+v\", tc)\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\ttestContainerName1 := fmt.Sprintf(\"%s-%d-1\", tID, i)\n\t\t\ttestContainerName2 := fmt.Sprintf(\"%s-%d-2\", tID, i)\n\t\t\tbase := testutil.NewBase(t)\n\t\t\tdefer base.Cmd(\"rm\", \"-f\", testContainerName1, testContainerName2).Run()\n\n\t\t\tpFlag := tc.containerPort\n\t\t\tcmd1 := base.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--name\", testContainerName1, \"-p\",\n\t\t\t\tpFlag,\n\t\t\t\ttestutil.NginxAlpineImage)\n\n\t\t\tcmd2 := base.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--name\", testContainerName2, \"-p\",\n\t\t\t\tpFlag,\n\t\t\t\ttestutil.NginxAlpineImage)\n\t\t\tvar result *icmd.Result\n\t\t\tstdoutContent := \"\"\n\t\t\tif tc.runShouldSuccess {\n\t\t\t\tcmd1.AssertOK()\n\t\t\t\tcmd2.AssertOK()\n\t\t\t} else {\n\t\t\t\tcmd1.AssertFail()\n\t\t\t\tcmd2.AssertFail()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tportCmd1 := base.Cmd(\"port\", testContainerName1)\n\t\t\tportCmd2 := base.Cmd(\"port\", testContainerName2)\n\t\t\tportCmd1.Base.T.Helper()\n\t\t\tportCmd2.Base.T.Helper()\n\t\t\tresult = portCmd1.Run()\n\t\t\tstdoutContent = result.Stdout() + result.Stderr()\n\t\t\tassert.Assert(t, result.ExitCode == 0, stdoutContent)\n\t\t\tport1, err := extractHostPort(stdoutContent, \"80\")\n\t\t\tassert.NilError(t, err)\n\t\t\tresult = portCmd2.Run()\n\t\t\tstdoutContent = result.Stdout() + result.Stderr()\n\t\t\tassert.Assert(t, result.ExitCode == 0, stdoutContent)\n\t\t\tport2, err := extractHostPort(stdoutContent, \"80\")\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Assert(t, port1 != port2, \"Host ports are not unique\")\n\n\t\t\t// Make HTTP GET request to container 1\n\t\t\tconnectURL1 := fmt.Sprintf(\"http://%s:%s\", \"127.0.0.1\", port1)\n\t\t\tresp1, err := nettestutil.HTTPGet(connectURL1, 5, false)\n\t\t\tassert.NilError(t, err)\n\t\t\trespBody1, err := io.ReadAll(resp1.Body)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Assert(t, strings.Contains(string(respBody1), testutil.NginxAlpineIndexHTMLSnippet))\n\n\t\t\t// Make HTTP GET request to container 2\n\t\t\tconnectURL2 := fmt.Sprintf(\"http://%s:%s\", \"127.0.0.1\", port2)\n\t\t\tresp2, err := nettestutil.HTTPGet(connectURL2, 5, false)\n\t\t\tassert.NilError(t, err)\n\t\t\trespBody2, err := io.ReadAll(resp2.Body)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Assert(t, strings.Contains(string(respBody2), testutil.NginxAlpineIndexHTMLSnippet))\n\t\t})\n\t}\n}\n\nfunc TestHostPortAlreadyInUse(t *testing.T) {\n\ttestCases := []struct {\n\t\thostPort      string\n\t\tcontainerPort string\n\t}{\n\t\t{\n\t\t\thostPort:      \"5000\",\n\t\t\tcontainerPort: \"80/tcp\",\n\t\t},\n\t\t{\n\t\t\thostPort:      \"5000\",\n\t\t\tcontainerPort: \"80/tcp\",\n\t\t},\n\t\t{\n\t\t\thostPort:      \"5000\",\n\t\t\tcontainerPort: \"80/udp\",\n\t\t},\n\t\t{\n\t\t\thostPort:      \"5000\",\n\t\t\tcontainerPort: \"80/sctp\",\n\t\t},\n\t}\n\n\ttID := testutil.Identifier(t)\n\n\tfor i, tc := range testCases {\n\t\ttc := tc\n\t\ttcName := fmt.Sprintf(\"%+v\", tc)\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\tif strings.Contains(tc.containerPort, \"sctp\") && rootlessutil.IsRootless() {\n\t\t\t\tt.Skip(\"sctp is not supported in rootless mode\")\n\t\t\t}\n\t\t\ttestContainerName1 := fmt.Sprintf(\"%s-%d-1\", tID, i)\n\t\t\ttestContainerName2 := fmt.Sprintf(\"%s-%d-2\", tID, i)\n\t\t\tbase := testutil.NewBase(t)\n\t\t\tt.Cleanup(func() {\n\t\t\t\tbase.Cmd(\"rm\", \"-f\", testContainerName1, testContainerName2).AssertOK()\n\t\t\t})\n\t\t\tpFlag := fmt.Sprintf(\"%s:%s\", tc.hostPort, tc.containerPort)\n\t\t\tcmd1 := base.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--name\", testContainerName1, \"-p\",\n\t\t\t\tpFlag,\n\t\t\t\ttestutil.NginxAlpineImage)\n\n\t\t\tcmd2 := base.Cmd(\"run\", \"-d\",\n\t\t\t\t\"--name\", testContainerName2, \"-p\",\n\t\t\t\tpFlag,\n\t\t\t\ttestutil.NginxAlpineImage)\n\n\t\t\tcmd1.AssertOK()\n\t\t\tcmd2.AssertFail()\n\t\t})\n\t}\n}\n\nfunc TestRunPort(t *testing.T) {\n\tbaseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, true)\n}\n\nfunc TestRunWithManyPortsThenCleanUp(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\t// docker does not set label restriction to 4096 bytes\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Run a container with many ports, and then clean up.\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--data-root\", data.Temp().Path(), \"--rm\", \"-p\", \"22200-22299:22200-22299\", testutil.CommonImage)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tgetAddrHash := func(addr string) string {\n\t\t\t\t\t\t\tconst addrHashLen = 8\n\n\t\t\t\t\t\t\td := digest.SHA256.FromString(addr)\n\t\t\t\t\t\t\th := d.Encoded()[0:addrHashLen]\n\n\t\t\t\t\t\t\treturn h\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdataRoot := data.Temp().Path()\n\t\t\t\t\t\th := getAddrHash(defaults.DefaultAddress)\n\t\t\t\t\t\tdataStore := filepath.Join(dataRoot, h)\n\t\t\t\t\t\tnamespace := string(helpers.Read(nerdtest.Namespace))\n\t\t\t\t\t\tetchostsPath := filepath.Join(dataStore, \"etchosts\", namespace)\n\n\t\t\t\t\t\tetchostsDirs, err := os.ReadDir(etchostsPath)\n\n\t\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\t\tassert.Equal(t, len(etchostsDirs), 0)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunContainerWithStaticIP(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"Static IP assignment is not supported rootless mode yet.\")\n\t}\n\tnetworkName := \"test-network\"\n\tnetworkSubnet := \"172.0.0.0/16\"\n\tbase := testutil.NewBase(t)\n\tcmd := base.Cmd(\"network\", \"create\", networkName, \"--subnet\", networkSubnet)\n\tcmd.AssertOK()\n\tdefer base.Cmd(\"network\", \"rm\", networkName).Run()\n\ttestCases := []struct {\n\t\tip                string\n\t\tshouldSuccess     bool\n\t\tuseNetwork        bool\n\t\tcheckTheIPAddress bool\n\t}{\n\t\t{\n\t\t\tip:                \"172.0.0.2\",\n\t\t\tshouldSuccess:     true,\n\t\t\tuseNetwork:        true,\n\t\t\tcheckTheIPAddress: true,\n\t\t},\n\t\t{\n\t\t\tip:                \"192.0.0.2\",\n\t\t\tshouldSuccess:     false,\n\t\t\tuseNetwork:        true,\n\t\t\tcheckTheIPAddress: false,\n\t\t},\n\t\t// XXX see https://github.com/containerd/nerdctl/issues/3101\n\t\t// docker 24 silently ignored the ip - now, docker 26 is erroring out - furthermore, this ip only makes sense\n\t\t// in the context of nerdctl bridge network, so, this test needs rewritting either way\n\t\t/*\n\t\t\t{\n\t\t\t\tip:                \"10.4.0.2\",\n\t\t\t\tshouldSuccess:     true,\n\t\t\t\tuseNetwork:        false,\n\t\t\t\tcheckTheIPAddress: false,\n\t\t\t},\n\t\t*/\n\t}\n\ttID := testutil.Identifier(t)\n\tfor i, tc := range testCases {\n\t\ti := i\n\t\ttc := tc\n\t\ttcName := fmt.Sprintf(\"%+v\", tc)\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\ttestContainerName := fmt.Sprintf(\"%s-%d\", tID, i)\n\t\t\tbase := testutil.NewBase(t)\n\t\t\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\t\t\targs := []string{\n\t\t\t\t\"run\", \"-d\", \"--name\", testContainerName,\n\t\t\t}\n\t\t\tif tc.useNetwork {\n\t\t\t\targs = append(args, []string{\"--network\", networkName}...)\n\t\t\t}\n\t\t\targs = append(args, []string{\"--ip\", tc.ip, testutil.NginxAlpineImage}...)\n\t\t\tcmd := base.Cmd(args...)\n\t\t\tif !tc.shouldSuccess {\n\t\t\t\tcmd.AssertFail()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcmd.AssertOK()\n\n\t\t\tif tc.checkTheIPAddress {\n\t\t\t\tinspectCmd := base.Cmd(\"inspect\", testContainerName, \"--format\", \"\\\"{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}\\\"\")\n\t\t\t\tresult := inspectCmd.Run()\n\t\t\t\tstdoutContent := result.Stdout() + result.Stderr()\n\t\t\t\tassert.Assert(inspectCmd.Base.T, result.ExitCode == 0, stdoutContent)\n\t\t\t\tif !strings.Contains(stdoutContent, tc.ip) {\n\t\t\t\t\tt.Fail()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRunDNS(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\tbase.Cmd(\"run\", \"--rm\", \"--dns\", \"8.8.8.8\", testutil.CommonImage,\n\t\t\"cat\", \"/etc/resolv.conf\").AssertOutContains(\"nameserver 8.8.8.8\\n\")\n\tbase.Cmd(\"run\", \"--rm\", \"--dns-search\", \"test\", testutil.CommonImage,\n\t\t\"cat\", \"/etc/resolv.conf\").AssertOutContains(\"search test\\n\")\n\tbase.Cmd(\"run\", \"--rm\", \"--dns-search\", \"test\", \"--dns-search\", \"test1\", testutil.CommonImage,\n\t\t\"cat\", \"/etc/resolv.conf\").AssertOutContains(\"search test test1\\n\")\n\tbase.Cmd(\"run\", \"--rm\", \"--dns-opt\", \"no-tld-query\", \"--dns-option\", \"attempts:10\", testutil.CommonImage,\n\t\t\"cat\", \"/etc/resolv.conf\").AssertOutContains(\"options no-tld-query attempts:10\\n\")\n\tcmd := base.Cmd(\"run\", \"--rm\", \"--dns\", \"8.8.8.8\", \"--dns-search\", \"test\", \"--dns-option\", \"attempts:10\", testutil.CommonImage,\n\t\t\"cat\", \"/etc/resolv.conf\")\n\tcmd.AssertOutContains(\"nameserver 8.8.8.8\\n\")\n\tcmd.AssertOutContains(\"search test\\n\")\n\tcmd.AssertOutContains(\"options attempts:10\\n\")\n}\n\nfunc TestRunNetworkHostHostname(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\thostname, err := os.Hostname()\n\tassert.NilError(t, err)\n\thostname = hostname + \"\\n\"\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", testutil.CommonImage, \"hostname\").AssertOutExactly(hostname)\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", testutil.CommonImage, \"sh\", \"-euxc\", \"echo $HOSTNAME\").AssertOutExactly(hostname)\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", \"--hostname\", \"override\", testutil.CommonImage, \"hostname\").AssertOutExactly(\"override\\n\")\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", \"--hostname\", \"override\", testutil.CommonImage, \"sh\", \"-euxc\", \"echo $HOSTNAME\").AssertOutExactly(\"override\\n\")\n}\n\nfunc TestRunNetworkHost2613(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\tbase.Cmd(\"run\", \"--rm\", \"--add-host\", \"foo:1.2.3.4\", testutil.CommonImage, \"getent\", \"hosts\", \"foo\").AssertOutExactly(\"1.2.3.4           foo  foo\\n\")\n}\n\nfunc TestSharedNetworkSetup(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Labels().Set(\"container1\", data.Identifier(\"container1\"))\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container1\"),\n\t\t\t\ttestutil.CommonImage, \"sleep\", \"inf\")\n\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"container1\"))\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container1\"))\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Test network is shared\",\n\t\t\t\tNoParallel:  true, // The validation involves starting of the main container: container1\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container2\"))\n\t\t\t\t},\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\n\t\t\t\t\t\t\"run\", \"-d\", \"--name\", data.Identifier(\"container2\"),\n\t\t\t\t\t\t\"--network=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.NginxAlpineImage)\n\t\t\t\t\tdata.Labels().Set(\"container2\", data.Identifier(\"container2\"))\n\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"container2\"))\n\t\t\t\t},\n\t\t\t\tSubTests: []*test.Case{\n\t\t\t\t\t{\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tDescription: \"Test network is shared\",\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container2\"), \"wget\", \"-qO-\", \"http://127.0.0.1:80\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(testutil.NginxAlpineIndexHTMLSnippet)),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\t\tDescription: \"Test network is shared after restart\",\n\t\t\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t\t\thelpers.Ensure(\"restart\", data.Labels().Get(\"container1\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"stop\", \"--time=1\", data.Labels().Get(\"container2\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"start\", data.Labels().Get(\"container2\"))\n\t\t\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Labels().Get(\"container2\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\t\treturn helpers.Command(\"exec\", data.Labels().Get(\"container2\"), \"wget\", \"-qO-\", \"http://127.0.0.1:80\")\n\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(testutil.NginxAlpineIndexHTMLSnippet)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test uts is supported in shared network\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--uts\", \"host\",\n\t\t\t\t\t\t\"--network=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.CommonImage)\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test dns is not supported\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--dns\", \"0.1.2.3\",\n\t\t\t\t\t\t\"--network=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.CommonImage)\n\t\t\t\t},\n\t\t\t\t// 1 for nerdctl, 125 for docker\n\t\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test dns options is not  supported\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--dns-option\", \"attempts:5\",\n\t\t\t\t\t\t\"--network=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t\t\t},\n\t\t\t\t// The Option doesn't throw an error but is never inserted to the resolv.conf\n\t\t\t\tExpected: test.Expects(0, nil, expect.DoesNotContain(\"attempts:5\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test publish is not supported\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--publish\", \"80:8080\",\n\t\t\t\t\t\t\"--network=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.AlpineImage)\n\t\t\t\t},\n\t\t\t\t// 1 for nerdctl, 125 for docker\n\t\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test hostname is not supported\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--hostname\", \"test\",\n\t\t\t\t\t\t\"--network=container:\"+data.Labels().Get(\"container1\"),\n\t\t\t\t\t\ttestutil.AlpineImage)\n\t\t\t\t},\n\t\t\t\t// 1 for nerdctl, 125 for docker\n\t\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestSharedNetworkWithNone(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container1\"), \"--network\", \"none\",\n\t\t\t\ttestutil.CommonImage, \"sleep\", \"inf\")\n\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"container1\"))\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container1\"))\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\"--network=container:\"+data.Identifier(\"container1\"), testutil.CommonImage)\n\t\t},\n\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestRunContainerInExistingNetNS(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"Can't create new netns in rootless mode\")\n\t}\n\ttestutil.DockerIncompatible(t)\n\tbase := testutil.NewBase(t)\n\n\tnetNS, err := netns.NewNetNS(t.TempDir() + \"/netns\")\n\tassert.NilError(t, err)\n\terr = netNS.Do(func(netns ns.NetNS) error {\n\t\tloopback, err := netlink.LinkByName(\"lo\")\n\t\tassert.NilError(t, err)\n\t\terr = netlink.LinkSetUp(loopback)\n\t\tassert.NilError(t, err)\n\t\treturn nil\n\t})\n\tassert.NilError(t, err)\n\tdefer netNS.Remove()\n\n\tcontainerName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\", \"-d\", \"--name\", containerName,\n\t\t\"--network=ns:\"+netNS.GetPath(), testutil.NginxAlpineImage).AssertOK()\n\tbase.EnsureContainerStarted(containerName)\n\ttime.Sleep(3 * time.Second)\n\n\terr = netNS.Do(func(netns ns.NetNS) error {\n\t\tstdout, err := exec.Command(\"curl\", \"-s\", \"http://127.0.0.1:80\").Output()\n\t\tassert.NilError(t, err)\n\t\tassert.Assert(t, strings.Contains(string(stdout), testutil.NginxAlpineIndexHTMLSnippet))\n\t\treturn nil\n\t})\n\tassert.NilError(t, err)\n}\n\nfunc TestRunContainerWithMACAddress(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\tnetworkBridge := \"testNetworkBridge\" + tID\n\tnetworkMACvlan := \"testNetworkMACvlan\" + tID\n\tnetworkIPvlan := \"testNetworkIPvlan\" + tID\n\ttearDown := func() {\n\t\tbase.Cmd(\"network\", \"rm\", networkBridge).Run()\n\t\tbase.Cmd(\"network\", \"rm\", networkMACvlan).Run()\n\t\tbase.Cmd(\"network\", \"rm\", networkIPvlan).Run()\n\t}\n\n\ttearDown()\n\tt.Cleanup(tearDown)\n\n\tbase.Cmd(\"network\", \"create\", networkBridge, \"--driver\", \"bridge\").AssertOK()\n\tbase.Cmd(\"network\", \"create\", networkMACvlan, \"--driver\", \"macvlan\").AssertOK()\n\tbase.Cmd(\"network\", \"create\", networkIPvlan, \"--driver\", \"ipvlan\").AssertOK()\n\n\tdefaultMac := base.Cmd(\"run\", \"--rm\", \"-i\", \"--network\", \"host\", testutil.CommonImage).\n\t\tCmdOption(testutil.WithStdin(strings.NewReader(\"ip addr show eth0 | grep ether | awk '{printf $2}'\"))).\n\t\tRun().Stdout()\n\n\tpassedMac := \"we expect the generated mac on the output\"\n\n\ttests := []struct {\n\t\tNetwork string\n\t\tWantErr bool\n\t\tExpect  string\n\t}{\n\t\t{\"host\", false, defaultMac},                     // anything but the actual address being passed\n\t\t{\"none\", false, \"\"},                             // nothing\n\t\t{\"container:whatever\" + tID, true, \"container\"}, // \"No such container\" vs. \"could not find container\"\n\t\t{\"bridge\", false, passedMac},\n\t\t{networkBridge, false, passedMac},\n\t\t{networkMACvlan, false, passedMac},\n\t\t{networkIPvlan, true, \"not support\"},\n\t}\n\n\tfor i, test := range tests {\n\t\tcontainerName := fmt.Sprintf(\"%s_%d\", tID, i)\n\t\ttestName := fmt.Sprintf(\"%s_container:%s_network:%s_expect:%s\", tID, containerName, test.Network, test.Expect)\n\t\texpect := test.Expect\n\t\tnetwork := test.Network\n\t\twantErr := test.WantErr\n\t\tt.Run(testName, func(tt *testing.T) {\n\t\t\ttt.Parallel()\n\n\t\t\tmacAddress, err := nettestutil.GenerateMACAddress()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"failed to generate MAC address: %s\", err)\n\t\t\t}\n\t\t\tif expect == passedMac {\n\t\t\t\texpect = macAddress\n\t\t\t}\n\n\t\t\tres := base.Cmd(\"run\", \"--rm\", \"-i\", \"--network\", network, \"--mac-address\", macAddress, testutil.CommonImage).\n\t\t\t\tCmdOption(testutil.WithStdin(strings.NewReader(\"ip addr show eth0 | grep ether | awk '{printf $2}'\"))).Run()\n\n\t\t\tif wantErr {\n\t\t\t\tassert.Assert(t, res.ExitCode != 0, \"Command should have failed\", res)\n\t\t\t\tassert.Assert(t, strings.Contains(res.Combined(), expect), fmt.Sprintf(\"expected output to contain %q: %q\", expect, res.Combined()))\n\t\t\t} else {\n\t\t\t\tassert.Assert(t, res.ExitCode == 0, \"Command should have succeeded\", res)\n\t\t\t\tassert.Assert(t, strings.Contains(res.Stdout(), expect), fmt.Sprintf(\"expected output to contain %q: %q\", expect, res.Stdout()))\n\t\t\t}\n\t\t})\n\n\t}\n}\n\nfunc TestHostsFileMounts(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tif detachedNetNS, _ := rootlessutil.DetachedNetNS(); detachedNetNS != \"\" {\n\t\t\tt.Skip(\"/etc/hosts is not writable\")\n\t\t}\n\t}\n\tbase := testutil.NewBase(t)\n\n\tbase.Cmd(\"run\", \"--rm\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/hosts\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/hosts\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/etc/hosts:/etc/hosts:ro\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/hosts\").AssertFail()\n\t// add a line into /etc/hosts and remove it.\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/etc/hosts:/etc/hosts\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/hosts\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/etc/hosts:/etc/hosts\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"head -n -1 /etc/hosts > temp && cat temp > /etc/hosts\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"none\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/hosts\").AssertOK()\n\n\tbase.Cmd(\"run\", \"--rm\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/resolv.conf\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/resolv.conf\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/etc/resolv.conf:/etc/resolv.conf:ro\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/resolv.conf\").AssertFail()\n\t// add a line into /etc/resolv.conf and remove it.\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/etc/resolv.conf:/etc/resolv.conf\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/resolv.conf\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"-v\", \"/etc/resolv.conf:/etc/resolv.conf\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"head -n -1 /etc/resolv.conf > temp && cat temp > /etc/resolv.conf\").AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", \"--network\", \"host\", testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo >> /etc/resolv.conf\").AssertOK()\n}\n\nfunc TestRunContainerWithStaticIP6(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"Static IP6 assignment is not supported rootless mode yet.\")\n\t}\n\tnetworkName := \"test-network\"\n\tnetworkSubnet := \"2001:db8:5::/64\"\n\t_, subnet, err := net.ParseCIDR(networkSubnet)\n\tassert.Assert(t, err == nil)\n\tbase := testutil.NewBaseWithIPv6Compatible(t)\n\tbase.Cmd(\"network\", \"create\", networkName, \"--subnet\", networkSubnet, \"--ipv6\").AssertOK()\n\tt.Cleanup(func() {\n\t\tbase.Cmd(\"network\", \"rm\", networkName).Run()\n\t})\n\ttestCases := []struct {\n\t\tip                string\n\t\tshouldSuccess     bool\n\t\tcheckTheIPAddress bool\n\t}{\n\t\t{\n\t\t\tip:                \"\",\n\t\t\tshouldSuccess:     true,\n\t\t\tcheckTheIPAddress: false,\n\t\t},\n\t\t{\n\t\t\tip:                \"2001:db8:5::6\",\n\t\t\tshouldSuccess:     true,\n\t\t\tcheckTheIPAddress: true,\n\t\t},\n\t\t{\n\t\t\tip:                \"2001:db8:4::6\",\n\t\t\tshouldSuccess:     false,\n\t\t\tcheckTheIPAddress: false,\n\t\t},\n\t}\n\ttID := testutil.Identifier(t)\n\tfor i, tc := range testCases {\n\t\ti := i\n\t\ttc := tc\n\t\ttcName := fmt.Sprintf(\"%+v\", tc)\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\ttestContainerName := fmt.Sprintf(\"%s-%d\", tID, i)\n\t\t\tbase := testutil.NewBaseWithIPv6Compatible(t)\n\t\t\targs := []string{\n\t\t\t\t\"run\", \"--rm\", \"--name\", testContainerName, \"--network\", networkName,\n\t\t\t}\n\t\t\tif tc.ip != \"\" {\n\t\t\t\targs = append(args, \"--ip6\", tc.ip)\n\t\t\t}\n\t\t\targs = append(args, []string{testutil.NginxAlpineImage, \"ip\", \"addr\", \"show\", \"dev\", \"eth0\"}...)\n\t\t\tcmd := base.Cmd(args...)\n\t\t\tif !tc.shouldSuccess {\n\t\t\t\tcmd.AssertFail()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcmd.AssertOutWithFunc(func(stdout string) error {\n\t\t\t\tip := nerdtest.FindIPv6(stdout)\n\t\t\t\tif !subnet.Contains(ip) {\n\t\t\t\t\treturn fmt.Errorf(\"expected subnet %s include ip %s\", subnet, ip)\n\t\t\t\t}\n\t\t\t\tif tc.checkTheIPAddress {\n\t\t\t\t\tif ip.String() != tc.ip {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected ip %s, got %s\", tc.ip, ip)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestNoneNetworkHostName(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\toutput := helpers.Capture(\"run\", \"-d\", \"--name\", data.Identifier(), \"--network\", \"none\", testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\tassert.Assert(helpers.T(), len(output) > 12, output)\n\t\t\tdata.Labels().Set(\"hostname\", output[:12])\n\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"exec\", data.Identifier(), \"cat\", \"/etc/hostname\")\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: expect.Equals(data.Labels().Get(\"hostname\") + \"\\n\"),\n\t\t\t}\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestHostNetworkHostName(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Custom(\"cat\", \"/etc/hostname\").Run(&test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tdata.Labels().Set(\"hostHostname\", stdout)\n\t\t\t\t},\n\t\t\t})\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\"--network\", \"host\",\n\t\t\t\ttestutil.AlpineImage, \"cat\", \"/etc/hostname\")\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: expect.Equals(data.Labels().Get(\"hostHostname\")),\n\t\t\t}\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestHostNetworkDnsPreserved(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t// In some rootless CI job, slirp provides 10.0.2.3 as DNS server.\n\t\t\t// We cannot simply parse host /etc/resolv.conf here.\n\t\t\thelpers.Command(\"run\", \"--rm\",\n\t\t\t\t\"-v\", \"/etc/resolv.conf:/mnt/resolv.conf:ro\",\n\t\t\t\ttestutil.AlpineImage,\n\t\t\t\t\"grep\", \"-E\", \"^nameserver\\\\s+\", \"/mnt/resolv.conf\").Run(&test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tdata.Labels().Set(\"nameservers\", stdout)\n\t\t\t\t},\n\t\t\t})\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\"--network\", \"host\",\n\t\t\t\ttestutil.AlpineImage,\n\t\t\t\t\"grep\", \"-E\", \"^nameserver\\\\s+\", \"/etc/resolv.conf\")\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t// container with --network=host should have same nameserver as host\n\t\t\tnameservers := data.Labels().Get(\"nameservers\")\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: expect.Equals(nameservers),\n\t\t\t}\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestDefaultNetworkDnsNoLocalhost(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\ttestutil.AlpineImage, \"grep\", \"-E\", \"^nameserver\\\\s+(127\\\\.|::1)\", \"/etc/resolv.conf\")\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tExitCode: 1, // no match\n\t\t\t}\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestNoneNetworkDnsConfigs(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\"--network\", \"none\",\n\t\t\t\t\"--dns\", \"0.1.2.3\", \"--dns-search\", \"example.com\", \"--dns-option\", \"timeout:3\", \"--dns-option\", \"attempts:5\",\n\t\t\t\ttestutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t},\n\t\tExpected: test.Expects(0, nil, expect.Contains(\n\t\t\t\"0.1.2.3\",\n\t\t\t\"example.com\",\n\t\t\t\"attempts:5\",\n\t\t\t\"timeout:3\",\n\t\t)),\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestHostNetworkDnsConfigs(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\"--network\", \"host\",\n\t\t\t\t\"--dns\", \"0.1.2.3\", \"--dns-search\", \"example.com\", \"--dns-option\", \"timeout:3\", \"--dns-option\", \"attempts:5\",\n\t\t\t\ttestutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t},\n\t\tExpected: test.Expects(0, nil, expect.Contains(\n\t\t\t\"0.1.2.3\",\n\t\t\t\"example.com\",\n\t\t\t\"attempts:5\",\n\t\t\t\"timeout:3\",\n\t\t)),\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestDNSWithGlobalConfig(t *testing.T) {\n\tvar configContent test.ConfigValue = `debug = false\ndebug_full = false\ndns = [\"10.10.10.10\", \"20.20.20.20\"]\ndns_opts = [\"ndots:2\", \"timeout:5\"]\ndns_search = [\"example.com\", \"test.local\"]`\n\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tConfig: test.WithConfig(nerdtest.NerdctlToml, configContent),\n\t\t// NERDCTL_TOML not supported in Docker\n\t\tRequire: require.Not(nerdtest.Docker),\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Global DNS settings are used when command line options are not provided\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tnerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))\n\t\t\t\t\thelpers.T().Log(\"NERDCTL_TOML file content:\\n%s\", nerdctlTomlContent)\n\t\t\t\t\tcmd := helpers.Command(\"run\", \"--rm\", testutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"nameserver 10.10.10.10\"),\n\t\t\t\t\texpect.Contains(\"nameserver 20.20.20.20\"),\n\t\t\t\t\texpect.Contains(\"search example.com test.local\"),\n\t\t\t\t\texpect.Contains(\"options ndots:2 timeout:5\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Command line DNS options override global config\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tnerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))\n\t\t\t\t\thelpers.T().Log(\"NERDCTL_TOML file content:\\n%s\", nerdctlTomlContent)\n\t\t\t\t\tcmd := helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\t\t\"--dns\", \"9.9.9.9\",\n\t\t\t\t\t\t\"--dns-search\", \"override.com\",\n\t\t\t\t\t\t\"--dns-opt\", \"ndots:3\",\n\t\t\t\t\t\ttestutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"nameserver 9.9.9.9\"),\n\t\t\t\t\texpect.Contains(\"search override.com\"),\n\t\t\t\t\texpect.Contains(\"options ndots:3\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Global DNS settings should also apply when using host network\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tnerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))\n\t\t\t\t\thelpers.T().Log(\"NERDCTL_TOML file content:\\n%s\", nerdctlTomlContent)\n\t\t\t\t\tcmd := helpers.Command(\"run\", \"--rm\", \"--network\", \"host\",\n\t\t\t\t\t\ttestutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"nameserver 10.10.10.10\"),\n\t\t\t\t\texpect.Contains(\"nameserver 20.20.20.20\"),\n\t\t\t\t\texpect.Contains(\"search example.com test.local\"),\n\t\t\t\t\texpect.Contains(\"options ndots:2 timeout:5\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Global DNS settings should also apply when using none network\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\tnerdctlTomlContent := string(helpers.Read(nerdtest.NerdctlToml))\n\t\t\t\t\thelpers.T().Log(\"NERDCTL_TOML file content:\\n%s\", nerdctlTomlContent)\n\t\t\t\t\tcmd := helpers.Command(\"run\", \"--rm\", \"--network\", \"none\",\n\t\t\t\t\t\ttestutil.CommonImage, \"cat\", \"/etc/resolv.conf\")\n\t\t\t\t\treturn cmd\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"nameserver 10.10.10.10\"),\n\t\t\t\t\texpect.Contains(\"nameserver 20.20.20.20\"),\n\t\t\t\t\texpect.Contains(\"search example.com test.local\"),\n\t\t\t\t\texpect.Contains(\"options ndots:2 timeout:5\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\n// TestReservePorts tests that a published port appears\n// as a listening port on the host.\n// See https://github.com/containerd/nerdctl/pull/4526\nfunc TestReservePorts(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Not(require.Windows),\n\t\t\trequire.Not(nerdtest.RootlessWithoutDetachNetNS), // RootlessKit v1\n\t\t),\n\t\tNoParallel: true,\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"TCP\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"nginx\"),\n\t\t\t\t\t\t\"-p\", \"60080:80\", testutil.NginxAlpineImage)\n\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"nginx\"))\n\t\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"nginx\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\t\t\"--network=host\", testutil.CommonImage, \"netstat\", \"-lnt\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\":60080\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"UDP\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"coredns\"),\n\t\t\t\t\t\t\"-p\", \"60053:53/udp\", testutil.CoreDNSImage)\n\t\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"coredns\"))\n\t\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"coredns\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\t\t\t\"--network=host\", testutil.CommonImage, \"netstat\", \"-lnu\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\":60053\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_network_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/Microsoft/hcsshim\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\n// TestRunInternetConnectivity tests Internet connectivity by pinging github.com.\nfunc TestRunInternetConnectivity(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\ttype testCase struct {\n\t\targs []string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\targs: []string{\"--net\", \"nat\"},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttc := tc // IMPORTANT\n\t\tname := \"default\"\n\t\tif len(tc.args) > 0 {\n\t\t\tname = strings.Join(tc.args, \"_\")\n\t\t}\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\targs := []string{\"run\", \"--rm\"}\n\t\t\targs = append(args, tc.args...)\n\t\t\t// TODO(aznashwan): smarter way to ensure internet connectivity is working.\n\t\t\t// ping doesn't seem to work on GitHub Actions (\"Request timed out.\")\n\t\t\targs = append(args, testutil.CommonImage, \"curl.exe -sSL https://github.com\")\n\t\t\tcmd := base.Cmd(args...)\n\t\t\tcmd.AssertOutContains(\"<!DOCTYPE html>\")\n\t\t})\n\t}\n}\n\nfunc TestRunPort(t *testing.T) {\n\t// NOTE: currently no isolation between the loopback and host namespaces on Windows.\n\tbaseTestRunPort(t, testutil.NginxAlpineImage, testutil.NginxAlpineIndexHTMLSnippet, false)\n}\n\n// Checks whether an HNS endpoint with a name matching exists.\nfunc listHnsEndpointsRegex(hnsEndpointNameRegex string) ([]hcsshim.HNSEndpoint, error) {\n\tr, err := regexp.Compile(hnsEndpointNameRegex)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thnsEndpoints, err := hcsshim.HNSListEndpointRequest()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list HNS endpoints for request: %w\", err)\n\t}\n\n\tres := []hcsshim.HNSEndpoint{}\n\tfor _, endp := range hnsEndpoints {\n\t\tif r.Match([]byte(endp.Name)) {\n\t\t\tres = append(res, endp)\n\t\t}\n\t}\n\treturn res, nil\n}\n\n// Asserts whether the container with the provided has any HNS endpoints with the expected\n// naming format (`${container_id}_${network_name}`) for all of the provided network names.\n// The container ID can be a regex.\nfunc assertHnsEndpointsExistence(t *testing.T, shouldExist bool, containerIDRegex string, networkNames ...string) {\n\tfor _, netName := range networkNames {\n\t\tendpointName := fmt.Sprintf(\"%s_%s\", containerIDRegex, netName)\n\n\t\ttestName := fmt.Sprintf(\"hns_endpoint_%s_shouldExist_%t\", endpointName, shouldExist)\n\t\tt.Run(testName, func(t *testing.T) {\n\t\t\tmatchingEndpoints, err := listHnsEndpointsRegex(endpointName)\n\t\t\tassert.NilError(t, err)\n\t\t\tif shouldExist {\n\t\t\t\tassert.Equal(t, len(matchingEndpoints), 1)\n\t\t\t\tassert.Equal(t, matchingEndpoints[0].Name, endpointName)\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, len(matchingEndpoints), 0)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Tests whether HNS endpoints are properly created and managed throughout the lifecycle of a container.\nfunc TestHnsEndpointsExistDuringContainerLifecycle(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\ttestNet, err := getTestingNetwork()\n\tassert.NilError(t, err)\n\n\ttID := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", tID).Run()\n\tcmd := base.Cmd(\n\t\t\"create\",\n\t\t\"--name\", tID,\n\t\t\"--net\", testNet.Name,\n\t\ttestutil.CommonImage,\n\t\t\"bash\", \"-c\",\n\t\t// NOTE: the BusyBox image used in Windows testing's `sleep` binary\n\t\t// does not support the `infinity` argument.\n\t\t\"tail\", \"-f\",\n\t)\n\tt.Logf(\"Creating HNS lifecycle test container with command: %q\", strings.Join(cmd.Command, \" \"))\n\tcontainerID := strings.TrimSpace(cmd.Run().Stdout())\n\tt.Logf(\"HNS endpoint lifecycle test container ID: %q\", containerID)\n\n\t// HNS endpoints should be allocated on container creation.\n\tassertHnsEndpointsExistence(t, true, containerID, testNet.Name)\n\n\t// Starting and stopping the container should NOT affect/change the endpoints.\n\tbase.Cmd(\"start\", containerID).AssertOK()\n\tassertHnsEndpointsExistence(t, true, containerID, testNet.Name)\n\n\tbase.Cmd(\"stop\", containerID).AssertOK()\n\tassertHnsEndpointsExistence(t, true, containerID, testNet.Name)\n\n\t// Removing the container should remove the HNS endpoints.\n\tbase.Cmd(\"rm\", containerID).AssertOK()\n\tassertHnsEndpointsExistence(t, false, containerID, testNet.Name)\n}\n\n// Returns a network to be used for testing.\n// Note: currently hardcoded to return the default network, as `network create`\n// does not work on Windows.\nfunc getTestingNetwork() (*netutil.NetworkConfig, error) {\n\t// NOTE: cannot currently `nerdctl network create` on Windows so we use a pre-existing network:\n\tcniEnv, err := netutil.NewCNIEnv(defaults.CNIPath(), defaults.CNINetConfPath())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cniEnv.GetDefaultNetworkConfig()\n}\n\n// Tests whether HNS endpoints are properly removed when running `run --rm`.\nfunc TestHnsEndpointsRemovedAfterAttachedRun(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\ttestNet, err := getTestingNetwork()\n\tassert.NilError(t, err)\n\n\t// NOTE: because we cannot set/obtain the ID of the container to check for the exact HNS\n\t// endpoint name, we record the number of HNS endpoints on the testing network and\n\t// ensure it remains constant until after the test.\n\texistingEndpoints, err := listHnsEndpointsRegex(fmt.Sprintf(\".*_%s\", testNet.Name))\n\tassert.NilError(t, err)\n\toriginalEndpointsCount := len(existingEndpoints)\n\n\ttID := testutil.Identifier(t)\n\tbase.Cmd(\n\t\t\"run\",\n\t\t\"--name\",\n\t\ttID,\n\t\t\"--rm\",\n\t\t\"--net\", testNet.Name,\n\t\ttestutil.CommonImage,\n\t\t\"ipconfig\", \"/all\",\n\t).AssertOK()\n\n\texistingEndpoints, err = listHnsEndpointsRegex(fmt.Sprintf(\".*_%s\", testNet.Name))\n\tassert.NilError(t, err)\n\tassert.Equal(t, originalEndpointsCount, len(existingEndpoints), \"the number of HNS endpoints should equal pre-test amount\")\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc capShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates := []string{}\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_restart_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\t\"gotest.tools/v3/poll\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\nfunc TestRunRestart(t *testing.T) {\n\tconst (\n\t\thostPort = 8080\n\t)\n\ttestContainerName := testutil.Identifier(t)\n\tif testing.Short() {\n\t\tt.Skipf(\"test is long\")\n\t}\n\tbase := testutil.NewBase(t)\n\tif !base.DaemonIsKillable {\n\t\tt.Skip(\"daemon is not killable (hint: set \\\"-test.allow-kill-daemon\\\")\")\n\t}\n\tt.Log(\"NOTE: this test may take a while\")\n\n\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\n\tbase.Cmd(\"run\", \"-d\",\n\t\t\"--restart=always\",\n\t\t\"--name\", testContainerName,\n\t\t\"-p\", fmt.Sprintf(\"127.0.0.1:%d:80\", hostPort),\n\t\ttestutil.NginxAlpineImage).AssertOK()\n\n\tcheck := func(httpGetRetry int) error {\n\t\tresp, err := nettestutil.HTTPGet(fmt.Sprintf(\"http://127.0.0.1:%d\", hostPort), httpGetRetry, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) {\n\t\t\treturn fmt.Errorf(\"expected contain %q, got %q\",\n\t\t\t\ttestutil.NginxAlpineIndexHTMLSnippet, string(respBody))\n\t\t}\n\t\treturn nil\n\t}\n\tassert.NilError(t, check(5))\n\n\tbase.KillDaemon()\n\tbase.EnsureDaemonActive()\n\n\tconst (\n\t\tmaxRetry = 30\n\t\tsleep    = 3 * time.Second\n\t)\n\tfor i := 0; i < maxRetry; i++ {\n\t\tt.Logf(\"(retry %d) ps -a: %q\", i, base.Cmd(\"ps\", \"-a\").Run().Combined())\n\t\terr := check(1)\n\t\tif err == nil {\n\t\t\tt.Logf(\"test is passing, after %d retries\", i)\n\t\t\treturn\n\t\t}\n\t\ttime.Sleep(sleep)\n\t}\n\tbase.DumpDaemonLogs(10)\n\tt.Fatalf(\"the container does not seem to be restarted\")\n}\n\nfunc TestRunRestartWithOnFailure(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tif !nerdtest.IsDocker() {\n\t\ttestutil.RequireContainerdPlugin(base, \"io.containerd.internal.v1\", \"restart\", []string{\"on-failure\"})\n\t}\n\ttID := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", tID).Run()\n\tbase.Cmd(\"run\", \"-d\", \"--restart=on-failure:2\", \"--name\", tID, testutil.AlpineImage, \"sh\", \"-c\", \"exit 1\").AssertOK()\n\n\tcheck := func(log poll.LogT) poll.Result {\n\t\tinspect := base.InspectContainer(tID)\n\t\tif inspect.State != nil && inspect.State.Status == \"exited\" {\n\t\t\treturn poll.Success()\n\t\t}\n\t\treturn poll.Continue(\"container is not yet exited\")\n\t}\n\tpoll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))\n\tinspect := base.InspectContainer(tID)\n\tassert.Equal(t, inspect.RestartCount, 2)\n}\n\nfunc TestRunRestartWithUnlessStopped(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tif !nerdtest.IsDocker() {\n\t\ttestutil.RequireContainerdPlugin(base, \"io.containerd.internal.v1\", \"restart\", []string{\"unless-stopped\"})\n\t}\n\ttID := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", tID).Run()\n\tbase.Cmd(\"run\", \"-d\", \"--restart=unless-stopped\", \"--name\", tID, testutil.AlpineImage, \"sh\", \"-c\", \"exit 1\").AssertOK()\n\n\tcheck := func(log poll.LogT) poll.Result {\n\t\tinspect := base.InspectContainer(tID)\n\t\tif inspect.State != nil && inspect.State.Status == \"exited\" {\n\t\t\treturn poll.Success()\n\t\t}\n\t\tif inspect.RestartCount == 2 {\n\t\t\tbase.Cmd(\"stop\", tID).AssertOK()\n\t\t}\n\t\treturn poll.Continue(\"container is not yet exited\")\n\t}\n\tpoll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))\n\tinspect := base.InspectContainer(tID)\n\tassert.Equal(t, inspect.RestartCount, 2)\n}\n\nfunc TestUpdateRestartPolicy(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tif !nerdtest.IsDocker() {\n\t\ttestutil.RequireContainerdPlugin(base, \"io.containerd.internal.v1\", \"restart\", []string{\"on-failure\"})\n\t}\n\ttID := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", tID).Run()\n\tbase.Cmd(\"run\", \"-d\", \"--restart=on-failure:1\", \"--name\", tID, testutil.AlpineImage, \"sh\", \"-c\", \"exit 1\").AssertOK()\n\tbase.Cmd(\"update\", \"--restart=on-failure:2\", tID).AssertOK()\n\tcheck := func(log poll.LogT) poll.Result {\n\t\tinspect := base.InspectContainer(tID)\n\t\tif inspect.State != nil && inspect.State.Status == \"exited\" {\n\t\t\treturn poll.Success()\n\t\t}\n\t\treturn poll.Continue(\"container is not yet exited\")\n\t}\n\tpoll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))\n\tinspect := base.InspectContainer(tID)\n\tassert.Equal(t, inspect.RestartCount, 2)\n}\n\n// The test is to add a restart policy to a container which has not restart policy before,\n// and check it can work correctly.\nfunc TestAddRestartPolicy(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tif !nerdtest.IsDocker() {\n\t\ttestutil.RequireContainerdPlugin(base, \"io.containerd.internal.v1\", \"restart\", []string{\"on-failure\"})\n\t}\n\ttID := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", tID).Run()\n\tbase.Cmd(\"run\", \"-d\", \"--name\", tID, testutil.NginxAlpineImage).AssertOK()\n\tbase.Cmd(\"update\", \"--restart=on-failure\", tID).AssertOK()\n\tinspect := base.InspectContainer(tID)\n\torgialPid := inspect.State.Pid\n\texec.Command(\"kill\", \"-9\", fmt.Sprintf(\"%v\", orgialPid)).Run()\n\n\tcheck := func(log poll.LogT) poll.Result {\n\t\tinspect := base.InspectContainer(tID)\n\t\tif inspect.State != nil && inspect.State.Status == \"running\" && inspect.State.Pid != orgialPid {\n\t\t\treturn poll.Success()\n\t\t}\n\t\treturn poll.Continue(\"container is not yet running\")\n\t}\n\tpoll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))\n\tinspect = base.InspectContainer(tID)\n\tassert.Equal(t, inspect.RestartCount, 1)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_runtime_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunSysctl(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--sysctl\", \"net.ipv4.ip_forward=1\", testutil.AlpineImage, \"cat\", \"/proc/sys/net/ipv4/ip_forward\")\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(\"1\\n\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunSysctl_DefaultUnprivilegedPortStart(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// No --sysctl flags, default network mode (non-host).\n\t// We expect net.ipv4.ip_unprivileged_port_start=0 inside the container,\n\t// because withDefaultUnprivilegedPortSysctl should apply the default.\n\ttestCase.Command = test.Command(\"run\", \"--rm\", testutil.AlpineImage, \"cat\", \"/proc/sys/net/ipv4/ip_unprivileged_port_start\")\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(\"0\\n\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunSysctl_UnprivilegedPortStartOverride(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// User explicitly sets net.ipv4.ip_unprivileged_port_start=1000.\n\t// We must NOT override this; the container should see \"1000\".\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--sysctl\", \"net.ipv4.ip_unprivileged_port_start=1000\", testutil.AlpineImage, \"cat\", \"/proc/sys/net/ipv4/ip_unprivileged_port_start\")\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(\"1000\\n\"))\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_security_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc getCapEff(base *testutil.Base, args ...string) uint64 {\n\tfullArgs := []string{\"run\", \"--rm\"}\n\tfullArgs = append(fullArgs, args...)\n\tfullArgs = append(fullArgs,\n\t\ttestutil.AlpineImage,\n\t\t\"sh\",\n\t\t\"-euc\",\n\t\t\"grep -w ^CapEff: /proc/self/status | sed -e \\\"s/^CapEff:[[:space:]]*//g\\\"\",\n\t)\n\tcmd := base.Cmd(fullArgs...)\n\tres := cmd.Run()\n\tassert.NilError(base.T, res.Error)\n\ts := strings.TrimSpace(res.Stdout())\n\tui64, err := strconv.ParseUint(s, 16, 64)\n\tassert.NilError(base.T, err)\n\treturn ui64\n}\n\nconst (\n\tCapNetRaw  = 13\n\tCapIPCLock = 14\n)\n\nfunc TestRunCap(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\n\t// allCaps varies depending on the target version and the kernel version.\n\tallCaps := getCapEff(base, \"--privileged\")\n\n\t// https://github.com/containerd/containerd/blob/9a9bd097564b0973bfdb0b39bf8262aa1b7da6aa/oci/spec.go#L93\n\tdefaultCaps := uint64(0xa80425fb)\n\n\tt.Logf(\"allCaps=%016x\", allCaps)\n\n\ttype testCase struct {\n\t\targs   []string\n\t\tcapEff uint64\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tcapEff: allCaps & defaultCaps,\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-add=all\"},\n\t\t\tcapEff: allCaps,\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-add=ipc_lock\"},\n\t\t\tcapEff: (allCaps & defaultCaps) | (1 << CapIPCLock),\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-add=all\", \"--cap-drop=net_raw\"},\n\t\t\tcapEff: allCaps ^ (1 << CapNetRaw),\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-drop=all\", \"--cap-add=net_raw\"},\n\t\t\tcapEff: 1 << CapNetRaw,\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-drop=all\", \"--cap-add=NET_RAW\"},\n\t\t\tcapEff: 1 << CapNetRaw,\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-drop=all\", \"--cap-add=cap_net_raw\"},\n\t\t\tcapEff: 1 << CapNetRaw,\n\t\t},\n\t\t{\n\t\t\targs:   []string{\"--cap-drop=all\", \"--cap-add=CAP_NET_RAW\"},\n\t\t\tcapEff: 1 << CapNetRaw,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttc := tc // IMPORTANT\n\t\tname := \"default\"\n\t\tif len(tc.args) > 0 {\n\t\t\tname = strings.Join(tc.args, \"_\")\n\t\t}\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tgot := getCapEff(base, tc.args...)\n\t\t\tassert.Equal(t, tc.capEff, got)\n\t\t})\n\t}\n}\n\nfunc TestRunSecurityOptSeccomp(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttype testCase struct {\n\t\targs    []string\n\t\tseccomp int\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tseccomp: 2,\n\t\t},\n\t\t{\n\t\t\targs:    []string{\"--security-opt\", \"seccomp=unconfined\"},\n\t\t\tseccomp: 0,\n\t\t},\n\t\t{\n\t\t\targs:    []string{\"--privileged\"},\n\t\t\tseccomp: 0,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttc := tc // IMPORTANT\n\t\tname := \"default\"\n\t\tif len(tc.args) > 0 {\n\t\t\tname = strings.Join(tc.args, \"_\")\n\t\t}\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\targs := []string{\"run\", \"--rm\"}\n\t\t\targs = append(args, tc.args...)\n\t\t\t// NOTE: busybox grep does not support -oP \\K\n\t\t\targs = append(args, testutil.AlpineImage, \"grep\", \"-Eo\", `^Seccomp:\\s*([0-9]+)`, \"/proc/1/status\")\n\t\t\tcmd := base.Cmd(args...)\n\t\t\tf := func(expectedSeccomp int) func(string) error {\n\t\t\t\treturn func(stdout string) error {\n\t\t\t\t\ts := strings.TrimPrefix(stdout, \"Seccomp:\")\n\t\t\t\t\ts = strings.TrimSpace(s)\n\t\t\t\t\ti, err := strconv.Atoi(s)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to parse line %q: %w\", stdout, err)\n\t\t\t\t\t}\n\t\t\t\t\tif i != expectedSeccomp {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected Seccomp to be %d, got %d\", expectedSeccomp, i)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tcmd.AssertOutWithFunc(f(tc.seccomp))\n\t\t})\n\t}\n}\n\nfunc TestRunApparmor(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tdefaultProfile := fmt.Sprintf(\"%s-default\", base.Target)\n\tif !apparmorutil.CanLoadNewProfile() && !apparmorutil.CanApplySpecificExistingProfile(defaultProfile) {\n\t\tt.Skipf(\"needs to be able to apply %q profile\", defaultProfile)\n\t}\n\tattrCurrentPath := \"/proc/self/attr/apparmor/current\"\n\tif _, err := os.Stat(attrCurrentPath); err != nil {\n\t\tattrCurrentPath = \"/proc/self/attr/current\"\n\t}\n\tattrCurrentEnforceExpected := fmt.Sprintf(\"%s (enforce)\\n\", defaultProfile)\n\tbase.Cmd(\"run\", \"--rm\", testutil.AlpineImage, \"cat\", attrCurrentPath).AssertOutExactly(attrCurrentEnforceExpected)\n\tbase.Cmd(\"run\", \"--rm\", \"--security-opt\", \"apparmor=\"+defaultProfile, testutil.AlpineImage, \"cat\", attrCurrentPath).AssertOutExactly(attrCurrentEnforceExpected)\n\tbase.Cmd(\"run\", \"--rm\", \"--security-opt\", \"apparmor=unconfined\", testutil.AlpineImage, \"cat\", attrCurrentPath).AssertOutContains(\"unconfined\")\n\tbase.Cmd(\"run\", \"--rm\", \"--privileged\", testutil.AlpineImage, \"cat\", attrCurrentPath).AssertOutContains(\"unconfined\")\n}\n\n// TestRunSeccompCapSysPtrace tests https://github.com/containerd/nerdctl/issues/976\nfunc TestRunSeccompCapSysPtrace(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"run\", \"--rm\", \"--cap-add\", \"sys_ptrace\", testutil.AlpineImage, \"sh\", \"-euxc\", \"apk add -q strace && strace true\").AssertOK()\n\t// Docker/Moby 's seccomp profile allows ptrace(2) by default, but containerd does not (yet): https://github.com/containerd/containerd/issues/6802\n}\n\nfunc TestRunSystemPathsUnconfined(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\tconst findmnt = \"`apk add -q findmnt && findmnt -R /proc && findmnt -R /sys`\"\n\tresult := base.Cmd(\"run\", \"--rm\", testutil.AlpineImage, \"sh\", \"-euxc\", findmnt).Run()\n\tdefaultContainerOutput := result.Combined()\n\n\tvar confined []string\n\n\tfor _, path := range []string{\n\t\t\"/proc/kcore\",\n\t\t\"/proc/keys\",\n\t\t\"/proc/latency_stats\",\n\t\t\"/proc/sched_debug\",\n\t\t\"/proc/scsi\",\n\t\t\"/proc/timer_list\",\n\t\t\"/proc/timer_stats\",\n\t\t\"/sys/firmware\",\n\t\t\"/sys/fs/selinux\",\n\t} {\n\t\t// Not each distribution will support every masked path here.\n\t\tif strings.Contains(defaultContainerOutput, path) {\n\t\t\tconfined = append(confined, path)\n\t\t}\n\t}\n\n\tassert.Check(t, len(confined) != 0, \"Default container has no confined paths to validate\")\n\n\tresult = base.Cmd(\"run\", \"--rm\", \"--security-opt\", \"systempaths=unconfined\", testutil.AlpineImage, \"sh\", \"-euxc\", findmnt).Run()\n\tunconfinedContainerOutput := result.Combined()\n\n\tfor _, path := range confined {\n\t\tassert.Assert(t, !strings.Contains(unconfinedContainerOutput, path), fmt.Sprintf(\"%s should not be masked when unconfined\", path))\n\t}\n\n\tfor _, path := range []string{\n\t\t\"/proc/acpi\",\n\t\t\"/proc/bus\",\n\t\t\"/proc/fs\",\n\t\t\"/proc/irq\",\n\t\t\"/proc/sysrq-trigger\",\n\t\t\"/proc/sys\",\n\t} {\n\t\tfindmntPath := fmt.Sprintf(\"`apk add -q findmnt && findmnt %s`\", path)\n\n\t\tresult := base.Cmd(\"run\", \"--rm\", testutil.AlpineImage, \"sh\", \"-euxc\", findmntPath).Run()\n\n\t\t// Not each distribution will support every read-only path here.\n\t\tif strings.Contains(result.Combined(), path) {\n\t\t\tresult = base.Cmd(\"run\", \"--rm\", \"--security-opt\", \"systempaths=unconfined\", testutil.AlpineImage, \"sh\", \"-euxc\", findmntPath).Run()\n\t\t\tassert.Assert(t, !strings.Contains(result.Combined(), \"ro,\"), fmt.Sprintf(\"%s should not be read-only when unconfined\", path))\n\t\t}\n\t}\n}\n\nfunc TestRunPrivileged(t *testing.T) {\n\t// docker does not support --privileged-without-host-devices\n\ttestutil.DockerIncompatible(t)\n\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"test skipped for rootless privileged containers\")\n\t}\n\n\tbase := testutil.NewBase(t)\n\n\tdevPath := \"/dev/dummy-zero\"\n\n\t// a dummy zero device: mknod /dev/dummy-zero c 1 5\n\thelperCmd := exec.Command(\"mknod\", []string{devPath, \"c\", \"1\", \"5\"}...)\n\tif out, err := helperCmd.CombinedOutput(); err != nil {\n\t\terr = fmt.Errorf(\"cannot create %q: %q: %w\", devPath, string(out), err)\n\t\tt.Fatal(err)\n\t}\n\n\t// ensure the file will be removed in case of failed in the test\n\tdefer func() {\n\t\texec.Command(\"rm\", devPath).Run()\n\t}()\n\n\t// get device with host devices\n\tbase.Cmd(\"run\", \"--rm\", \"--privileged\", testutil.AlpineImage, \"ls\", devPath).AssertOutExactly(devPath + \"\\n\")\n\n\t// get device without host devices\n\tres := base.Cmd(\"run\", \"--rm\", \"--privileged\", \"--security-opt\", \"privileged-without-host-devices\", testutil.AlpineImage, \"ls\", devPath).Run()\n\n\t// normally for not a exists file, the `ls` will return `1``.\n\tassert.Check(t, res.ExitCode != 0, res)\n\n\t// something like `ls: /dev/dummy-zero: No such file or directory`\n\tassert.Check(t, strings.Contains(res.Combined(), \"No such file or directory\"))\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_soci_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunSoci(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Docker),\n\t\trequire.Amd64,\n\t\tnerdtest.Soci,\n\t)\n\n\t// Tests relying on the output of \"mount\" cannot be run in parallel obviously\n\ttestCase.NoParallel = true\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Custom(\"mount\").Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tdata.Labels().Set(\"beforeCount\", strconv.Itoa(strings.Count(stdout, \"fuse.rawBridge\")))\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.FfmpegSociImage)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"--snapshotter=soci\", \"run\", \"--rm\", testutil.FfmpegSociImage)\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tvar afterCount int\n\t\t\t\tbeforeCount, _ := strconv.Atoi(data.Labels().Get(\"beforeCount\"))\n\n\t\t\t\thelpers.Custom(\"mount\").Run(&test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tafterCount = strings.Count(stdout, \"fuse.rawBridge\")\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\tassert.Equal(t, 11, afterCount-beforeCount, \"expected the number of fuse.rawBridge\")\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_stargz_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunStargz(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\tnerdtest.Stargz,\n\t\trequire.Amd64,\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.Command = test.Command(\"--snapshotter=stargz\", \"run\", \"--quiet\", \"--rm\", testutil.FedoraESGZImage, \"ls\", \"/.stargz-snapshotter\")\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_systemd_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunWithSystemdAlways(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"containerName\", testutil.Identifier(t))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"should mount cgroup filesystem as rw\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"run\", \"--name\", containerName, \"--systemd=always\", \"--entrypoint=/bin/bash\", testutil.UbuntuImage, \"-c\", \"mount | grep cgroup\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"(rw,\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should expose SIGTERM+3 stop signal label\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"inspect\", \"--format\", \"{{json .Config.Labels}}\", containerName)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"SIGRTMIN+3\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithSystemdTrueEnabled(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Amd64,\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), \"--systemd=true\", \"--entrypoint=/sbin/init\", testutil.SystemdImage)\n\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier())\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t// should expose SIGTERM+3 stop signal labels\n\t\thelpers.Command(\"inspect\", \"--format\", \"{{json .Config.Labels}}\", data.Identifier()).\n\t\t\tRun(&test.Expected{\n\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\tOutput:   expect.Contains(\"SIGRTMIN+3\"),\n\t\t\t})\n\n\t\t// waits for systemd to become ready and lists systemd jobs\n\t\treturn helpers.Command(\"exec\", data.Identifier(), \"sh\", \"-c\", \"--\", `tries=0\nuntil systemctl is-system-running >/dev/null 2>&1; do\n\t>&2 printf \"Waiting for systemd to come up...\\n\"\n\tsleep 1s\n\ttries=$(( tries + 1))\n\t[ $tries -lt 10 ] || {\n\t\t>&2 printf \"systemd failed to come up in a reasonable amount of time\\n\"\n\t\texit 1\n\t}\ndone\nsystemctl list-jobs`)\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"jobs\"))\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithSystemdTrueDisabled(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Amd64,\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"containerName\", testutil.Identifier(t))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\treturn helpers.Command(\"run\", \"--name\", containerName, \"--systemd=true\", \"--entrypoint=/bin/bash\", testutil.SystemdImage, \"-c\", \"systemctl list-jobs\")\n\t}\n\ttestCase.Expected = test.Expects(1, []error{errors.New(\"System has not been booted with systemd as init system\")}, nil)\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithSystemdFalse(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"containerName\", testutil.Identifier(t))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"should mount cgroup filesystem as ro\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"run\", \"--name\", containerName, \"--systemd=false\", \"--entrypoint=/bin/bash\", testutil.UbuntuImage, \"-c\", \"mount | grep cgroup\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"(ro,\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should expose SIGTERM stop signal label\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"inspect\", \"--format\", \"{{json .Config.Labels}}\", containerName)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"SIGTERM\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithNoSystemd(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"containerName\", testutil.Identifier(t))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"should mount cgroup filesystem as ro\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"run\", \"--name\", containerName, \"--entrypoint=/bin/bash\", testutil.UbuntuImage, \"-c\", \"mount | grep cgroup\")\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"(ro,\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should expose SIGTERM stop signal label\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"inspect\", \"--format\", \"{{json .Config.Labels}}\", containerName)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"SIGTERM\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithSystemdPrivilegedError(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Amd64,\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.Command = test.Command(\"run\", \"--privileged\", \"--rm\", \"--systemd=always\", \"--entrypoint=/sbin/init\", testutil.SystemdImage)\n\ttestCase.Expected = test.Expects(1, []error{errors.New(\"if --privileged is used with systemd `--security-opt privileged-without-host-devices` must also be used\")}, nil)\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithSystemdPrivilegedSuccess(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Amd64,\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := testutil.Identifier(t)\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", containerName, \"--privileged\", \"--security-opt\", \"privileged-without-host-devices\", \"--systemd=true\", \"--entrypoint=/sbin/init\", testutil.SystemdImage)\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\treturn helpers.Command(\"inspect\", \"--format\", \"{{json .Config.Labels}}\", containerName)\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"SIGRTMIN+3\"))\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\t\"gotest.tools/v3/icmd\"\n\t\"gotest.tools/v3/poll\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunEntrypointWithBuild(t *testing.T) {\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nENTRYPOINT [\"echo\", \"foo\"]\nCMD [\"echo\", \"bar\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"image\", data.Identifier())\n\t\t\thelpers.Ensure(\"build\", \"-t\", data.Labels().Get(\"image\"), data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Run image\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"foo echo bar\\n\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Run image empty entrypoint\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--entrypoint\", \"\", data.Labels().Get(\"image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Run image time entrypoint\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--entrypoint\", \"time\", data.Labels().Get(\"image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Run image empty entrypoint custom command\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--entrypoint\", \"\", data.Labels().Get(\"image\"), \"echo\", \"blah\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"blah\"),\n\t\t\t\t\texpect.DoesNotContain(\"foo\", \"bar\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Run image time entrypoint custom command\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--entrypoint\", \"time\", data.Labels().Get(\"image\"), \"echo\", \"blah\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.All(\n\t\t\t\t\texpect.Contains(\"blah\"),\n\t\t\t\t\texpect.DoesNotContain(\"foo\", \"bar\"),\n\t\t\t\t)),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWorkdir(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tdir := \"/foo\"\n\tif runtime.GOOS == \"windows\" {\n\t\tdir = \"c:\" + dir\n\t}\n\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--workdir=\"+dir, testutil.CommonImage, \"pwd\")\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(dir))\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithDoubleDash(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Command = test.Command(\"run\", \"--rm\", testutil.CommonImage, \"--\", \"sh\", \"-euxc\", \"exit 0\")\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil)\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunExitCode(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"exit0\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"exit123\"))\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(\"exit0\"), testutil.CommonImage, \"sh\", \"-euxc\", \"exit 0\")\n\t\thelpers.Command(\"run\", \"--name\", data.Identifier(\"exit123\"), testutil.CommonImage, \"sh\", \"-euxc\", \"exit 123\").\n\t\t\tRun(&test.Expected{ExitCode: 123})\n\t}\n\n\ttestCase.Command = test.Command(\"ps\", \"-a\")\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\tErrors:   nil,\n\t\t\tOutput: expect.All(\n\t\t\t\texpect.Match(regexp.MustCompile(\"Exited [(]123[)][A-Za-z0-9 ]+\"+data.Identifier(\"exit123\"))),\n\t\t\t\texpect.Match(regexp.MustCompile(\"Exited [(]0[)][A-Za-z0-9 ]+\"+data.Identifier(\"exit0\"))),\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier(\"exit0\")).State.Status, \"exited\")\n\t\t\t\t\tassert.Equal(t, nerdtest.InspectContainer(helpers, data.Identifier(\"exit123\")).State.Status, \"exited\")\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunCIDFile(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--rm\", \"--cidfile\", data.Temp().Path(\"cid-file\"), testutil.CommonImage)\n\t\tdata.Temp().Exists(\"cid-file\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"run\", \"--rm\", \"--cidfile\", data.Temp().Path(\"cid-file\"), testutil.CommonImage)\n\t}\n\n\t// Docker will return 125 while nerdctl returns 1, so, generic fail instead of specific exit code\n\ttestCase.Expected = test.Expects(expect.ExitCodeGenericFail, []error{errors.New(\"container ID file found\")}, nil)\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunEnvFile(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Env = map[string]string{\n\t\t\"HOST_ENV\": \"ENV-IN-HOST\",\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(\"# this is a comment line\\nTESTKEY1=TESTVAL1\", \"env1-file\")\n\t\tdata.Temp().Save(\"# this is a comment line\\nTESTKEY2=TESTVAL2\\nHOST_ENV\", \"env2-file\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\n\t\t\t\"run\", \"--rm\",\n\t\t\t\"--env-file\", data.Temp().Path(\"env1-file\"),\n\t\t\t\"--env-file\", data.Temp().Path(\"env2-file\"),\n\t\t\ttestutil.CommonImage, \"env\")\n\t}\n\n\ttestCase.Expected = test.Expects(\n\t\texpect.ExitCodeSuccess,\n\t\tnil,\n\t\texpect.Contains(\"TESTKEY1=TESTVAL1\", \"TESTKEY2=TESTVAL2\", \"HOST_ENV=ENV-IN-HOST\"),\n\t)\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunEnv(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Env = map[string]string{\n\t\t\"CORGE\":  \"corge-value-in-host\",\n\t\t\"GARPLY\": \"garply-value-in-host\",\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"run\", \"--rm\",\n\t\t\t\"--env\", \"FOO=foo1,foo2\",\n\t\t\t\"--env\", \"BAR=bar1 bar2\",\n\t\t\t\"--env\", \"BAZ=\",\n\t\t\t\"--env\", \"QUX\", // not exported in OS\n\t\t\t\"--env\", \"QUUX=quux1\",\n\t\t\t\"--env\", \"QUUX=quux2\",\n\t\t\t\"--env\", \"CORGE\", // OS exported\n\t\t\t\"--env\", \"GRAULT=grault_key=grault_value\", // value contains `=` char\n\t\t\t\"--env\", \"GARPLY=\", // OS exported\n\t\t\t\"--env\", \"WALDO=\", // not exported in OS\n\t\t\ttestutil.CommonImage, \"env\")\n\t}\n\n\tvalidate := []test.Comparator{\n\t\texpect.Contains(\n\t\t\t\"\\nFOO=foo1,foo2\\n\",\n\t\t\t\"\\nBAR=bar1 bar2\\n\",\n\t\t\t\"\\nQUUX=quux2\\n\",\n\t\t\t\"\\nCORGE=corge-value-in-host\\n\",\n\t\t\t\"\\nGRAULT=grault_key=grault_value\\n\",\n\t\t),\n\t\texpect.DoesNotContain(\"QUX\"),\n\t}\n\n\tif runtime.GOOS != \"windows\" {\n\t\tvalidate = append(\n\t\t\tvalidate,\n\t\t\texpect.Contains(\n\t\t\t\t\"\\nBAZ=\\n\",\n\t\t\t\t\"\\nGARPLY=\\n\",\n\t\t\t\t\"\\nWALDO=\\n\",\n\t\t\t),\n\t\t)\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.All(validate...))\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunHostnameEnv(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"default hostname\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"--rm\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t// Note: on Windows, just straight passing the command will not work (some cmd escaping weirdness?)\n\t\t\t\tcmd.Feed(strings.NewReader(`[[ \"HOSTNAME=$(hostname)\" == \"$(env | grep HOSTNAME)\" ]]`))\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with --hostname\",\n\t\t\t// Windows does not support --hostname\n\t\t\tRequire:  require.Not(require.Windows),\n\t\t\tCommand:  test.Command(\"run\", \"--rm\", \"--quiet\", \"--hostname\", \"foobar\", testutil.CommonImage, \"env\"),\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"HOSTNAME=foobar\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunStdin(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst testStr = \"test-run-stdin\"\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcmd := helpers.Command(\"run\", \"--rm\", \"-i\", testutil.CommonImage, \"cat\")\n\t\tcmd.Feed(strings.NewReader(testStr))\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(testStr))\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithJsonFileLogDriver(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"json-file log driver is not yet implemented on Windows\")\n\t}\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", \"json-file\", \"--log-opt\", \"max-size=5K\", \"--log-opt\", \"max-file=2\", \"--name\", containerName, testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"hexdump -C /dev/urandom | head -n1000\").AssertOK()\n\n\ttime.Sleep(3 * time.Second)\n\tinspectedContainer := base.InspectContainer(containerName)\n\tlogJSONPath := filepath.Dir(inspectedContainer.LogPath)\n\t// matches = current log file + old log files to retain\n\tmatches, err := filepath.Glob(filepath.Join(logJSONPath, inspectedContainer.ID+\"*\"))\n\tassert.NilError(t, err)\n\tif len(matches) != 2 {\n\t\tt.Fatalf(\"the number of log files is not equal to 2 files, got: %s\", matches)\n\t}\n\tfor _, file := range matches {\n\t\tfInfo, err := os.Stat(file)\n\t\tassert.NilError(t, err)\n\t\t// The log file size is compared to 5200 bytes (instead 5k) to keep docker compatibility.\n\t\t// Docker log rotation lacks precision because the size check is done at the log entry level\n\t\t// and not at the byte level (io.Writer), so docker log files can exceed 5k\n\t\tif fInfo.Size() > 5200 {\n\t\t\tt.Fatal(\"file size exceeded 5k\")\n\t\t}\n\t}\n}\n\nfunc TestRunWithJsonFileLogDriverAndLogPathOpt(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"json-file log driver is not yet implemented on Windows\")\n\t}\n\ttestutil.DockerIncompatible(t)\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tcustomLogJSONPath := filepath.Join(t.TempDir(), containerName, containerName+\"-json.log\")\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", \"json-file\", \"--log-opt\", fmt.Sprintf(\"log-path=%s\", customLogJSONPath), \"--log-opt\", \"max-size=5K\", \"--log-opt\", \"max-file=2\", \"--name\", containerName, testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"hexdump -C /dev/urandom | head -n1000\").AssertOK()\n\n\ttime.Sleep(3 * time.Second)\n\trawBytes, err := os.ReadFile(customLogJSONPath)\n\tassert.NilError(t, err)\n\tif len(rawBytes) == 0 {\n\t\tt.Fatalf(\"logs are not written correctly to log-path: %s\", customLogJSONPath)\n\t}\n\n\t// matches = current log file + old log files to retain\n\tmatches, err := filepath.Glob(filepath.Join(filepath.Dir(customLogJSONPath), containerName+\"*\"))\n\tassert.NilError(t, err)\n\tif len(matches) != 2 {\n\t\tt.Fatalf(\"the number of log files is not equal to 2 files, got: %s\", matches)\n\t}\n\tfor _, file := range matches {\n\t\tfInfo, err := os.Stat(file)\n\t\tassert.NilError(t, err)\n\t\tif fInfo.Size() > 5200 {\n\t\t\tt.Fatal(\"file size exceeded 5k\")\n\t\t}\n\t}\n}\n\nfunc TestRunWithJournaldLogDriver(t *testing.T) {\n\ttestutil.RequireExecutable(t, \"journalctl\")\n\tjournalctl, _ := exec.LookPath(\"journalctl\")\n\tres := icmd.RunCmd(icmd.Command(journalctl, \"-xe\"))\n\tif res.ExitCode != 0 {\n\t\tt.Skipf(\"current user is not allowed to access journal logs: %s\", res.Combined())\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"journald log driver is not yet implemented on Windows\")\n\t}\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", \"journald\", \"--name\", containerName, testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo foo; echo bar\").AssertOK()\n\n\ttime.Sleep(3 * time.Second)\n\n\tinspectedContainer := base.InspectContainer(containerName)\n\n\ttype testCase struct {\n\t\tname   string\n\t\tfilter string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:   \"filter journald logs using SYSLOG_IDENTIFIER field\",\n\t\t\tfilter: fmt.Sprintf(\"SYSLOG_IDENTIFIER=%s\", inspectedContainer.ID[:12]),\n\t\t},\n\t\t{\n\t\t\tname:   \"filter journald logs using CONTAINER_NAME field\",\n\t\t\tfilter: fmt.Sprintf(\"CONTAINER_NAME=%s\", containerName),\n\t\t},\n\t\t{\n\t\t\tname:   \"filter journald logs using IMAGE_NAME field\",\n\t\t\tfilter: fmt.Sprintf(\"IMAGE_NAME=%s\", testutil.CommonImage),\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfound := 0\n\t\t\tcheck := func(log poll.LogT) poll.Result {\n\t\t\t\tres := icmd.RunCmd(icmd.Command(journalctl, \"--no-pager\", \"--since\", \"2 minutes ago\", tc.filter))\n\t\t\t\tassert.Equal(t, 0, res.ExitCode, res)\n\t\t\t\tif strings.Contains(res.Stdout(), \"bar\") && strings.Contains(res.Stdout(), \"foo\") {\n\t\t\t\t\tfound = 1\n\t\t\t\t\treturn poll.Success()\n\t\t\t\t}\n\t\t\t\treturn poll.Continue(\"reading from journald is not yet finished\")\n\t\t\t}\n\t\t\tpoll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second))\n\t\t\tassert.Equal(t, 1, found)\n\t\t})\n\t}\n}\n\nfunc TestRunWithJournaldLogDriverAndLogOpt(t *testing.T) {\n\ttestutil.RequireExecutable(t, \"journalctl\")\n\tjournalctl, _ := exec.LookPath(\"journalctl\")\n\tres := icmd.RunCmd(icmd.Command(journalctl, \"-xe\"))\n\tif res.ExitCode != 0 {\n\t\tt.Skipf(\"current user is not allowed to access journal logs: %s\", res.Combined())\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"journald log driver is not yet implemented on Windows\")\n\t}\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", \"journald\", \"--log-opt\", \"tag={{.FullID}}\", \"--name\", containerName, testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo foo; echo bar\").AssertOK()\n\n\ttime.Sleep(3 * time.Second)\n\tinspectedContainer := base.InspectContainer(containerName)\n\tfound := 0\n\tcheck := func(log poll.LogT) poll.Result {\n\t\tres := icmd.RunCmd(icmd.Command(journalctl, \"--no-pager\", \"--since\", \"2 minutes ago\", fmt.Sprintf(\"SYSLOG_IDENTIFIER=%s\", inspectedContainer.ID)))\n\t\tassert.Equal(t, 0, res.ExitCode, res)\n\t\tif strings.Contains(res.Stdout(), \"bar\") && strings.Contains(res.Stdout(), \"foo\") {\n\t\t\tfound = 1\n\t\t\treturn poll.Success()\n\t\t}\n\t\treturn poll.Continue(\"reading from journald is not yet finished\")\n\t}\n\tpoll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(20*time.Second))\n\tassert.Equal(t, 1, found)\n}\n\nfunc TestRunWithLogBinary(t *testing.T) {\n\ttestutil.RequiresBuild(t)\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"buildkit is not enabled on windows, this feature may work on windows.\")\n\t}\n\ttestutil.DockerIncompatible(t)\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t) + \"-image\"\n\tcontainerName := testutil.Identifier(t)\n\n\tvar dockerfile = `\nFROM ` + testutil.GolangImage + ` as builder\nWORKDIR /go/src/\nRUN mkdir -p logger\nWORKDIR /go/src/logger\nRUN echo '\\\n\tpackage main \\n\\\n\t\\n\\\n\timport ( \\n\\\n\t\"bufio\" \\n\\\n\t\"context\" \\n\\\n\t\"fmt\" \\n\\\n\t\"io\" \\n\\\n\t\"os\" \\n\\\n\t\"path/filepath\" \\n\\\n\t\"sync\" \\n\\\n\t\\n\\\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\\n\\\n\t)\\n\\\n\n\tfunc main() {\\n\\\n\t\tlogging.Run(log)\\n\\\n\t}\\n\\\n\n\tfunc log(ctx context.Context, config *logging.Config, ready func() error) error {\\n\\\n\t\tvar wg sync.WaitGroup \\n\\\n\t\twg.Add(2) \\n\\\n\t\t// forward both stdout and stderr to temp files \\n\\\n\t\tgo copy(&wg, config.Stdout, config.ID, \"stdout\") \\n\\\n\t\tgo copy(&wg, config.Stderr, config.ID, \"stderr\") \\n\\\n\n\t\t// signal that we are ready and setup for the container to be started \\n\\\n\t\tif err := ready(); err != nil { \\n\\\n\t\treturn err \\n\\\n\t\t} \\n\\\n\t\twg.Wait() \\n\\\n\t\treturn nil \\n\\\n\t}\\n\\\n\t\\n\\\n\tfunc copy(wg *sync.WaitGroup, r io.Reader, id string, kind string) { \\n\\\n\t\tf, _ := os.Create(filepath.Join(os.TempDir(), fmt.Sprintf(\"%s_%s.log\", id, kind))) \\n\\\n\t\tdefer f.Close() \\n\\\n\t\tdefer wg.Done() \\n\\\n\t\ts := bufio.NewScanner(r) \\n\\\n\t\tfor s.Scan() { \\n\\\n\t\t\tf.WriteString(s.Text()) \\n\\\n\t\t} \\n\\\n\t}\\n' >> main.go\n\n\nRUN go mod init\n# Workaround for \"package slices is not in GOROOT\" https://github.com/containerd/nerdctl/issues/4214\nRUN go get github.com/containerd/containerd/v2@v2.0.5\nRUN go mod tidy\nRUN go build .\n\nFROM scratch\nCOPY --from=builder /go/src/logger/logger /\n\t`\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\ttmpDir := t.TempDir()\n\tbase.Cmd(\"build\", buildCtx, \"--output\", fmt.Sprintf(\"type=local,src=/go/src/logger/logger,dest=%s\", tmpDir)).AssertOK()\n\tdefer base.Cmd(\"image\", \"rm\", \"-f\", imageName).AssertOK()\n\n\tbase.Cmd(\"container\", \"rm\", \"-f\", containerName).AssertOK()\n\tbase.Cmd(\"run\", \"-d\", \"--log-driver\", fmt.Sprintf(\"binary://%s/logger\", tmpDir), \"--name\", containerName, testutil.CommonImage,\n\t\t\"sh\", \"-euxc\", \"echo foo; echo bar\").AssertOK()\n\tdefer base.Cmd(\"container\", \"rm\", \"-f\", containerName).AssertOK()\n\n\tinspectedContainer := base.InspectContainer(containerName)\n\tbytes, err := os.ReadFile(filepath.Join(os.TempDir(), fmt.Sprintf(\"%s_%s.log\", inspectedContainer.ID, \"stdout\")))\n\tassert.NilError(t, err)\n\tlog := string(bytes)\n\tassert.Check(t, strings.Contains(log, \"foo\"))\n\tassert.Check(t, strings.Contains(log, \"bar\"))\n}\n\n// history: There was a bug that the --add-host items disappear when the another container created.\n// This test ensures that it doesn't happen.\n// (https://github.com/containerd/nerdctl/issues/2560)\nfunc TestRunAddHostRemainsWhenAnotherContainerCreated(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"ocihook is not yet supported on Windows\")\n\t}\n\tbase := testutil.NewBase(t)\n\n\tcontainerName := testutil.Identifier(t)\n\thostMapping := \"test-add-host:10.0.0.1\"\n\tbase.Cmd(\"run\", \"-d\", \"--add-host\", hostMapping, \"--name\", containerName, testutil.CommonImage, \"sleep\", nerdtest.Infinity).AssertOK()\n\tdefer base.Cmd(\"container\", \"rm\", \"-f\", containerName).Run()\n\n\tcheckEtcHosts := func(stdout string) error {\n\t\tmatcher, err := regexp.Compile(`^10.0.0.1\\s+test-add-host$`)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar found bool\n\t\tsc := bufio.NewScanner(bytes.NewBufferString(stdout))\n\t\tfor sc.Scan() {\n\t\t\tif matcher.Match(sc.Bytes()) {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\treturn fmt.Errorf(\"host not found\")\n\t\t}\n\t\treturn nil\n\t}\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"/etc/hosts\").AssertOutWithFunc(checkEtcHosts)\n\n\t// run another container\n\tbase.Cmd(\"run\", \"--rm\", testutil.CommonImage).AssertOK()\n\n\tbase.Cmd(\"exec\", containerName, \"cat\", \"/etc/hosts\").AssertOutWithFunc(checkEtcHosts)\n}\n\n// https://github.com/containerd/nerdctl/issues/2726\nfunc TestRunRmTime(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tbase.Cmd(\"pull\", \"--quiet\", testutil.CommonImage)\n\tt0 := time.Now()\n\tbase.Cmd(\"run\", \"--rm\", testutil.CommonImage, \"true\").AssertOK()\n\tt1 := time.Now()\n\ttook := t1.Sub(t0)\n\tvar deadline = 3 * time.Second\n\t// FIXME: Investigate? it appears that since the move to containerd 2 on Windows, this is taking longer.\n\tif runtime.GOOS == \"windows\" {\n\t\tdeadline = 10 * time.Second\n\t}\n\tif took > deadline {\n\t\tt.Fatalf(\"expected to have completed in %v, took %v\", deadline, took)\n\t}\n}\n\nfunc runAttachStdin(t *testing.T, testStr string, args []string) string {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"run attach test is not yet implemented on Windows\")\n\t}\n\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\topts := []func(*testutil.Cmd){\n\t\ttestutil.WithStdin(strings.NewReader(\"echo \" + testStr + \"\\nexit\\n\")),\n\t}\n\n\tfullArgs := []string{\"run\", \"--rm\", \"-i\"}\n\tfullArgs = append(fullArgs, args...)\n\tfullArgs = append(fullArgs,\n\t\t\"--name\",\n\t\tcontainerName,\n\t\ttestutil.CommonImage,\n\t)\n\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tresult := base.Cmd(fullArgs...).CmdOption(opts...).Run()\n\n\treturn result.Combined()\n}\n\nfunc runAttach(t *testing.T, testStr string, args []string) string {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"run attach test is not yet implemented on Windows\")\n\t}\n\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\tcontainerName := testutil.Identifier(t)\n\n\tfullArgs := []string{\"run\"}\n\tfullArgs = append(fullArgs, args...)\n\tfullArgs = append(fullArgs,\n\t\t\"--name\",\n\t\tcontainerName,\n\t\ttestutil.CommonImage,\n\t\t\"sh\",\n\t\t\"-euxc\",\n\t\t\"echo \"+testStr,\n\t)\n\n\tdefer base.Cmd(\"rm\", \"-f\", containerName).AssertOK()\n\tresult := base.Cmd(fullArgs...).Run()\n\n\treturn result.Combined()\n}\n\nfunc TestRunAttachFlag(t *testing.T) {\n\n\ttype testCase struct {\n\t\tname        string\n\t\targs        []string\n\t\ttestFunc    func(t *testing.T, testStr string, args []string) string\n\t\ttestStr     string\n\t\texpectedOut string\n\t\tdockerOut   string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:        \"AttachFlagStdin\",\n\t\t\targs:        []string{\"-a\", \"STDIN\", \"-a\", \"STDOUT\"},\n\t\t\ttestFunc:    runAttachStdin,\n\t\t\ttestStr:     \"test-run-stdio\",\n\t\t\texpectedOut: \"test-run-stdio\",\n\t\t\tdockerOut:   \"test-run-stdio\",\n\t\t},\n\t\t{\n\t\t\tname:        \"AttachFlagStdOut\",\n\t\t\targs:        []string{\"-a\", \"STDOUT\"},\n\t\t\ttestFunc:    runAttach,\n\t\t\ttestStr:     \"foo\",\n\t\t\texpectedOut: \"foo\",\n\t\t\tdockerOut:   \"foo\",\n\t\t},\n\t\t{\n\t\t\tname:        \"AttachFlagMixedValue\",\n\t\t\targs:        []string{\"-a\", \"STDIN\", \"-a\", \"invalid-value\"},\n\t\t\ttestFunc:    runAttach,\n\t\t\ttestStr:     \"foo\",\n\t\t\texpectedOut: \"invalid stream specified with -a flag. Valid streams are STDIN, STDOUT, and STDERR\",\n\t\t\tdockerOut:   \"valid streams are STDIN, STDOUT and STDERR\",\n\t\t},\n\t\t{\n\t\t\tname:        \"AttachFlagInvalidValue\",\n\t\t\targs:        []string{\"-a\", \"invalid-stream\"},\n\t\t\ttestFunc:    runAttach,\n\t\t\ttestStr:     \"foo\",\n\t\t\texpectedOut: \"invalid stream specified with -a flag. Valid streams are STDIN, STDOUT, and STDERR\",\n\t\t\tdockerOut:   \"valid streams are STDIN, STDOUT and STDERR\",\n\t\t},\n\t\t{\n\t\t\tname:        \"AttachFlagCaseInsensitive\",\n\t\t\targs:        []string{\"-a\", \"stdin\", \"-a\", \"stdout\"},\n\t\t\ttestFunc:    runAttachStdin,\n\t\t\ttestStr:     \"test-run-stdio\",\n\t\t\texpectedOut: \"test-run-stdio\",\n\t\t\tdockerOut:   \"test-run-stdio\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactualOut := tc.testFunc(t, tc.testStr, tc.args)\n\t\t\terrorMsg := fmt.Sprintf(\"%s failed;\\nExpected: '%s'\\nActual: '%s'\", tc.name, tc.expectedOut, actualOut)\n\t\t\tif nerdtest.IsDocker() {\n\t\t\t\tassert.Equal(t, true, strings.Contains(actualOut, tc.dockerOut), errorMsg)\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, true, strings.Contains(actualOut, tc.expectedOut), errorMsg)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRunQuiet(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\n\tteardown := func() {\n\t\tbase.Cmd(\"rmi\", \"-f\", testutil.CommonImage).Run()\n\t}\n\tdefer teardown()\n\tteardown()\n\n\tsentinel := \"test run quiet\"\n\tresult := base.Cmd(\"run\", \"--rm\", \"--quiet\", testutil.CommonImage, fmt.Sprintf(`echo \"%s\"`, sentinel)).Run()\n\tassert.Assert(t, strings.Contains(result.Combined(), sentinel))\n\n\twasQuiet := func(output, sentinel string) bool {\n\t\treturn !strings.Contains(output, sentinel)\n\t}\n\n\t// Docker and nerdctl image pulls are not 1:1.\n\tif nerdtest.IsDocker() {\n\t\tsentinel = \"Pull complete\"\n\t} else {\n\t\tsentinel = \"resolved\"\n\t}\n\n\tassert.Assert(t, wasQuiet(result.Combined(), sentinel), \"Found %s in container run output\", sentinel)\n}\n\nfunc TestRunFromOCIArchive(t *testing.T) {\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\n\t// Docker does not support running container images from OCI archive.\n\ttestutil.DockerIncompatible(t)\n\n\tbase := testutil.NewBase(t)\n\timageName := testutil.Identifier(t)\n\n\tteardown := func() {\n\t\tbase.Cmd(\"rmi\", \"-f\", imageName).Run()\n\t}\n\tdefer teardown()\n\tteardown()\n\n\tconst sentinel = \"test-nerdctl-run-from-oci-archive\"\n\tdockerfile := fmt.Sprintf(`FROM %s\n\tCMD [\"echo\", \"%s\"]`, testutil.CommonImage, sentinel)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\ttag := fmt.Sprintf(\"%s:latest\", imageName)\n\ttarPath := fmt.Sprintf(\"%s/%s.tar\", buildCtx, imageName)\n\n\tbase.Cmd(\"build\", \"--tag\", tag, fmt.Sprintf(\"--output=type=oci,dest=%s\", tarPath), buildCtx).AssertOK()\n\tbase.Cmd(\"run\", \"--rm\", fmt.Sprintf(\"oci-archive://%s\", tarPath)).AssertOutContainsAll(tag, sentinel)\n}\n\nfunc TestRunDomainname(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"run --hostname not implemented on Windows yet\")\n\t}\n\n\ttestCases := []struct {\n\t\tname        string\n\t\thostname    string\n\t\tdomainname  string\n\t\tCmd         string\n\t\tCmdFlag     string\n\t\texpectedOut string\n\t}{\n\t\t{\n\t\t\tname:        \"Check domain name\",\n\t\t\thostname:    \"foobar\",\n\t\t\tdomainname:  \"example.com\",\n\t\t\tCmd:         \"hostname\",\n\t\t\tCmdFlag:     \"-d\",\n\t\t\texpectedOut: \"example.com\",\n\t\t},\n\t\t{\n\t\t\tname:        \"check fqdn\",\n\t\t\thostname:    \"foobar\",\n\t\t\tdomainname:  \"example.com\",\n\t\t\tCmd:         \"hostname\",\n\t\t\tCmdFlag:     \"-f\",\n\t\t\texpectedOut: \"foobar.example.com\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tbase := testutil.NewBase(t)\n\n\t\t\tbase.Cmd(\"run\",\n\t\t\t\t\"--rm\",\n\t\t\t\t\"--hostname\", tc.hostname,\n\t\t\t\t\"--domainname\", tc.domainname,\n\t\t\t\ttestutil.CommonImage,\n\t\t\t\ttc.Cmd,\n\t\t\t\ttc.CmdFlag,\n\t\t\t).AssertOutContains(tc.expectedOut)\n\t\t})\n\t}\n}\n\nfunc TestRunHealthcheckFlags(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"healthcheck tests are skipped in rootless environment\")\n\t}\n\ttestCase := nerdtest.Setup()\n\n\ttestCases := []struct {\n\t\tname              string\n\t\targs              []string\n\t\tshouldFail        bool\n\t\texpectTest        []string\n\t\texpectRetries     int\n\t\texpectInterval    time.Duration\n\t\texpectTimeout     time.Duration\n\t\texpectStartPeriod time.Duration\n\t}{\n\t\t{\n\t\t\tname: \"Valid_full_config\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"curl -f http://localhost || exit 1\",\n\t\t\t\t\"--health-interval\", \"30s\",\n\t\t\t\t\"--health-timeout\", \"5s\",\n\t\t\t\t\"--health-retries\", \"3\",\n\t\t\t\t\"--health-start-period\", \"2s\",\n\t\t\t},\n\t\t\texpectTest:        []string{\"CMD-SHELL\", \"curl -f http://localhost || exit 1\"},\n\t\t\texpectInterval:    30 * time.Second,\n\t\t\texpectTimeout:     5 * time.Second,\n\t\t\texpectRetries:     3,\n\t\t\texpectStartPeriod: 2 * time.Second,\n\t\t},\n\t\t{\n\t\t\tname: \"No_healthcheck\",\n\t\t\targs: []string{\n\t\t\t\t\"--no-healthcheck\",\n\t\t\t},\n\t\t\texpectTest: []string{\"NONE\"},\n\t\t},\n\t\t{\n\t\t\tname:       \"No_healthcheck_flag\",\n\t\t\targs:       []string{},\n\t\t\texpectTest: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"Conflicting_flags\",\n\t\t\targs: []string{\n\t\t\t\t\"--no-healthcheck\", \"--health-cmd\", \"true\",\n\t\t\t},\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Negative_retries\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"true\",\n\t\t\t\t\"--health-retries\", \"-2\",\n\t\t\t},\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Negative_timeout\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"true\",\n\t\t\t\t\"--health-timeout\", \"-5s\",\n\t\t\t},\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid_timeout_format\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"true\",\n\t\t\t\t\"--health-timeout\", \"5blah\",\n\t\t\t},\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Health_cmd_cmd_shell\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"curl -f http://localhost || exit 1\",\n\t\t\t},\n\t\t\texpectTest: []string{\"CMD-SHELL\", \"curl -f http://localhost || exit 1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Health_cmd_array_like\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"echo hello\",\n\t\t\t},\n\t\t\texpectTest: []string{\"CMD-SHELL\", \"echo hello\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Health_cmd_empty\",\n\t\t\targs: []string{\n\t\t\t\t\"--health-cmd\", \"\",\n\t\t\t\t\"--health-retries\", \"2\",\n\t\t\t},\n\t\t\texpectTest:    nil,\n\t\t\texpectRetries: 2,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\n\t\ttestCase.SubTests = append(testCase.SubTests, &test.Case{\n\t\t\tDescription: tc.name,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\targs := append([]string{\"run\", \"-d\", \"--name\", tc.name}, tc.args...)\n\t\t\t\targs = append(args, testutil.CommonImage, \"sleep\", \"infinity\")\n\t\t\t\treturn helpers.Command(args...)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tif tc.shouldFail {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: expect.ExitCodeGenericFail,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, tc.name)\n\t\t\t\t\t\t\thc := inspect.Config.Healthcheck\n\t\t\t\t\t\t\tif tc.expectTest == nil {\n\t\t\t\t\t\t\t\tassert.Assert(t, hc == nil || len(hc.Test) == 0)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassert.Assert(t, hc != nil)\n\t\t\t\t\t\t\t\tassert.DeepEqual(t, hc.Test, tc.expectTest)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif tc.expectRetries > 0 {\n\t\t\t\t\t\t\t\tassert.Equal(t, hc.Retries, tc.expectRetries)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif tc.expectTimeout > 0 {\n\t\t\t\t\t\t\t\tassert.Equal(t, hc.Timeout, tc.expectTimeout)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif tc.expectInterval > 0 {\n\t\t\t\t\t\t\t\tassert.Equal(t, hc.Interval, tc.expectInterval)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif tc.expectStartPeriod > 0 {\n\t\t\t\t\t\t\t\tassert.Equal(t, hc.StartPeriod, tc.expectStartPeriod)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", tc.name)\n\t\t\t},\n\t\t})\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRunHealthcheckFromImage(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"healthcheck tests are skipped in rootless environment\")\n\t}\n\tnerdtest.Setup()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nHEALTHCHECK --interval=30s --timeout=10s CMD wget -q --spider http://localhost:8080 || exit 1\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"image\", data.Identifier())\n\t\t\thelpers.Ensure(\"build\", \"-t\", data.Labels().Get(\"image\"), data.Temp().Path())\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"merge_with_image\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\t\"--health-retries=5\",\n\t\t\t\t\t\t\"--health-interval=45s\",\n\t\t\t\t\t\tdata.Labels().Get(\"image\"))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\t\thc := inspect.Config.Healthcheck\n\t\t\t\t\t\t\tassert.Assert(t, hc != nil, \"expected healthcheck config to be present\")\n\t\t\t\t\t\t\tassert.DeepEqual(t, hc.Test, []string{\"CMD-SHELL\", \"wget -q --spider http://localhost:8080 || exit 1\"})\n\t\t\t\t\t\t\tassert.Equal(t, 5, hc.Retries)               // From CLI flags\n\t\t\t\t\t\t\tassert.Equal(t, 45*time.Second, hc.Interval) // From CLI flags\n\t\t\t\t\t\t\tassert.Equal(t, 10*time.Second, hc.Timeout)  // From Dockerfile\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Disable image health checks via runtime flag\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\t\"run\", \"-d\", \"--name\", data.Identifier(),\n\t\t\t\t\t\t\"--no-healthcheck\",\n\t\t\t\t\t\tdata.Labels().Get(\"image\"),\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\t\tOutput: expect.All(func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\t\t\thc := inspect.Config.Healthcheck\n\t\t\t\t\t\t\tassert.Assert(t, hc != nil, \"expected healthcheck config to be present\")\n\t\t\t\t\t\t\tassert.DeepEqual(t, hc.Test, []string{\"NONE\"})\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc countFIFOFiles(root string) (int, error) {\n\tcount := 0\n\terr := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.Mode()&os.ModeNamedPipe != 0 {\n\t\t\tcount++\n\t\t}\n\t\treturn nil\n\t})\n\treturn count, err\n}\nfunc TestCleanupFIFOs(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"/run/containerd/fifo/ doesn't exist on rootless\")\n\t}\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\ttestutil.DockerIncompatible(t)\n\ttestCase := nerdtest.Setup()\n\ttestCase.NoParallel = true\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcmd := helpers.Command(\"run\", \"-it\", \"--rm\", testutil.CommonImage, \"date\")\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t})\n\t\toldNumFifos, err := countFIFOFiles(\"/run/containerd/fifo/\")\n\t\tassert.NilError(t, err)\n\n\t\tcmd = helpers.Command(\"run\", \"-it\", \"--rm\", testutil.CommonImage, \"date\")\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t})\n\t\tnewNumFifos, err := countFIFOFiles(\"/run/containerd/fifo/\")\n\t\tassert.NilError(t, err)\n\t\tassert.Equal(t, oldNumFifos, newNumFifos)\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_user_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunUserGID(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Test container run as default user (root) and verify root belongs to standard system groups\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"root bin daemon sys adm disk wheel floppy dialout tape video\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run with numeric UID (1000) and verify it resolves to root group inside the container\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1000\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"root\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as user (guest) and verify group membership is resolved correctly\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"guest\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"users\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run with well-known user 'nobody' and verify it belongs to the 'nobody' group\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"nobody\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"nobody\")),\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestRunUmask(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--umask\", \"0200\", testutil.AlpineImage, \"sh\", \"-c\", \"umask\")\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"0200\"))\n\ttestCase.Run(t)\n}\n\nfunc TestRunAddGroup(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Test container run as default root user and its inherited system groups\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"root bin daemon sys adm disk wheel floppy dialout tape video\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as numeric UID only and its fallback to root group\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1000\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"root\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as numeric UID with extra group addition\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1000\", \"--group-add\", \"nogroup\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"root nogroup\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as UID:GID pair with extra group addition\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1000:wheel\", \"--group-add\", \"nogroup\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"wheel nogroup\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as root with extra group addition and system group persistence\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"root\", \"--group-add\", \"nogroup\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"root bin daemon sys adm disk wheel floppy dialout tape video nogroup\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as root:group override and its effect on supplementary groups\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"root:nogroup\", \"--group-add\", \"nogroup\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nogroup\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as named non-root user with multiple group additions\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"guest\", \"--group-add\", \"root\", \"--group-add\", \"nogroup\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"users root nogroup\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as named user:group with numeric GID resolution\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"guest:nogroup\", \"--group-add\", \"0\", testutil.AlpineImage, \"id\", \"-nG\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Equals(\"nogroup root\\n\")),\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\n// TestRunAddGroup_CVE_2023_25173 tests https://github.com/advisories/GHSA-hmfx-3pcx-653p\n//\n// Equates to https://github.com/containerd/containerd/commit/286a01f350a2298b4fdd7e2a0b31c04db3937ea8\nfunc TestRunAddGroup_CVE_2023_25173(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t}\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Test container run as default root user\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=0(root),10(wheel)\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as root with additional groups\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--group-add\", \"1\", \"--group-add\", \"1234\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=0(root),1(daemon),10(wheel),1234\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as custom UID with inherited root group\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1234\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=0(root)\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as custom UID and GID pair\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1234:1234\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=1234\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as custom UID with explicit group add\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"1234\", \"--group-add\", \"1234\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=0(root),1234\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as named non-root user (daemon)\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"daemon\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=1(daemon)\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test container run as named user with extra groups\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"daemon\", \"--group-add\", \"1234\", testutil.BusyboxImage, \"id\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"groups=1(daemon),1234\\n\")),\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestUsernsMappingRunCmd(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.AllowModifyUserns,\n\t\t\tnerdtest.RemapIDs,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Labels().Set(\"validUserns\", \"nerdctltestuser\")\n\t\t\tdata.Labels().Set(\"expectedHostUID\", \"123456789\")\n\t\t\tdata.Labels().Set(\"validUid\", \"123456789\")\n\t\t\tdata.Labels().Set(\"net-container\", \"net-container\")\n\t\t\tdata.Labels().Set(\"invalidUserns\", \"invaliduser\")\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with valid Userns format userns username\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tactualHostUID, err := getContainerHostUID(helpers, data.Identifier())\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Failed to get container host UID: %v\", err))\n\t\t\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, actualHostUID == data.Labels().Get(\"expectedHostUID\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with valid Userns  --userns uid\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"validUid\"), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tactualHostUID, err := getContainerHostUID(helpers, data.Identifier())\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Failed to get container host UID: %v\", err))\n\t\t\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, actualHostUID == data.Labels().Get(\"expectedHostUID\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run failure with valid Userns and privileged flag\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"--privileged\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with valid Userns format --userns <username>:<groupname>\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", fmt.Sprintf(\"%s:%s\", data.Labels().Get(\"validUserns\"), data.Labels().Get(\"validUserns\")), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tactualHostUID, err := getContainerHostUID(helpers, data.Identifier())\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Failed to get container host UID: %v\", err))\n\t\t\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, actualHostUID == data.Labels().Get(\"expectedHostUID\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with valid Userns  --userns uid:gid\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", fmt.Sprintf(\"%s:%s\", data.Labels().Get(\"validUid\"), data.Labels().Get(\"validUid\")), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tactualHostUID, err := getContainerHostUID(helpers, data.Identifier())\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Failed to get container host UID: %v\", err))\n\t\t\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, actualHostUID == data.Labels().Get(\"expectedHostUID\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container network share with valid Userns\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t\thelpers.Ensure(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--name\", data.Labels().Get(\"net-container\"), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"net-container\"))\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--net\", fmt.Sprintf(\"container:%s\", data.Labels().Get(\"net-container\")), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with valid Userns with override --userns=host\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--userns\", \"host\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tactualHostUID, err := getContainerHostUID(helpers, data.Identifier())\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tt.Log(fmt.Sprintf(\"Failed to get container host UID: %v\", err))\n\t\t\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, actualHostUID == \"0\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with valid Userns with invalid overrid --userns=hostinvalid\",\n\t\t\t\tNoParallel:  true, // Changes system config so running in non parallel mode\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\terr := appendUsernsConfig(data.Labels().Get(\"validUserns\"), data.Labels().Get(\"expectedHostUID\"), helpers)\n\t\t\t\t\tassert.NilError(t, err, \"Failed to append Userns config\")\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\tremoveUsernsConfig(t, data.Labels().Get(\"validUserns\"), helpers)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"validUserns\"), \"--userns\", \"hostinvalid\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Test container run with invalid Userns\",\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--tty\", \"-d\", \"--userns-remap\", data.Labels().Get(\"invalidUserns\"), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", \"inf\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_user_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunUserName(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"should run Windows container as ContainerAdministrator by default\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", testutil.WindowsNano, \"whoami\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"ContainerAdministrator\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should run Windows container as ContainerAdministrator when user is set to ContainerAdministrator\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"ContainerAdministrator\", testutil.WindowsNano, \"whoami\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"ContainerAdministrator\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should run Windows container as ContainerUser when user is set to ContainerUser\",\n\t\t\tCommand:     test.Command(\"run\", \"--rm\", \"--user\", \"ContainerUser\", testutil.WindowsNano, \"whoami\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"ContainerUser\")),\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_verify_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestRunVerifyCosign(t *testing.T) {\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := nerdtest.Setup()\n\n\tvar reg *registry.Server\n\n\ttestCase.Require = require.All(\n\t\trequire.Binary(\"cosign\"),\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.Build,\n\t\tnerdtest.Registry,\n\t)\n\n\ttestCase.Env[\"COSIGN_PASSWORD\"] = \"1\"\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\tpri, pub := nerdtest.GenerateCosignKeyPair(data, helpers, \"1\")\n\t\treg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)\n\t\treg.Setup(data, helpers)\n\n\t\ttestImageRef := fmt.Sprintf(\"127.0.0.1:%d/%s\", reg.Port, data.Identifier(\"push-cosign-image\"))\n\t\thelpers.Ensure(\"build\", \"-t\", testImageRef, data.Temp().Path())\n\t\thelpers.Ensure(\"push\", testImageRef, \"--sign=cosign\", \"--cosign-key=\"+pri)\n\n\t\tdata.Labels().Set(\"public_key\", pub)\n\t\tdata.Labels().Set(\"image_ref\", testImageRef)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif reg != nil {\n\t\t\treg.Cleanup(data, helpers)\n\t\t}\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\thelpers.Fail(\n\t\t\t\"run\", \"--rm\", \"--verify=cosign\",\n\t\t\t\"--cosign-key=dummy\",\n\t\t\tdata.Labels().Get(\"image_ref\"))\n\n\t\treturn helpers.Command(\n\t\t\t\"run\", \"--rm\", \"--verify=cosign\",\n\t\t\t\"--cosign-key=\"+data.Labels().Get(\"public_key\"),\n\t\t\tdata.Labels().Get(\"image_ref\"))\n\t}\n\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil)\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_run_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRunHostProcessContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thostname, err := exec.Command(\"hostname\").Output()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unable to get hostname: %s\", err)\n\t\t}\n\t\tdata.Labels().Set(\"hostname\", string(bytes.TrimSpace(hostname)))\n\n\t\twhoami := helpers.Capture(\"run\", \"--rm\", \"--isolation=host\", testutil.WindowsNano, \"whoami\")\n\t\tt.Logf(\"whoami %s\", whoami)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"run\", \"--rm\", \"--isolation=host\", testutil.WindowsNano, \"hostname\")\n\t}\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(data.Labels().Get(\"hostname\")))(data, helpers)\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestRunHostProcessContainerAsUser(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--isolation=host\", \"-u\", \"NT AUTHORITY\\\\SYSTEM\", testutil.WindowsNano, \"whoami\")\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"nt authority\\\\system\"))\n\ttestCase.Run(t)\n}\n\nfunc TestRunHostProcessContainerAsLocalService(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--isolation=host\", \"-u\", \"NT AUTHORITY\\\\Local Service\", testutil.WindowsNano, \"whoami\")\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"nt authority\\\\local service\"))\n\ttestCase.Run(t)\n}\n\nfunc TestRunHostProcessContainerAsNetworkService(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Command = test.Command(\"run\", \"--rm\", \"--isolation=host\", \"-u\", \"NT AUTHORITY\\\\Network Service\", testutil.WindowsNano, \"whoami\")\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"nt authority\\\\network service\"))\n\ttestCase.Run(t)\n}\n\nfunc TestRunProcessIsolated(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"containeruser\", \"ContainerUser\")\n\t}\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"run\", \"--rm\", \"--isolation=process\", \"-u\", data.Labels().Get(\"containeruser\"), testutil.WindowsNano, \"whoami\")\n\t}\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(data.Labels().Get(\"containeruser\")))(data, helpers)\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestRunHyperVContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.HyperV,\n\t)\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// hyperv must not be in the name for this test, the output is parsed for it\n\t\tcontainerName := \"nerdctl-testwcowcontainer\"\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\thelpers.Ensure(\"run\", \"--isolation\", \"hyperv\", \"--name\", containerName, testutil.WindowsNano)\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Labels().Get(\"containerName\"))\n\t}\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"container\", \"inspect\", \"--mode\", \"native\", data.Labels().Get(\"containerName\"))\n\t}\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"hyperv\"))(data, helpers)\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestRunProcessContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"--isolation\", \"process\", \"--name\", data.Identifier(), testutil.WindowsNano)\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"container\", \"inspect\", \"--mode\", \"native\", data.Identifier())\n\t}\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.DoesNotContain(\"hyperv\"))\n\ttestCase.Run(t)\n}\n\n// Note that the current implementation of this test is not ideal, since it relies on internal HCS details that\n// Microsoft could decide to change in the future (breaking both this unit test and the one in containerd itself):\n// https://github.com/containerd/containerd/pull/6618#discussion_r823302852\nfunc TestRunProcessContainerWithDevice(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\ttestCase.Command = test.Command(\n\t\t\"run\",\n\t\t\"--rm\",\n\t\t\"--isolation=process\",\n\t\t\"--device\", \"class://5B45201D-F2F2-4F3B-85BB-30FF1F953599\",\n\t\ttestutil.WindowsNano,\n\t\t\"cmd\", \"/S\", \"/C\", \"dir C:\\\\Windows\\\\System32\\\\HostDriverStore\",\n\t)\n\ttestCase.Expected = test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(\"FileRepository\"))\n\ttestCase.Run(t)\n}\n\nfunc TestRunWithTtyAndDetached(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// This test is currently disabled, as it is failing most of the time.\n\ttestCase.Require = nerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/3437\")\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// with -t, success, the container should run with tty support.\n\t\thelpers.Ensure(\"run\", \"-d\", \"-t\", \"--name\", data.Identifier(\"with-terminal\"), testutil.CommonImage, \"cmd\", \"/c\", \"echo\", \"Hello, World with TTY!\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", data.Identifier(\"with-terminal\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\twithTtyContainer := nerdtest.InspectContainer(helpers, data.Identifier(\"with-terminal\"))\n\t\tassert.Equal(helpers.T(), 0, withTtyContainer.State.ExitCode)\n\t\treturn helpers.Command(\"logs\", data.Identifier(\"with-terminal\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Contains(\"Hello, World with TTY!\"))\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_start.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n)\n\nfunc StartCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"start [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Start one or more running containers\",\n\t\tRunE:              startAction,\n\t\tValidArgsFunction: startShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\tcmd.Flags().SetInterspersed(false)\n\tcmd.Flags().BoolP(\"attach\", \"a\", false, \"Attach STDOUT/STDERR and forward signals\")\n\tcmd.Flags().String(\"detach-keys\", consoleutil.DefaultDetachKeys, \"Override the default detach keys\")\n\tcmd.Flags().BoolP(\"interactive\", \"i\", false, \"Attach container's STDIN\")\n\tcmd.Flags().String(\"checkpoint\", \"\", \"checkpoint name\")\n\tcmd.Flags().String(\"checkpoint-dir\", \"\", \"checkpoint directory\")\n\treturn cmd\n}\n\nfunc startOptions(cmd *cobra.Command) (types.ContainerStartOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerStartOptions{}, err\n\t}\n\tattach, err := cmd.Flags().GetBool(\"attach\")\n\tif err != nil {\n\t\treturn types.ContainerStartOptions{}, err\n\t}\n\tdetachKeys, err := cmd.Flags().GetString(\"detach-keys\")\n\tif err != nil {\n\t\treturn types.ContainerStartOptions{}, err\n\t}\n\tinteractive, err := cmd.Flags().GetBool(\"interactive\")\n\tif err != nil {\n\t\treturn types.ContainerStartOptions{}, err\n\t}\n\tcheckpoint, err := cmd.Flags().GetString(\"checkpoint\")\n\tif err != nil {\n\t\treturn types.ContainerStartOptions{}, err\n\t}\n\tcheckpointDir, err := cmd.Flags().GetString(\"checkpoint-dir\")\n\tif err != nil {\n\t\treturn types.ContainerStartOptions{}, err\n\t}\n\treturn types.ContainerStartOptions{\n\t\tStdout:        cmd.OutOrStdout(),\n\t\tGOptions:      globalOptions,\n\t\tAttach:        attach,\n\t\tDetachKeys:    detachKeys,\n\t\tInteractive:   interactive,\n\t\tCheckpoint:    checkpoint,\n\t\tCheckpointDir: checkpointDir,\n\t}, nil\n}\n\nfunc startAction(cmd *cobra.Command, args []string) error {\n\toptions, err := startOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toptions.NerdctlCmd, options.NerdctlArgs = helpers.GlobalFlags(cmd)\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Start(ctx, client, args, options)\n}\n\nfunc startShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show non-running container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st != containerd.Running && st != containerd.Unknown\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_start_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestStartDetachKeys(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcmd := helpers.Command(\"run\", \"-it\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.Feed(strings.NewReader(\"exit\\n\"))\n\t\tcmd.Run(&test.Expected{\n\t\t\tExitCode: 0,\n\t\t})\n\t\tassert.Assert(t,\n\t\t\tstrings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":false\"),\n\t\t)\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcmd := helpers.Command(\"start\", \"-ai\", \"--detach-keys=ctrl-a,ctrl-b\", data.Identifier())\n\t\tcmd.WithPseudoTTY()\n\t\tcmd.WithFeeder(func() io.Reader {\n\t\t\t// ctrl+a and ctrl+b (see https://en.wikipedia.org/wiki/C0_and_C1_control_codes)\n\t\t\treturn bytes.NewReader([]byte{1, 2})\n\t\t})\n\n\t\treturn cmd\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tErrors:   []error{errors.New(\"detach keys\")},\n\t\t\tOutput: expect.All(\n\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"inspect\", \"--format\", \"json\", data.Identifier()), \"\\\"Running\\\":true\"))\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestStartWithCheckpoint(t *testing.T) {\n\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.All(\n\t\trequire.Not(nerdtest.Rootless),\n\t\t// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.\n\t\t// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.\n\t\trequire.Not(nerdtest.Docker),\n\t)\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// Use an in-memory tmpfs to model in-memory state without introducing extra processes\n\t\t// Single PID 1 shell: continuously increment a counter and write to /state/counter (tmpfs)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), \"--tmpfs\", \"/state\", testutil.CommonImage,\n\t\t\t\"sh\", \"-c\", `i=0; while true; do i=$((i+1)); printf \"%d\\n\" \"$i\" >/state/counter; sleep 0.2; done`)\n\t\t// Give some time for the counter to increase before checkpoint to validate continuity after restore\n\t\ttime.Sleep(1 * time.Second)\n\t\thelpers.Ensure(\"checkpoint\", \"create\", data.Identifier(), data.Identifier()+\"-checkpoint\")\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"start\", \"--checkpoint\", data.Identifier()+\"-checkpoint\", data.Identifier())\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tExitCode: 0,\n\t\t\tOutput: expect.All(\n\t\t\t\tfunc(_ string, t tig.T) {\n\t\t\t\t\t// Validate in-memory state continuity via tmpfs: counter should not reset and must keep increasing\n\t\t\t\t\t// Short delay to allow the container to resume; if the counter had reset to 0, it could not reach >5 this fast\n\t\t\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t\t\t\tc1Str := strings.TrimSpace(helpers.Capture(\"exec\", data.Identifier(), \"cat\", \"/state/counter\"))\n\t\t\t\t\tvar parseErrs []error\n\t\t\t\t\tc1, err1 := strconv.Atoi(c1Str)\n\t\t\t\t\tif err1 != nil {\n\t\t\t\t\t\tparseErrs = append(parseErrs, err1)\n\t\t\t\t\t}\n\t\t\t\t\tassert.Assert(t, len(parseErrs) == 0, \"failed to parse counter values: %v\", parseErrs)\n\t\t\t\t\tassert.Assert(t, c1 > 5, \"tmpfs in-memory counter seems reset or too small: %d\", c1)\n\t\t\t\t},\n\t\t\t),\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_start_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestStart(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\", \"-d\",\n\t\t\t\t\"--name\", data.Identifier(),\n\t\t\t\ttestutil.CommonImage)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"start\", data.Identifier())\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn test.Expects(0, nil, expect.Contains(data.Identifier()))(data, helpers)\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n\nfunc TestStartAttach(t *testing.T) {\n\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"run\",\n\t\t\t\t\"--name\", data.Identifier(),\n\t\t\t\ttestutil.CommonImage, \"sh\", \"-euxc\", \"echo foo\")\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"start\", \"-a\", data.Identifier())\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn test.Expects(0, nil, expect.Contains(\"foo\"))(data, helpers)\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_stats.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc StatsCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"stats\",\n\t\tShort:             \"Display a live stream of container(s) resource usage statistics.\",\n\t\tRunE:              statsAction,\n\t\tValidArgsFunction: statsShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\taddStatsFlags(cmd)\n\n\treturn cmd\n}\n\nfunc addStatsFlags(cmd *cobra.Command) {\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Show all containers (default shows just running)\")\n\tcmd.Flags().String(\"format\", \"\", \"Pretty-print images using a Go template, e.g, '{{json .}}'\")\n\tcmd.Flags().Bool(\"no-stream\", false, \"Disable streaming stats and only pull the first result\")\n\tcmd.Flags().Bool(\"no-trunc\", false, \"Do not truncate output\")\n}\n\nfunc processStatsCommandFlags(cmd *cobra.Command) (types.ContainerStatsOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerStatsOptions{}, err\n\t}\n\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn types.ContainerStatsOptions{}, err\n\t}\n\n\tnoStream, err := cmd.Flags().GetBool(\"no-stream\")\n\tif err != nil {\n\t\treturn types.ContainerStatsOptions{}, err\n\t}\n\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.ContainerStatsOptions{}, err\n\t}\n\n\tnoTrunc, err := cmd.Flags().GetBool(\"no-trunc\")\n\tif err != nil {\n\t\treturn types.ContainerStatsOptions{}, err\n\t}\n\n\treturn types.ContainerStatsOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tStderr:   cmd.ErrOrStderr(),\n\t\tGOptions: globalOptions,\n\t\tAll:      all,\n\t\tFormat:   format,\n\t\tNoStream: noStream,\n\t\tNoTrunc:  noTrunc,\n\t}, nil\n}\n\nfunc statsAction(cmd *cobra.Command, args []string) error {\n\toptions, err := processStatsCommandFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Stats(ctx, client, args, options)\n}\n\nfunc statsShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show running container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st == containerd.Running\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_stats_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestStats(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// FIXME: does not seem to work on windows\n\ttestCase.Require = require.Not(require.Windows)\n\n\tif runtime.GOOS == \"linux\" {\n\t\t// this comment is for `nerdctl ps` but it also valid for `nerdctl stats` :\n\t\t// https://github.com/containerd/nerdctl/pull/223#issuecomment-851395178\n\t\ttestCase.Require = require.All(\n\t\t\ttestCase.Require,\n\t\t\tnerdtest.CgroupsAccessible,\n\t\t)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"memlimited\"))\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"exited\"))\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"container\"), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"memlimited\"), \"--memory\", \"1g\", testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(\"exited\"), testutil.CommonImage, \"echo\", \"'exited'\")\n\t\tdata.Labels().Set(\"id\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"stats\",\n\t\t\tCommand:     test.Command(\"stats\", \"--no-stream\", \"--no-trunc\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"id\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"container stats\",\n\t\t\tCommand:     test.Command(\"container\", \"stats\", \"--no-stream\", \"--no-trunc\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"id\")),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"stats ID\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"stats\", \"--no-stream\", data.Labels().Get(\"id\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"container stats ID\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"container\", \"stats\", \"--no-stream\", data.Labels().Get(\"id\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"no mem limit set\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"stats\", \"--no-stream\")\n\t\t\t},\n\t\t\t// https://github.com/containerd/nerdctl/issues/1240\n\t\t\t// nerdctl used to print UINT64_MAX as the memory limit, so, ensure it does no more\n\t\t\tExpected: test.Expects(0, nil, expect.DoesNotContain(\"16EiB\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"mem limit set\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"stats\", \"--no-stream\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"1GiB\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_stop.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc StopCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"stop [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Stop one or more running containers\",\n\t\tRunE:              stopAction,\n\t\tValidArgsFunction: stopShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().IntP(\"time\", \"t\", 10, \"Seconds to wait before sending a SIGKILL\")\n\tcmd.Flags().StringP(\"signal\", \"s\", \"SIGTERM\", \"Signal to send to the container\")\n\treturn cmd\n}\n\nfunc stopOptions(cmd *cobra.Command) (types.ContainerStopOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerStopOptions{}, err\n\t}\n\tvar timeout *time.Duration\n\tif cmd.Flags().Changed(\"time\") {\n\t\ttimeValue, err := cmd.Flags().GetInt(\"time\")\n\t\tif err != nil {\n\t\t\treturn types.ContainerStopOptions{}, err\n\t\t}\n\t\tt := time.Duration(timeValue) * time.Second\n\t\ttimeout = &t\n\t}\n\tvar signal string\n\tif cmd.Flags().Changed(\"signal\") {\n\t\tsignalValue, err := cmd.Flags().GetString(\"signal\")\n\t\tif err != nil {\n\t\t\treturn types.ContainerStopOptions{}, err\n\t\t}\n\t\tsignal = signalValue\n\t}\n\treturn types.ContainerStopOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tStderr:   cmd.ErrOrStderr(),\n\t\tGOptions: globalOptions,\n\t\tTimeout:  timeout,\n\t\tSignal:   signal,\n\t}, nil\n}\n\nfunc stopAction(cmd *cobra.Command, args []string) error {\n\toptions, err := stopOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Stop(ctx, client, args, options)\n}\n\nfunc stopShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show non-stopped container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st != containerd.Stopped && st != containerd.Created && st != containerd.Unknown\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_stop_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/coreos/go-iptables/iptables\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\tiptablesutil \"github.com/containerd/nerdctl/v2/pkg/testutil/iptables\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\nfunc TestStopStart(t *testing.T) {\n\tconst (\n\t\thostPort = 8080\n\t)\n\ttestContainerName := testutil.Identifier(t)\n\tbase := testutil.NewBase(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\n\tbase.Cmd(\"run\", \"-d\",\n\t\t\"--restart=no\",\n\t\t\"--name\", testContainerName,\n\t\t\"-p\", fmt.Sprintf(\"127.0.0.1:%d:80\", hostPort),\n\t\ttestutil.NginxAlpineImage).AssertOK()\n\n\tcheck := func(httpGetRetry int) error {\n\t\tresp, err := nettestutil.HTTPGet(fmt.Sprintf(\"http://127.0.0.1:%d\", hostPort), httpGetRetry, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) {\n\t\t\treturn fmt.Errorf(\"expected contain %q, got %q\",\n\t\t\t\ttestutil.NginxAlpineIndexHTMLSnippet, string(respBody))\n\t\t}\n\t\treturn nil\n\t}\n\n\tassert.NilError(t, check(5))\n\tbase.Cmd(\"stop\", testContainerName).AssertOK()\n\tbase.Cmd(\"exec\", testContainerName, \"ps\").AssertFail()\n\tif check(1) == nil {\n\t\tt.Fatal(\"expected to get an error\")\n\t}\n\tbase.Cmd(\"start\", testContainerName).AssertOK()\n\tassert.NilError(t, check(5))\n}\n\nfunc TestStopWithStopSignal(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tcmd := nerdtest.RunSigProxyContainer(nerdtest.SigQuit, false,\n\t\t\t[]string{\"--stop-signal\", nerdtest.SigQuit.String()}, data, helpers)\n\t\thelpers.Ensure(\"stop\", data.Identifier())\n\t\treturn cmd\n\t}\n\n\t// Verify that SIGQUIT was sent to the container AND that the container did forcefully exit\n\ttestCase.Expected = test.Expects(137, nil, expect.Contains(nerdtest.SignalCaught))\n\n\ttestCase.Run(t)\n}\n\nfunc TestStopCleanupForwards(t *testing.T) {\n\tconst (\n\t\thostPort          = 9999\n\t\ttestContainerName = \"ngx\"\n\t)\n\tbase := testutil.NewBase(t)\n\tdefer func() {\n\t\tbase.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\t}()\n\n\t// skip if rootless\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"pkg/testutil/iptables does not support rootless\")\n\t}\n\n\tipt, err := iptables.New()\n\tassert.NilError(t, err)\n\n\tcontainerID := base.Cmd(\"run\", \"-d\",\n\t\t\"--restart=no\",\n\t\t\"--name\", testContainerName,\n\t\t\"-p\", fmt.Sprintf(\"127.0.0.1:%d:80\", hostPort),\n\t\ttestutil.NginxAlpineImage).Run().Stdout()\n\tcontainerID = strings.TrimSuffix(containerID, \"\\n\")\n\n\tcontainerIP := base.Cmd(\"inspect\",\n\t\t\"-f\",\n\t\t\"'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'\",\n\t\ttestContainerName).Run().Stdout()\n\tcontainerIP = strings.ReplaceAll(containerIP, \"'\", \"\")\n\tcontainerIP = strings.TrimSuffix(containerIP, \"\\n\")\n\n\t// define iptables chain name depending on the target (docker/nerdctl)\n\tvar chain string\n\tif nerdtest.IsDocker() {\n\t\tchain = \"DOCKER\"\n\t} else {\n\t\tredirectChain := \"CNI-HOSTPORT-DNAT\"\n\t\tchain = iptablesutil.GetRedirectedChain(t, ipt, redirectChain, testutil.Namespace, containerID)\n\t}\n\tassert.Equal(t, iptablesutil.ForwardExists(t, ipt, chain, containerIP, hostPort), true)\n\n\tbase.Cmd(\"stop\", testContainerName).AssertOK()\n\tassert.Equal(t, iptablesutil.ForwardExists(t, ipt, chain, containerIP, hostPort), false)\n}\n\n// Regression test for https://github.com/containerd/nerdctl/issues/3353\nfunc TestStopCreated(t *testing.T) {\n\tt.Parallel()\n\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\n\ttearDown := func() {\n\t\tbase.Cmd(\"rm\", \"-f\", tID).Run()\n\t}\n\n\tsetup := func() {\n\t\tbase.Cmd(\"create\", \"--name\", tID, testutil.CommonImage).AssertOK()\n\t}\n\n\tt.Cleanup(tearDown)\n\ttearDown()\n\tsetup()\n\n\tbase.Cmd(\"stop\", tID).AssertOK()\n}\n\nfunc TestStopWithLongTimeoutAndSIGKILL(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttestContainerName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\n\t// Start a container that sleeps forever\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainerName, testutil.CommonImage, \"sleep\", \"Inf\").AssertOK()\n\n\t// Stop the container with a 5-second timeout and SIGKILL\n\tstart := time.Now()\n\tbase.Cmd(\"stop\", \"--time=5\", \"--signal\", \"SIGKILL\", testContainerName).AssertOK()\n\telapsed := time.Since(start)\n\n\t// The container should be stopped almost immediately, well before the 5-second timeout\n\tassert.Assert(t, elapsed < 5*time.Second, \"Container wasn't stopped immediately with SIGKILL\")\n}\n\nfunc TestStopWithTimeout(t *testing.T) {\n\tt.Parallel()\n\tbase := testutil.NewBase(t)\n\ttestContainerName := testutil.Identifier(t)\n\tdefer base.Cmd(\"rm\", \"-f\", testContainerName).Run()\n\n\t// Start a container that sleeps forever\n\tbase.Cmd(\"run\", \"-d\", \"--name\", testContainerName, testutil.CommonImage, \"sleep\", \"Inf\").AssertOK()\n\n\t// Stop the container with a 3-second timeout\n\tstart := time.Now()\n\tbase.Cmd(\"stop\", \"--time=3\", testContainerName).AssertOK()\n\telapsed := time.Since(start)\n\n\t// The container should get the SIGKILL before the 10s default timeout\n\tassert.Assert(t, elapsed < 10*time.Second, \"Container did not respect --timeout flag\")\n}\nfunc TestStopCleanupFIFOs(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"/run/containerd/fifo/ doesn't exist on rootless\")\n\t}\n\ttestutil.DockerIncompatible(t)\n\tbase := testutil.NewBase(t)\n\ttestContainerName := testutil.Identifier(t)\n\toldNumFifos, err := countFIFOFiles(\"/run/containerd/fifo/\")\n\tassert.NilError(t, err)\n\t// Stop the container after 2 seconds\n\tgo func() {\n\t\ttime.Sleep(2 * time.Second)\n\t\tbase.Cmd(\"stop\", testContainerName).AssertOK()\n\t\tnewNumFifos, err := countFIFOFiles(\"/run/containerd/fifo/\")\n\t\tassert.NilError(t, err)\n\t\tassert.Equal(t, oldNumFifos, newNumFifos)\n\t}()\n\t// Start a container that is automatically removed after it exits\n\tbase.Cmd(\"run\", \"--rm\", \"--name\", testContainerName, testutil.NginxAlpineImage).AssertOK()\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_top.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc TopCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"top CONTAINER [ps OPTIONS]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Display the running processes of a container\",\n\t\tRunE:              topAction,\n\t\tValidArgsFunction: topShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().SetInterspersed(false)\n\treturn cmd\n}\n\nfunc topAction(cmd *cobra.Command, args []string) error {\n\t// NOTE: rootless container does not rely on cgroupv1.\n\t// more details about possible ways to resolve this concern: #223\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif rootlessutil.IsRootless() && infoutil.CgroupsVersion() == \"1\" {\n\t\treturn fmt.Errorf(\"top requires cgroup v2 for rootless containers, see https://rootlesscontaine.rs/getting-started/common/cgroup2/\")\n\t}\n\n\tif globalOptions.CgroupManager == \"none\" {\n\t\treturn errors.New(\"cgroup manager must not be \\\"none\\\"\")\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tcontainerID := args[0]\n\tvar psArgs string\n\tif len(args) > 1 {\n\t\t// Join all remaining arguments as ps args\n\t\tpsArgs = strings.Join(args[1:], \" \")\n\t}\n\n\treturn container.Top(ctx, client, []string{containerID}, types.ContainerTopOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tPsArgs:   psArgs,\n\t})\n\n}\n\nfunc topShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show running container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st == containerd.Running\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_top_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestTop(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t//more details https://github.com/containerd/nerdctl/pull/223#issuecomment-851395178\n\tif runtime.GOOS == \"linux\" {\n\t\ttestCase.Require = nerdtest.CgroupsAccessible\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// FIXME: busybox 1.36 on windows still appears to not support sleep inf. Unclear why.\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tdata.Labels().Set(\"cID\", data.Identifier())\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with o pid,user,cmd\",\n\t\t\t// Docker does not support top -o\n\t\t\tRequire: require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"top\", data.Labels().Get(\"cID\"), \"-o\", \"pid,user,cmd\")\n\t\t\t},\n\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"simple\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"top\", data.Labels().Get(\"cID\"))\n\t\t\t},\n\n\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestTopHyperVContainer(t *testing.T) {\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.HyperV\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// FIXME: busybox 1.36 on windows still appears to not support sleep inf. Unclear why.\n\t\thelpers.Ensure(\"run\", \"--isolation\", \"hyperv\", \"-d\", \"--name\", data.Identifier(\"container\"), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"top\", data.Identifier(\"container\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_unpause.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc UnpauseCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"unpause [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Unpause all processes within one or more containers\",\n\t\tRunE:              unpauseAction,\n\t\tValidArgsFunction: unpauseShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc unpauseOptions(cmd *cobra.Command) (types.ContainerUnpauseOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerUnpauseOptions{}, err\n\t}\n\tnerdctlCmd, nerdctlArgs := helpers.GlobalFlags(cmd)\n\treturn types.ContainerUnpauseOptions{\n\t\tGOptions:    globalOptions,\n\t\tStdout:      cmd.OutOrStdout(),\n\t\tNerdctlCmd:  nerdctlCmd,\n\t\tNerdctlArgs: nerdctlArgs,\n\t}, nil\n}\n\nfunc unpauseAction(cmd *cobra.Command, args []string) error {\n\toptions, err := unpauseOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Unpause(ctx, client, args, options)\n}\n\nfunc unpauseShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show paused container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st == containerd.Paused\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_update.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\truntimespec \"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/typeurl/v2\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\tnerdctlcontainer \"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n)\n\ntype updateResourceOptions struct {\n\tCPUPeriod          uint64\n\tCPUQuota           int64\n\tCPUShares          uint64\n\tMemoryLimitInBytes int64\n\tMemoryReservation  int64\n\tMemorySwapInBytes  int64\n\tCpusetCpus         string\n\tCpusetMems         string\n\tPidsLimit          int64\n\tBlkioWeight        uint16\n}\n\nfunc UpdateCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"update [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Update one or more running containers\",\n\t\tRunE:              updateAction,\n\t\tValidArgsFunction: updateShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().SetInterspersed(false)\n\tsetUpdateFlags(cmd)\n\treturn cmd\n}\n\nfunc setUpdateFlags(cmd *cobra.Command) {\n\tcmd.Flags().Float64(\"cpus\", 0.0, \"Number of CPUs\")\n\tcmd.Flags().Uint64(\"cpu-period\", 0, \"Limit CPU CFS (Completely Fair Scheduler) period\")\n\tcmd.Flags().Int64(\"cpu-quota\", -1, \"Limit CPU CFS (Completely Fair Scheduler) quota\")\n\tcmd.Flags().Uint64(\"cpu-shares\", 0, \"CPU shares (relative weight)\")\n\tcmd.Flags().StringP(\"memory\", \"m\", \"\", \"Memory limit\")\n\tcmd.Flags().String(\"memory-reservation\", \"\", \"Memory soft limit\")\n\tcmd.Flags().String(\"memory-swap\", \"\", \"Swap limit equal to memory plus swap: '-1' to enable unlimited swap\")\n\tcmd.Flags().String(\"kernel-memory\", \"\", \"Kernel memory limit (deprecated)\")\n\tcmd.Flags().String(\"cpuset-cpus\", \"\", \"CPUs in which to allow execution (0-3, 0,1)\")\n\tcmd.Flags().String(\"cpuset-mems\", \"\", \"MEMs in which to allow execution (0-3, 0,1)\")\n\tcmd.Flags().Int64(\"pids-limit\", -1, \"Tune container pids limit (set -1 for unlimited)\")\n\tcmd.Flags().Uint16(\"blkio-weight\", 0, \"Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)\")\n\tcmd.Flags().String(\"restart\", \"no\", `Restart policy to apply when a container exits (implemented values: \"no\"|\"always|on-failure:n|unless-stopped\")`)\n\tcmd.RegisterFlagCompletionFunc(\"restart\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"no\", \"always\", \"on-failure\", \"unless-stopped\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n}\n\nfunc updateAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\toptions, err := getUpdateOption(cmd, globalOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\terr = updateContainer(ctx, client, found.Container.ID(), options, cmd)\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, args, true)\n}\n\nfunc getUpdateOption(cmd *cobra.Command, globalOptions types.GlobalCommandOptions) (updateResourceOptions, error) {\n\tvar options updateResourceOptions\n\tcpus, err := cmd.Flags().GetFloat64(\"cpus\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tcpuPeriod, err := cmd.Flags().GetUint64(\"cpu-period\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tcpuQuota, err := cmd.Flags().GetInt64(\"cpu-quota\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tif cpuQuota != -1 || cpuPeriod != 0 {\n\t\tif cpus > 0.0 {\n\t\t\treturn options, errors.New(\"cpus and quota/period should be used separately\")\n\t\t}\n\t}\n\tif cpus > 0.0 {\n\t\tcpuPeriod = uint64(100000)\n\t\tcpuQuota = int64(cpus * 100000.0)\n\t}\n\tshares, err := cmd.Flags().GetUint64(\"cpu-shares\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tmemStr, err := cmd.Flags().GetString(\"memory\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tmemSwap, err := cmd.Flags().GetString(\"memory-swap\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tvar mem64 int64\n\tif memStr != \"\" {\n\t\tmem64, err = units.RAMInBytes(memStr)\n\t\tif err != nil {\n\t\t\treturn options, fmt.Errorf(\"failed to parse memory bytes %q: %w\", memStr, err)\n\t\t}\n\t}\n\tvar memSwap64 int64\n\tif memSwap != \"\" {\n\t\tif memSwap == \"-1\" {\n\t\t\tmemSwap64 = -1\n\t\t} else {\n\t\t\tmemSwap64, err = units.RAMInBytes(memSwap)\n\t\t\tif err != nil {\n\t\t\t\treturn options, fmt.Errorf(\"failed to parse memory-swap bytes %q: %w\", memSwap, err)\n\t\t\t}\n\t\t\tif mem64 > 0 && memSwap64 > 0 && memSwap64 < mem64 {\n\t\t\t\treturn options, fmt.Errorf(\"minimum memoryswap limit should be larger than memory limit, see usage\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\tmemSwap64 = mem64 * 2\n\t}\n\tif memSwap64 == 0 {\n\t\tmemSwap64 = mem64 * 2\n\t}\n\tmemReserve, err := cmd.Flags().GetString(\"memory-reservation\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tvar memReserve64 int64\n\tif memReserve != \"\" {\n\t\tmemReserve64, err = units.RAMInBytes(memReserve)\n\t\tif err != nil {\n\t\t\treturn options, fmt.Errorf(\"failed to parse memory bytes %q: %w\", memReserve, err)\n\t\t}\n\t}\n\tif mem64 > 0 && memReserve64 > 0 && mem64 < memReserve64 {\n\t\treturn options, fmt.Errorf(\"minimum memory limit can not be less than memory reservation limit, see usage\")\n\t}\n\n\tkernelMemStr, err := cmd.Flags().GetString(\"kernel-memory\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tif kernelMemStr != \"\" && cmd.Flag(\"kernel-memory\").Changed {\n\t\tlog.L.Warnf(\"The --kernel-memory flag is no longer supported. This flag is a noop.\")\n\t}\n\tcpuset, err := cmd.Flags().GetString(\"cpuset-cpus\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tcpusetMems, err := cmd.Flags().GetString(\"cpuset-mems\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tpidsLimit, err := cmd.Flags().GetInt64(\"pids-limit\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tblkioWeight, err := cmd.Flags().GetUint16(\"blkio-weight\")\n\tif err != nil {\n\t\treturn options, err\n\t}\n\tif blkioWeight != 0 && !infoutil.BlockIOWeight(globalOptions.CgroupManager) {\n\t\treturn options, fmt.Errorf(\"kernel support for cgroup blkio weight missing, weight discarded\")\n\t}\n\tif blkioWeight > 0 && blkioWeight < 10 || blkioWeight > 1000 {\n\t\treturn options, errors.New(\"range of blkio weight is from 10 to 1000\")\n\t}\n\n\tif runtime.GOOS == \"linux\" {\n\t\toptions = updateResourceOptions{\n\t\t\tCPUPeriod:          cpuPeriod,\n\t\t\tCPUQuota:           cpuQuota,\n\t\t\tCPUShares:          shares,\n\t\t\tCpusetCpus:         cpuset,\n\t\t\tCpusetMems:         cpusetMems,\n\t\t\tMemoryLimitInBytes: mem64,\n\t\t\tMemoryReservation:  memReserve64,\n\t\t\tMemorySwapInBytes:  memSwap64,\n\t\t\tPidsLimit:          pidsLimit,\n\t\t\tBlkioWeight:        blkioWeight,\n\t\t}\n\t}\n\treturn options, nil\n}\n\nfunc updateContainer(ctx context.Context, client *containerd.Client, id string, opts updateResourceOptions, cmd *cobra.Command) (retErr error) {\n\tcontainer, err := client.LoadContainer(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcStatus := formatter.ContainerStatus(ctx, container)\n\tif cStatus == \"pausing\" {\n\t\treturn fmt.Errorf(\"container %q is in pausing state\", id)\n\t}\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toldSpec, err := copySpec(spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif runtime.GOOS == \"linux\" {\n\t\tif spec.Linux == nil {\n\t\t\tspec.Linux = &runtimespec.Linux{}\n\t\t}\n\t\tif spec.Linux.Resources == nil {\n\t\t\tspec.Linux.Resources = &runtimespec.LinuxResources{}\n\t\t}\n\t\tif cmd.Flags().Changed(\"blkio-weight\") {\n\t\t\tif spec.Linux.Resources.BlockIO == nil {\n\t\t\t\tspec.Linux.Resources.BlockIO = &runtimespec.LinuxBlockIO{}\n\t\t\t}\n\t\t\tif spec.Linux.Resources.BlockIO.Weight != &opts.BlkioWeight {\n\t\t\t\tspec.Linux.Resources.BlockIO.Weight = &opts.BlkioWeight\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"cpu-shares\") || cmd.Flags().Changed(\"cpu-quota\") || cmd.Flags().Changed(\"cpu-period\") || cmd.Flags().Changed(\"cpus\") || cmd.Flags().Changed(\"cpuset-mems\") || cmd.Flags().Changed(\"cpuset-cpus\") {\n\t\t\tif spec.Linux.Resources.CPU == nil {\n\t\t\t\tspec.Linux.Resources.CPU = &runtimespec.LinuxCPU{}\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"cpu-shares\") {\n\t\t\tif spec.Linux.Resources.CPU.Shares != &opts.CPUShares {\n\t\t\t\tspec.Linux.Resources.CPU.Shares = &opts.CPUShares\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"cpu-quota\") {\n\t\t\tif spec.Linux.Resources.CPU.Quota != &opts.CPUQuota {\n\t\t\t\tspec.Linux.Resources.CPU.Quota = &opts.CPUQuota\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"cpu-period\") {\n\t\t\tif spec.Linux.Resources.CPU.Period != &opts.CPUPeriod {\n\t\t\t\tspec.Linux.Resources.CPU.Period = &opts.CPUPeriod\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"cpus\") {\n\t\t\tif spec.Linux.Resources.CPU.Cpus != opts.CpusetCpus {\n\t\t\t\tspec.Linux.Resources.CPU.Cpus = opts.CpusetCpus\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"cpuset-mems\") {\n\t\t\tif spec.Linux.Resources.CPU.Mems != opts.CpusetMems {\n\t\t\t\tspec.Linux.Resources.CPU.Mems = opts.CpusetMems\n\t\t\t}\n\t\t}\n\n\t\tif cmd.Flags().Changed(\"cpuset-cpus\") {\n\t\t\tif spec.Linux.Resources.CPU.Cpus != opts.CpusetCpus {\n\t\t\t\tspec.Linux.Resources.CPU.Cpus = opts.CpusetCpus\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"memory\") || cmd.Flags().Changed(\"memory-reservation\") {\n\t\t\tif spec.Linux.Resources.Memory == nil {\n\t\t\t\tspec.Linux.Resources.Memory = &runtimespec.LinuxMemory{}\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"memory\") {\n\t\t\tif spec.Linux.Resources.Memory.Limit != &opts.MemoryLimitInBytes {\n\t\t\t\tspec.Linux.Resources.Memory.Limit = &opts.MemoryLimitInBytes\n\t\t\t}\n\t\t\tif spec.Linux.Resources.Memory.Swap != &opts.MemorySwapInBytes {\n\t\t\t\tspec.Linux.Resources.Memory.Swap = &opts.MemorySwapInBytes\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"memory-reservation\") {\n\t\t\tif spec.Linux.Resources.Memory.Reservation != &opts.MemoryReservation {\n\t\t\t\tspec.Linux.Resources.Memory.Reservation = &opts.MemoryReservation\n\t\t\t}\n\t\t}\n\t\tif cmd.Flags().Changed(\"pids-limit\") {\n\t\t\tif spec.Linux.Resources.Pids == nil {\n\t\t\t\tspec.Linux.Resources.Pids = &runtimespec.LinuxPids{}\n\t\t\t}\n\t\t\tif spec.Linux.Resources.Pids.Limit == nil || (spec.Linux.Resources.Pids.Limit != nil && *spec.Linux.Resources.Pids.Limit != opts.PidsLimit) {\n\t\t\t\tspec.Linux.Resources.Pids.Limit = &opts.PidsLimit\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := updateContainerSpec(ctx, container, spec); err != nil {\n\t\treturn fmt.Errorf(\"failed to update spec %+v for container %q\", spec, id)\n\t}\n\tdefer func() {\n\t\tif retErr != nil {\n\t\t\tdeferCtx, deferCancel := context.WithTimeout(ctx, 1*time.Minute)\n\t\t\tdefer deferCancel()\n\t\t\t// Reset spec on error.\n\t\t\tif err := updateContainerSpec(deferCtx, container, oldSpec); err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Errorf(\"Failed to update spec %+v for container %q\", oldSpec, id)\n\t\t\t}\n\t\t}\n\t}()\n\n\trestart, err := cmd.Flags().GetString(\"restart\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif cmd.Flags().Changed(\"restart\") && restart != \"\" {\n\t\tif err := nerdctlcontainer.UpdateContainerRestartPolicyLabel(ctx, client, container, restart); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// If container is not running, only update spec is enough, new resource\n\t// limit will be applied when container start.\n\tif cStatus != \"Up\" {\n\t\treturn nil\n\t}\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\tif errdefs.IsNotFound(err) {\n\t\t\t// Task exited already.\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to get task:%w\", err)\n\t}\n\treturn task.Update(ctx, containerd.WithResources(spec.Linux.Resources))\n}\n\nfunc updateContainerSpec(ctx context.Context, container containerd.Container, spec *runtimespec.Spec) error {\n\tif err := container.Update(ctx, func(ctx context.Context, client *containerd.Client, c *containers.Container) error {\n\t\ta, err := typeurl.MarshalAny(spec)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to marshal spec %+v:%w\", spec, err)\n\t\t}\n\t\tc.Spec = a\n\t\treturn nil\n\t}); err != nil {\n\t\treturn fmt.Errorf(\"failed to update container spec:%w\", err)\n\t}\n\treturn nil\n}\n\nfunc copySpec(spec *runtimespec.Spec) (*runtimespec.Spec, error) {\n\tvar copySpec runtimespec.Spec\n\tif spec == nil {\n\t\treturn nil, errors.New(\"spec cannot be nil\")\n\t}\n\tbytes, err := json.Marshal(spec)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to marshal spec: %w\", err)\n\t}\n\terr = json.Unmarshal(bytes, &copySpec)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal into spec copy: %w\", err)\n\t}\n\treturn &copySpec, nil\n}\n\nfunc updateShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ContainerNames(cmd, nil)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_update_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestUpdateContainer(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := testutil.Identifier(t)\n\t\tdata.Labels().Set(\"containerName\", containerName)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", containerName, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\tnerdtest.EnsureContainerStarted(helpers, containerName)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\thelpers.Anyhow(\"rm\", \"-f\", containerName)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"should fail on unsupported restart policy value\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"update\", \"--memory\", \"999999999\", \"--restart\", \"123\", containerName)\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"unsupported restart policy\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"should not update memory in inspect\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcontainerName := data.Labels().Get(\"containerName\")\n\t\t\t\treturn helpers.Command(\"inspect\", \"--mode=native\", containerName)\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeSuccess, nil, expect.DoesNotContain(`\"limit\": 999999999,`)),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_wait.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n)\n\nfunc WaitCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"wait [flags] CONTAINER [CONTAINER, ...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Block until one or more containers stop, then print their exit codes.\",\n\t\tRunE:              waitAction,\n\t\tValidArgsFunction: waitShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc waitOptions(cmd *cobra.Command) (types.ContainerWaitOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ContainerWaitOptions{}, err\n\t}\n\treturn types.ContainerWaitOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t}, nil\n}\n\nfunc waitAction(cmd *cobra.Command, args []string) error {\n\toptions, err := waitOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn container.Wait(ctx, client, args, options)\n}\n\nfunc waitShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show running container names\n\tstatusFilterFn := func(st containerd.ProcessStatus) bool {\n\t\treturn st == containerd.Running\n\t}\n\treturn completion.ContainerNames(cmd, statusFilterFn)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/container_wait_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestWait(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"1\"), data.Identifier(\"2\"), data.Identifier(\"3\"))\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"1\"), testutil.CommonImage)\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"2\"), testutil.CommonImage, \"sleep\", \"1\")\n\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(\"3\"), testutil.CommonImage, \"sh\", \"-euxc\", \"sleep 5; exit 123\")\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"wait\", data.Identifier(\"1\"), data.Identifier(\"2\"), data.Identifier(\"3\"))\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, expect.Equals(`0\n0\n123\n`))\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/container/multi_platform_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry\"\n)\n\nfunc testMultiPlatformRun(base *testutil.Base, alpineImage string) {\n\tt := base.T\n\ttestutil.RequireExecPlatform(t, \"linux/amd64\", \"linux/arm64\", \"linux/arm/v7\")\n\ttestCases := map[string]string{\n\t\t\"amd64\":        \"x86_64\",\n\t\t\"arm64\":        \"aarch64\",\n\t\t\"arm\":          \"armv7l\",\n\t\t\"linux/arm\":    \"armv7l\",\n\t\t\"linux/arm/v7\": \"armv7l\",\n\t}\n\tfor plat, expectedUnameM := range testCases {\n\t\tt.Logf(\"Testing %q (%q)\", plat, expectedUnameM)\n\t\tcmd := base.Cmd(\"run\", \"--rm\", \"--platform=\"+plat, alpineImage, \"uname\", \"-m\")\n\t\tcmd.AssertOutExactly(expectedUnameM + \"\\n\")\n\t}\n}\n\nfunc TestMultiPlatformRun(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\ttestMultiPlatformRun(base, testutil.AlpineImage)\n}\n\nfunc TestMultiPlatformBuildPush(t *testing.T) {\n\ttestutil.DockerIncompatible(t) // non-buildx version of `docker build` lacks multi-platform. Also, `docker push` lacks --platform.\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\ttestutil.RequireExecPlatform(t, \"linux/amd64\", \"linux/arm64\", \"linux/arm/v7\")\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\treg := testregistry.NewWithNoAuth(base, 0, false)\n\tdefer reg.Cleanup(nil)\n\n\timageName := fmt.Sprintf(\"localhost:%d/%s:latest\", reg.Port, tID)\n\tdefer base.Cmd(\"rmi\", imageName).Run()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN echo dummy\n\t`, testutil.AlpineImage)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, \"--platform=amd64,arm64,linux/arm/v7\", buildCtx).AssertOK()\n\ttestMultiPlatformRun(base, imageName)\n\tbase.Cmd(\"push\", \"--platform=amd64,arm64,linux/arm/v7\", imageName).AssertOK()\n}\n\n// TestMultiPlatformBuildPushNoRun tests if the push succeeds in a situation where nerdctl builds\n// a Dockerfile without RUN, COPY, etc commands. In such situation, BuildKit doesn't download the base image\n// so nerdctl needs to ensure these blobs to be locally available.\nfunc TestMultiPlatformBuildPushNoRun(t *testing.T) {\n\ttestutil.DockerIncompatible(t) // non-buildx version of `docker build` lacks multi-platform. Also, `docker push` lacks --platform.\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\ttestutil.RequireExecPlatform(t, \"linux/amd64\", \"linux/arm64\", \"linux/arm/v7\")\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\treg := testregistry.NewWithNoAuth(base, 0, false)\n\tdefer reg.Cleanup(nil)\n\n\timageName := fmt.Sprintf(\"localhost:%d/%s:latest\", reg.Port, tID)\n\tdefer base.Cmd(\"rmi\", imageName).Run()\n\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD echo dummy\n\t`, testutil.AlpineImage)\n\n\tbuildCtx := helpers.CreateBuildContext(t, dockerfile)\n\n\tbase.Cmd(\"build\", \"-t\", imageName, \"--platform=amd64,arm64,linux/arm/v7\", buildCtx).AssertOK()\n\ttestMultiPlatformRun(base, imageName)\n\tbase.Cmd(\"push\", \"--platform=amd64,arm64,linux/arm/v7\", imageName).AssertOK()\n}\n\nfunc TestMultiPlatformPullPushAllPlatforms(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\tbase := testutil.NewBase(t)\n\ttID := testutil.Identifier(t)\n\treg := testregistry.NewWithNoAuth(base, 0, false)\n\tdefer reg.Cleanup(nil)\n\n\tpushImageName := fmt.Sprintf(\"localhost:%d/%s:latest\", reg.Port, tID)\n\tdefer base.Cmd(\"rmi\", pushImageName).Run()\n\n\tbase.Cmd(\"pull\", \"--quiet\", \"--all-platforms\", testutil.AlpineImage).AssertOK()\n\tbase.Cmd(\"tag\", testutil.AlpineImage, pushImageName).AssertOK()\n\tbase.Cmd(\"push\", \"--all-platforms\", pushImageName).AssertOK()\n\ttestMultiPlatformRun(base, pushImageName)\n}\n\nfunc TestMultiPlatformComposeUpBuild(t *testing.T) {\n\ttestutil.DockerIncompatible(t)\n\ttestutil.RequiresBuild(t)\n\ttestutil.RegisterBuildCacheCleanup(t)\n\ttestutil.RequireExecPlatform(t, \"linux/amd64\", \"linux/arm64\", \"linux/arm/v7\")\n\tbase := testutil.NewBase(t)\n\n\tconst dockerComposeYAML = `\nservices:\n  svc0:\n    build: .\n    platform: amd64\n    ports:\n    - 8080:80\n  svc1:\n    build: .\n    platform: arm64\n    ports:\n    - 8081:80\n  svc2:\n    build: .\n    platform: linux/arm/v7\n    ports:\n    - 8082:80\n`\n\tdockerfile := fmt.Sprintf(`FROM %s\nRUN uname -m > /usr/share/nginx/html/index.html\n`, testutil.NginxAlpineImage)\n\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tcomp.WriteFile(\"Dockerfile\", dockerfile)\n\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"up\", \"-d\", \"--build\").AssertOK()\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\n\ttestCases := map[string]string{\n\t\t\"http://127.0.0.1:8080\": \"x86_64\",\n\t\t\"http://127.0.0.1:8081\": \"aarch64\",\n\t\t\"http://127.0.0.1:8082\": \"armv7l\",\n\t}\n\n\tfor testURL, expectedIndexHTML := range testCases {\n\t\tresp, err := nettestutil.HTTPGet(testURL, 5, false)\n\t\tassert.NilError(t, err)\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tassert.NilError(t, err)\n\t\tt.Logf(\"respBody=%q\", respBody)\n\t\tassert.Assert(t, strings.Contains(string(respBody), expectedIndexHTML))\n\t}\n}\n"
  },
  {
    "path": "cmd/nerdctl/helpers/cobra.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/containerd/log\"\n)\n\n// UnknownSubcommandAction is needed to let `nerdctl system non-existent-command` fail\n// https://github.com/containerd/nerdctl/issues/487\n//\n// Ideally this should be implemented in Cobra itself.\nfunc UnknownSubcommandAction(cmd *cobra.Command, args []string) error {\n\tif len(args) == 0 {\n\t\treturn cmd.Help()\n\t}\n\t// The output mimics https://github.com/spf13/cobra/blob/v1.2.1/command.go#L647-L662\n\tmsg := fmt.Sprintf(\"unknown subcommand %q for %q\", args[0], cmd.Name())\n\tif suggestions := cmd.SuggestionsFor(args[0]); len(suggestions) > 0 {\n\t\tmsg += \"\\n\\nDid you mean this?\\n\"\n\t\tfor _, s := range suggestions {\n\t\t\tmsg += fmt.Sprintf(\"\\t%v\\n\", s)\n\t\t}\n\t}\n\treturn errors.New(msg)\n}\n\n// IsExactArgs returns an error if there is not the exact number of args\nfunc IsExactArgs(number int) cobra.PositionalArgs {\n\treturn func(cmd *cobra.Command, args []string) error {\n\t\tif len(args) == number {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\n\t\t\t\"%q requires exactly %d %s.\\nSee '%s --help'.\\n\\nUsage:  %s\\n\\n%s\",\n\t\t\tcmd.CommandPath(),\n\t\t\tnumber,\n\t\t\t\"argument(s)\",\n\t\t\tcmd.CommandPath(),\n\t\t\tcmd.UseLine(),\n\t\t\tcmd.Short,\n\t\t)\n\t}\n}\n\n// AddStringFlag is similar to cmd.Flags().String but supports aliases and env var\nfunc AddStringFlag(cmd *cobra.Command, name string, aliases []string, value string, env, usage string) {\n\tif env != \"\" {\n\t\tusage = fmt.Sprintf(\"%s [$%s]\", usage, env)\n\t}\n\tif envV, ok := os.LookupEnv(env); ok {\n\t\tvalue = envV\n\t}\n\taliasesUsage := fmt.Sprintf(\"Alias of --%s\", name)\n\tp := new(string)\n\tflags := cmd.Flags()\n\tflags.StringVar(p, name, value, usage)\n\tfor _, a := range aliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tflags.StringVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tflags.StringVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n}\n\n// AddIntFlag is similar to cmd.Flags().Int but supports aliases and env var\nfunc AddIntFlag(cmd *cobra.Command, name string, aliases []string, value int, env, usage string) {\n\tif env != \"\" {\n\t\tusage = fmt.Sprintf(\"%s [$%s]\", usage, env)\n\t}\n\tif envV, ok := os.LookupEnv(env); ok {\n\t\tv, err := strconv.ParseInt(envV, 10, 64)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"Invalid int value for `%s`\", env)\n\t\t}\n\t\tvalue = int(v)\n\t}\n\taliasesUsage := fmt.Sprintf(\"Alias of --%s\", name)\n\tp := new(int)\n\tflags := cmd.Flags()\n\tflags.IntVar(p, name, value, usage)\n\tfor _, a := range aliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tflags.IntVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tflags.IntVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n}\n\n// AddDurationFlag is similar to cmd.Flags().Duration but supports aliases and env var\nfunc AddDurationFlag(cmd *cobra.Command, name string, aliases []string, value time.Duration, env, usage string) {\n\tif env != \"\" {\n\t\tusage = fmt.Sprintf(\"%s [$%s]\", usage, env)\n\t}\n\tif envV, ok := os.LookupEnv(env); ok {\n\t\tvar err error\n\t\tvalue, err = time.ParseDuration(envV)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"Invalid duration value for `%s`\", env)\n\t\t}\n\t}\n\taliasesUsage := fmt.Sprintf(\"Alias of --%s\", name)\n\tp := new(time.Duration)\n\tflags := cmd.Flags()\n\tflags.DurationVar(p, name, value, usage)\n\tfor _, a := range aliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tflags.DurationVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tflags.DurationVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n}\n\nfunc GlobalFlags(cmd *cobra.Command) (string, []string) {\n\targs0, err := os.Executable()\n\tif err != nil {\n\t\tlog.L.WithError(err).Warnf(\"cannot call os.Executable(), assuming the executable to be %q\", os.Args[0])\n\t\targs0 = os.Args[0]\n\t}\n\tif len(os.Args) < 2 {\n\t\treturn args0, nil\n\t}\n\n\trootCmd := cmd.Root()\n\tflagSet := rootCmd.Flags()\n\targs := []string{}\n\tflagSet.VisitAll(func(f *pflag.Flag) {\n\t\tkey := f.Name\n\t\tval := f.Value.String()\n\t\t// Include flag if:\n\t\t// 1. It was explicitly changed via CLI (highest priority), OR\n\t\t// 2. It has a non-default value (from TOML config)\n\t\t// This ensures both CLI flags and TOML config values are propagated\n\t\tif f.Changed || (val != f.DefValue && val != \"\") {\n\t\t\targs = append(args, \"--\"+key+\"=\"+val)\n\t\t}\n\t})\n\treturn args0, args\n}\n\n// AddPersistentStringArrayFlag is similar to cmd.Flags().StringArray but supports aliases and env var and persistent.\n// See https://github.com/spf13/cobra/blob/main/user_guide.md#persistent-flags to learn what is \"persistent\".\nfunc AddPersistentStringArrayFlag(cmd *cobra.Command, name string, aliases, nonPersistentAliases []string, value []string, env string, usage string) {\n\tif env != \"\" {\n\t\tusage = fmt.Sprintf(\"%s [$%s]\", usage, env)\n\t}\n\tif envV, ok := os.LookupEnv(env); ok {\n\t\tvalue = []string{envV}\n\t}\n\taliasesUsage := fmt.Sprintf(\"Alias of --%s\", name)\n\tp := new([]string)\n\tflags := cmd.Flags()\n\tfor _, a := range nonPersistentAliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tflags.StringArrayVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tflags.StringArrayVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n\n\tpersistentFlags := cmd.PersistentFlags()\n\tpersistentFlags.StringArrayVar(p, name, value, usage)\n\tfor _, a := range aliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tpersistentFlags.StringArrayVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tpersistentFlags.StringArrayVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n}\n\n// AddPersistentStringFlag is similar to AddStringFlag but persistent.\n// See https://github.com/spf13/cobra/blob/main/user_guide.md#persistent-flags to learn what is \"persistent\".\nfunc AddPersistentStringFlag(cmd *cobra.Command, name string, aliases, localAliases, persistentAliases []string, aliasToBeInherited *pflag.FlagSet, value string, env, usage string) {\n\tif env != \"\" {\n\t\tusage = fmt.Sprintf(\"%s [$%s]\", usage, env)\n\t}\n\tif envV, ok := os.LookupEnv(env); ok {\n\t\tvalue = envV\n\t}\n\taliasesUsage := fmt.Sprintf(\"Alias of --%s\", name)\n\tp := new(string)\n\n\t// flags is full set of flag(s)\n\t// flags can redefine alias already used in subcommands\n\tflags := cmd.Flags()\n\tfor _, a := range aliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tflags.StringVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tflags.StringVar(p, a, value, aliasesUsage)\n\t\t}\n\t\t// non-persistent flags are not added to the InheritedFlags, so we should add them manually\n\t\tf := flags.Lookup(a)\n\t\taliasToBeInherited.AddFlag(f)\n\t}\n\n\t// localFlags are local to the rootCmd\n\tlocalFlags := cmd.LocalFlags()\n\tfor _, a := range localAliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tlocalFlags.StringVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tlocalFlags.StringVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n\n\t// persistentFlags cannot redefine alias already used in subcommands\n\tpersistentFlags := cmd.PersistentFlags()\n\tpersistentFlags.StringVar(p, name, value, usage)\n\tfor _, a := range persistentAliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tpersistentFlags.StringVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tpersistentFlags.StringVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n}\n\n// AddPersistentBoolFlag is similar to AddBoolFlag but persistent.\n// See https://github.com/spf13/cobra/blob/main/user_guide.md#persistent-flags to learn what is \"persistent\".\nfunc AddPersistentBoolFlag(cmd *cobra.Command, name string, aliases, nonPersistentAliases []string, value bool, env, usage string) {\n\tif env != \"\" {\n\t\tusage = fmt.Sprintf(\"%s [$%s]\", usage, env)\n\t}\n\tif envV, ok := os.LookupEnv(env); ok {\n\t\tvar err error\n\t\tvalue, err = strconv.ParseBool(envV)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"Invalid boolean value for `%s`\", env)\n\t\t}\n\t}\n\taliasesUsage := fmt.Sprintf(\"Alias of --%s\", name)\n\tp := new(bool)\n\tflags := cmd.Flags()\n\tfor _, a := range nonPersistentAliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tflags.BoolVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tflags.BoolVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n\n\tpersistentFlags := cmd.PersistentFlags()\n\tpersistentFlags.BoolVar(p, name, value, usage)\n\tfor _, a := range aliases {\n\t\tif len(a) == 1 {\n\t\t\t// pflag doesn't support short-only flags, so we have to register long one as well here\n\t\t\tpersistentFlags.BoolVarP(p, a, a, value, aliasesUsage)\n\t\t} else {\n\t\t\tpersistentFlags.BoolVar(p, a, value, aliasesUsage)\n\t\t}\n\t}\n}\n\n// HiddenPersistentStringArrayFlag creates a persistent string slice flag and hides it.\n// Used mainly to pass global config values to individual commands.\nfunc HiddenPersistentStringArrayFlag(cmd *cobra.Command, name string, value []string, usage string) {\n\tcmd.PersistentFlags().StringSlice(name, value, usage)\n\tcmd.PersistentFlags().MarkHidden(name)\n}\n"
  },
  {
    "path": "cmd/nerdctl/helpers/consts.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage helpers\n\nconst (\n\tCategory   = \"category\"\n\tManagement = \"management\"\n)\n"
  },
  {
    "path": "cmd/nerdctl/helpers/flagutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/fs\"\n)\n\nfunc VerifyOptions(cmd *cobra.Command) (opt types.ImageVerifyOptions, err error) {\n\tif opt.Provider, err = cmd.Flags().GetString(\"verify\"); err != nil {\n\t\treturn\n\t}\n\tif opt.CosignKey, err = cmd.Flags().GetString(\"cosign-key\"); err != nil {\n\t\treturn\n\t}\n\tif opt.CosignCertificateIdentity, err = cmd.Flags().GetString(\"cosign-certificate-identity\"); err != nil {\n\t\treturn\n\t}\n\tif opt.CosignCertificateIdentityRegexp, err = cmd.Flags().GetString(\"cosign-certificate-identity-regexp\"); err != nil {\n\t\treturn\n\t}\n\tif opt.CosignCertificateOidcIssuer, err = cmd.Flags().GetString(\"cosign-certificate-oidc-issuer\"); err != nil {\n\t\treturn\n\t}\n\tif opt.CosignCertificateOidcIssuerRegexp, err = cmd.Flags().GetString(\"cosign-certificate-oidc-issuer-regexp\"); err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\nfunc ValidateHealthcheckFlags(options types.ContainerCreateOptions) error {\n\thealthFlagsSet :=\n\t\toptions.HealthInterval != 0 ||\n\t\t\toptions.HealthTimeout != 0 ||\n\t\t\toptions.HealthRetries != 0 ||\n\t\t\toptions.HealthStartPeriod != 0\n\n\tif options.NoHealthcheck {\n\t\tif options.HealthCmd != \"\" || healthFlagsSet {\n\t\t\treturn fmt.Errorf(\"--no-healthcheck conflicts with --health-* options\")\n\t\t}\n\t}\n\n\t// Note: HealthCmd can be empty with other healthcheck flags set cause healthCmd could be coming from image.\n\tif options.HealthInterval < 0 {\n\t\treturn fmt.Errorf(\"--health-interval cannot be negative\")\n\t}\n\tif options.HealthTimeout < 0 {\n\t\treturn fmt.Errorf(\"--health-timeout cannot be negative\")\n\t}\n\tif options.HealthRetries < 0 {\n\t\treturn fmt.Errorf(\"--health-retries cannot be negative\")\n\t}\n\tif options.HealthStartPeriod < 0 {\n\t\treturn fmt.Errorf(\"--health-start-period cannot be negative\")\n\t}\n\treturn nil\n}\n\nfunc ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error) {\n\tdebug, err := cmd.Flags().GetBool(\"debug\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tdebugFull, err := cmd.Flags().GetBool(\"debug-full\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\taddress, err := cmd.Flags().GetString(\"address\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tnamespace, err := cmd.Flags().GetString(\"namespace\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tsnapshotter, err := cmd.Flags().GetString(\"snapshotter\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tcniPath, err := cmd.Flags().GetString(\"cni-path\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tcniConfigPath, err := cmd.Flags().GetString(\"cni-netconfpath\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tdataRoot, err := cmd.Flags().GetString(\"data-root\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tcgroupManager, err := cmd.Flags().GetString(\"cgroup-manager\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tinsecureRegistry, err := cmd.Flags().GetBool(\"insecure-registry\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\thostsDir, err := cmd.Flags().GetStringSlice(\"hosts-dir\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\texperimental, err := cmd.Flags().GetBool(\"experimental\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\thostGatewayIP, err := cmd.Flags().GetString(\"host-gateway-ip\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tbridgeIP, err := cmd.Flags().GetString(\"bridge-ip\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tkubeHideDupe, err := cmd.Flags().GetBool(\"kube-hide-dupe\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tcdiSpecDirs, err := cmd.Flags().GetStringSlice(\"cdi-spec-dirs\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tdns, err := cmd.Flags().GetStringSlice(\"global-dns\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tdnsOpts, err := cmd.Flags().GetStringSlice(\"global-dns-opts\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\tdnsSearch, err := cmd.Flags().GetStringSlice(\"global-dns-search\")\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\n\t// Point to dataRoot for filesystem-helpers implementing rollback / backups.\n\terr = fs.InitFS(dataRoot)\n\tif err != nil {\n\t\treturn types.GlobalCommandOptions{}, err\n\t}\n\n\treturn types.GlobalCommandOptions{\n\t\tDebug:            debug,\n\t\tDebugFull:        debugFull,\n\t\tAddress:          address,\n\t\tNamespace:        namespace,\n\t\tSnapshotter:      snapshotter,\n\t\tCNIPath:          cniPath,\n\t\tCNINetConfPath:   cniConfigPath,\n\t\tDataRoot:         dataRoot,\n\t\tCgroupManager:    cgroupManager,\n\t\tInsecureRegistry: insecureRegistry,\n\t\tHostsDir:         hostsDir,\n\t\tExperimental:     experimental,\n\t\tHostGatewayIP:    hostGatewayIP,\n\t\tBridgeIP:         bridgeIP,\n\t\tKubeHideDupe:     kubeHideDupe,\n\t\tCDISpecDirs:      cdiSpecDirs,\n\t\tDNS:              dns,\n\t\tDNSOpts:          dnsOpts,\n\t\tDNSSearch:        dnsSearch,\n\t}, nil\n}\n\nfunc CheckExperimental(feature string) func(cmd *cobra.Command, args []string) error {\n\treturn func(cmd *cobra.Command, args []string) error {\n\t\tglobalOptions, err := ProcessRootCmdFlags(cmd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !globalOptions.Experimental {\n\t\t\treturn fmt.Errorf(\"%s is experimental feature, you should enable experimental config\", feature)\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "cmd/nerdctl/helpers/prompt.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc Confirm(cmd *cobra.Command, message string) (bool, error) {\n\tmessage += \"\\nAre you sure you want to continue? [y/N] \"\n\t_, err := fmt.Fprint(cmd.OutOrStdout(), message)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar confirm string\n\t_, err = fmt.Fscanf(cmd.InOrStdin(), \"%s\", &confirm)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn strings.ToLower(confirm) == \"y\", err\n}\n"
  },
  {
    "path": "cmd/nerdctl/helpers/testing.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc CreateBuildContext(t *testing.T, dockerfile string) string {\n\ttmpDir := t.TempDir()\n\terr := os.WriteFile(filepath.Join(tmpDir, \"Dockerfile\"), []byte(dockerfile), 0644)\n\tassert.NilError(t, err)\n\treturn tmpDir\n}\n\nfunc ExtractDockerArchive(archiveTarPath, rootfsPath string) error {\n\tif err := os.MkdirAll(rootfsPath, 0755); err != nil {\n\t\treturn err\n\t}\n\tworkDir, err := os.MkdirTemp(\"\", \"extract-docker-archive\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(workDir)\n\tif err := ExtractTarFile(workDir, archiveTarPath); err != nil {\n\t\treturn err\n\t}\n\tmanifestJSONPath := filepath.Join(workDir, \"manifest.json\")\n\tmanifestJSONBytes, err := os.ReadFile(manifestJSONPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar mani DockerArchiveManifestJSON\n\tif err := json.Unmarshal(manifestJSONBytes, &mani); err != nil {\n\t\treturn err\n\t}\n\tif len(mani) > 1 {\n\t\treturn fmt.Errorf(\"multi-image archive cannot be extracted: contains %d images\", len(mani))\n\t}\n\tif len(mani) < 1 {\n\t\treturn errors.New(\"invalid archive\")\n\t}\n\tent := mani[0]\n\tfor _, l := range ent.Layers {\n\t\tlayerTarPath := filepath.Join(workDir, l)\n\t\tif err := ExtractTarFile(rootfsPath, layerTarPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\ntype DockerArchiveManifestJSON []DockerArchiveManifestJSONEntry\n\ntype DockerArchiveManifestJSONEntry struct {\n\tConfig   string\n\tRepoTags []string\n\tLayers   []string\n}\n\nfunc ExtractTarFile(dirPath, tarFilePath string) error {\n\tcmd := exec.Command(\"tar\", \"Cxf\", dirPath, tarFilePath)\n\tif out, err := cmd.CombinedOutput(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run %v: %q: %w\", cmd.Args, string(out), err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/helpers/testing_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\ntype CosignKeyPair struct {\n\tPublicKey  string\n\tPrivateKey string\n\tCleanup    func()\n}\n\nfunc NewCosignKeyPair(t testing.TB, path string, password string) *CosignKeyPair {\n\ttd, err := os.MkdirTemp(t.TempDir(), path)\n\tassert.NilError(t, err)\n\n\tcmd := exec.Command(\"cosign\", \"generate-key-pair\")\n\tcmd.Dir = td\n\tcmd.Env = append(cmd.Env, fmt.Sprintf(\"COSIGN_PASSWORD=%s\", password))\n\tif out, err := cmd.CombinedOutput(); err != nil {\n\t\tt.Fatalf(\"failed to run %v: %v (%q)\", cmd.Args, err, string(out))\n\t}\n\n\tpublicKey := filepath.Join(td, \"cosign.pub\")\n\tprivateKey := filepath.Join(td, \"cosign.key\")\n\n\treturn &CosignKeyPair{\n\t\tPublicKey:  publicKey,\n\t\tPrivateKey: privateKey,\n\t\tCleanup: func() {\n\t\t\t_ = os.RemoveAll(td)\n\t\t},\n\t}\n}\n\nfunc ComposeUp(t *testing.T, base *testutil.Base, dockerComposeYAML string, opts ...string) {\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tprojectName := comp.ProjectName()\n\tt.Logf(\"projectName=%q\", projectName)\n\n\tbase.ComposeCmd(append(append([]string{\"-f\", comp.YAMLFullPath()}, opts...), \"up\", \"-d\")...).AssertOK()\n\tdefer base.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").Run()\n\tbase.Cmd(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName)).AssertOK()\n\tbase.Cmd(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName)).AssertOK()\n\n\tcheckWordpress := func() error {\n\t\tresp, err := nettestutil.HTTPGet(\"http://127.0.0.1:8080\", 5, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !strings.Contains(string(respBody), testutil.WordpressIndexHTMLSnippet) {\n\t\t\tt.Logf(\"respBody=%q\", respBody)\n\t\t\treturn fmt.Errorf(\"respBody does not contain %q\", testutil.WordpressIndexHTMLSnippet)\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar wordpressWorking bool\n\tfor i := 0; i < 30; i++ {\n\t\tt.Logf(\"(retry %d)\", i)\n\t\terr := checkWordpress()\n\t\tif err == nil {\n\t\t\twordpressWorking = true\n\t\t\tbreak\n\t\t}\n\t\t// NOTE: \"<h1>Error establishing a database connection</h1>\" is expected for the first few iterations\n\t\tt.Log(err)\n\t\ttime.Sleep(3 * time.Second)\n\t}\n\n\tif !wordpressWorking {\n\t\tt.Fatal(\"wordpress is not working\")\n\t}\n\tt.Log(\"wordpress seems functional\")\n\n\tbase.ComposeCmd(\"-f\", comp.YAMLFullPath(), \"down\", \"-v\").AssertOK()\n\tbase.Cmd(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName)).AssertFail()\n\tbase.Cmd(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName)).AssertFail()\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/builder\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"image\",\n\t\tShort:         \"Manage images\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tbuilder.BuildCommand(),\n\t\t// commitCommand is in \"container\", not in \"image\"\n\t\timageLsCommand(),\n\t\tHistoryCommand(),\n\t\tPullCommand(),\n\t\tPushCommand(),\n\t\tLoadCommand(),\n\t\tSaveCommand(),\n\t\tImportCommand(),\n\t\tTagCommand(),\n\t\timageRemoveCommand(),\n\t\tconvertCommand(),\n\t\tinspectCommand(),\n\t\tencryptCommand(),\n\t\tdecryptCommand(),\n\t\tpruneCommand(),\n\t)\n\treturn cmd\n}\n\nfunc imageLsCommand() *cobra.Command {\n\tx := ImagesCommand()\n\tx.Use = \"ls\"\n\tx.Aliases = []string{\"list\"}\n\treturn x\n}\n\nfunc imageRemoveCommand() *cobra.Command {\n\tx := RmiCommand()\n\tx.Use = \"rm\"\n\tx.Aliases = []string{\"remove\"}\n\treturn x\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_convert.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"compress/gzip\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nconst imageConvertHelp = `Convert an image format.\n\ne.g., 'nerdctl image convert --estargz --oci example.com/foo:orig example.com/foo:esgz'\n\nUse '--platform' to define the output platform.\nWhen '--all-platforms' is given all images in a manifest list must be available.\n\nFor encryption and decryption, use 'nerdctl image (encrypt|decrypt)' command.\n`\n\n// imageConvertCommand is from https://github.com/containerd/stargz-snapshotter/blob/d58f43a8235e46da73fb94a1a35280cb4d607b2c/cmd/ctr-remote/commands/convert.go\nfunc convertCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"convert [flags] <source_ref> <target_ref>...\",\n\t\tShort:             \"convert an image\",\n\t\tLong:              imageConvertHelp,\n\t\tArgs:              cobra.MinimumNArgs(2),\n\t\tRunE:              imageConvertAction,\n\t\tValidArgsFunction: imageConvertShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given Go template, e.g, 'json'\")\n\n\t// #region estargz flags\n\tcmd.Flags().Bool(\"estargz\", false, \"Convert legacy tar(.gz) layers to eStargz for lazy pulling. Should be used in conjunction with '--oci'\")\n\tcmd.Flags().String(\"estargz-record-in\", \"\", \"Read 'ctr-remote optimize --record-out=<FILE>' record file (EXPERIMENTAL)\")\n\tcmd.Flags().Int(\"estargz-compression-level\", gzip.BestCompression, \"eStargz compression level\")\n\tcmd.Flags().Int(\"estargz-chunk-size\", 0, \"eStargz chunk size\")\n\tcmd.Flags().Int(\"estargz-min-chunk-size\", 0, \"The minimal number of bytes of data must be written in one gzip stream. (requires stargz-snapshotter >= v0.13.0)\")\n\tcmd.Flags().Bool(\"estargz-external-toc\", false, \"Separate TOC JSON into another image (called \\\"TOC image\\\"). The name of TOC image is the original + \\\"-esgztoc\\\" suffix. Both eStargz and the TOC image should be pushed to the same registry. (requires stargz-snapshotter >= v0.13.0) (EXPERIMENTAL)\")\n\tcmd.Flags().Bool(\"estargz-keep-diff-id\", false, \"Convert to esgz without changing diffID (cannot be used in conjunction with '--estargz-record-in'. must be specified with '--estargz-external-toc')\")\n\tcmd.Flags().String(\"estargz-gzip-helper\", \"\", \"Helper command for decompressing layers compressed with gzip. Options: pigz, igzip, or gzip.\")\n\t// #endregion\n\n\t// #region zstd flags\n\tcmd.Flags().Bool(\"zstd\", false, \"Convert legacy tar(.gz) layers to zstd. Should be used in conjunction with '--oci'\")\n\tcmd.Flags().Int(\"zstd-compression-level\", 3, \"zstd compression level\")\n\t// #endregion\n\n\t// #region zstd:chunked flags\n\tcmd.Flags().Bool(\"zstdchunked\", false, \"Convert legacy tar(.gz) layers to zstd:chunked for lazy pulling. Should be used in conjunction with '--oci'\")\n\tcmd.Flags().String(\"zstdchunked-record-in\", \"\", \"Read 'ctr-remote optimize --record-out=<FILE>' record file (EXPERIMENTAL)\")\n\tcmd.Flags().Int(\"zstdchunked-compression-level\", 3, \"zstd:chunked compression level\") // SpeedDefault; see also https://pkg.go.dev/github.com/klauspost/compress/zstd#EncoderLevel\n\tcmd.Flags().Int(\"zstdchunked-chunk-size\", 0, \"zstd:chunked chunk size\")\n\t// #endregion\n\n\t// #region nydus flags\n\tcmd.Flags().Bool(\"nydus\", false, \"Convert an OCI image to Nydus image. Should be used in conjunction with '--oci'\")\n\tcmd.Flags().String(\"nydus-builder-path\", \"nydus-image\", \"The nydus-image binary path, if unset, search in PATH environment\")\n\tcmd.Flags().String(\"nydus-work-dir\", \"\", \"Work directory path for image conversion, default is the nerdctl data root directory\")\n\tcmd.Flags().String(\"nydus-prefetch-patterns\", \"\", \"The file path pattern list want to prefetch\")\n\tcmd.Flags().String(\"nydus-compressor\", \"lz4_block\", \"Nydus blob compression algorithm, possible values: `none`, `lz4_block`, `zstd`, default is `lz4_block`\")\n\t// #endregion\n\n\t// #region overlaybd flags\n\tcmd.Flags().Bool(\"overlaybd\", false, \"Convert tar.gz layers to overlaybd layers\")\n\tcmd.Flags().String(\"overlaybd-fs-type\", \"ext4\", \"Filesystem type for overlaybd\")\n\tcmd.Flags().String(\"overlaybd-dbstr\", \"\", \"Database config string for overlaybd\")\n\t// #endregion\n\n\t// #region soci flags\n\tcmd.Flags().Bool(\"soci\", false, \"Convert image to SOCI Index V2 format.\")\n\tcmd.Flags().Int64(\"soci-min-layer-size\", -1, \"The minimum size of layers that will be converted to SOCI Index V2 format\")\n\tcmd.Flags().Int64(\"soci-span-size\", -1, \"The size of SOCI spans\")\n\t// #endregion\n\n\t// #region generic flags\n\tcmd.Flags().Bool(\"uncompress\", false, \"Convert tar.gz layers to uncompressed tar layers\")\n\tcmd.Flags().Bool(\"oci\", false, \"Convert Docker media types to OCI media types\")\n\t// #endregion\n\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tcmd.Flags().StringSlice(\"platform\", []string{}, \"Convert content for a specific platform\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tcmd.Flags().Bool(\"all-platforms\", false, \"Convert content for all platforms\")\n\t// #endregion\n\n\treturn cmd\n}\n\nfunc convertOptions(cmd *cobra.Command) (types.ImageConvertOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\n\tprogressOutput := cmd.ErrOrStderr()\n\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\n\t// #region estargz flags\n\testargz, err := cmd.Flags().GetBool(\"estargz\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzRecordIn, err := cmd.Flags().GetString(\"estargz-record-in\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzCompressionLevel, err := cmd.Flags().GetInt(\"estargz-compression-level\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzChunkSize, err := cmd.Flags().GetInt(\"estargz-chunk-size\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzMinChunkSize, err := cmd.Flags().GetInt(\"estargz-min-chunk-size\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzExternalTOC, err := cmd.Flags().GetBool(\"estargz-external-toc\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzKeepDiffID, err := cmd.Flags().GetBool(\"estargz-keep-diff-id\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\testargzGzipHelper, err := cmd.Flags().GetString(\"estargz-gzip-helper\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region zstd flags\n\tzstd, err := cmd.Flags().GetBool(\"zstd\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tzstdCompressionLevel, err := cmd.Flags().GetInt(\"zstd-compression-level\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region zstd:chunked flags\n\tzstdchunked, err := cmd.Flags().GetBool(\"zstdchunked\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tzstdChunkedCompressionLevel, err := cmd.Flags().GetInt(\"zstdchunked-compression-level\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tzstdChunkedChunkSize, err := cmd.Flags().GetInt(\"zstdchunked-chunk-size\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tzstdChunkedRecordIn, err := cmd.Flags().GetString(\"zstdchunked-record-in\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region nydus flags\n\tnydus, err := cmd.Flags().GetBool(\"nydus\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tnydusBuilderPath, err := cmd.Flags().GetString(\"nydus-builder-path\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tnydusWorkDir, err := cmd.Flags().GetString(\"nydus-work-dir\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tnydusPrefetchPatterns, err := cmd.Flags().GetString(\"nydus-prefetch-patterns\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tnydusCompressor, err := cmd.Flags().GetString(\"nydus-compressor\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region overlaybd flags\n\toverlaybd, err := cmd.Flags().GetBool(\"overlaybd\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\toverlaybdFsType, err := cmd.Flags().GetString(\"overlaybd-fs-type\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\toverlaybdDbstr, err := cmd.Flags().GetString(\"overlaybd-dbstr\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region soci flags\n\tsoci, err := cmd.Flags().GetBool(\"soci\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tsociMinLayerSize, err := cmd.Flags().GetInt64(\"soci-min-layer-size\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tsociSpanSize, err := cmd.Flags().GetInt64(\"soci-span-size\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region generic flags\n\tuncompress, err := cmd.Flags().GetBool(\"uncompress\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\toci, err := cmd.Flags().GetBool(\"oci\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\n\t// #region platform flags\n\tplatforms, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\tallPlatforms, err := cmd.Flags().GetBool(\"all-platforms\")\n\tif err != nil {\n\t\treturn types.ImageConvertOptions{}, err\n\t}\n\t// #endregion\n\treturn types.ImageConvertOptions{\n\t\tGOptions: globalOptions,\n\t\tFormat:   format,\n\t\t// #region generic flags\n\t\tUncompress: uncompress,\n\t\tOci:        oci,\n\t\t// #endregion\n\t\t// #region platform flags\n\t\tPlatforms:    platforms,\n\t\tAllPlatforms: allPlatforms,\n\t\t// #endregion\n\t\t// Embed image format options\n\t\tEstargzOptions: types.EstargzOptions{\n\t\t\tEstargz:                 estargz,\n\t\t\tEstargzRecordIn:         estargzRecordIn,\n\t\t\tEstargzCompressionLevel: estargzCompressionLevel,\n\t\t\tEstargzChunkSize:        estargzChunkSize,\n\t\t\tEstargzMinChunkSize:     estargzMinChunkSize,\n\t\t\tEstargzExternalToc:      estargzExternalTOC,\n\t\t\tEstargzKeepDiffID:       estargzKeepDiffID,\n\t\t\tEstargzGzipHelper:       estargzGzipHelper,\n\t\t},\n\t\tZstdOptions: types.ZstdOptions{\n\t\t\tZstd:                 zstd,\n\t\t\tZstdCompressionLevel: zstdCompressionLevel,\n\t\t},\n\t\tZstdChunkedOptions: types.ZstdChunkedOptions{\n\t\t\tZstdChunked:                 zstdchunked,\n\t\t\tZstdChunkedCompressionLevel: zstdChunkedCompressionLevel,\n\t\t\tZstdChunkedChunkSize:        zstdChunkedChunkSize,\n\t\t\tZstdChunkedRecordIn:         zstdChunkedRecordIn,\n\t\t},\n\t\tNydusOptions: types.NydusOptions{\n\t\t\tNydus:                 nydus,\n\t\t\tNydusBuilderPath:      nydusBuilderPath,\n\t\t\tNydusWorkDir:          nydusWorkDir,\n\t\t\tNydusPrefetchPatterns: nydusPrefetchPatterns,\n\t\t\tNydusCompressor:       nydusCompressor,\n\t\t},\n\t\tOverlaybdOptions: types.OverlaybdOptions{\n\t\t\tOverlaybd:      overlaybd,\n\t\t\tOverlayFsType:  overlaybdFsType,\n\t\t\tOverlaydbDBStr: overlaybdDbstr,\n\t\t},\n\t\tSociConvertOptions: types.SociConvertOptions{\n\t\t\tSoci: soci,\n\t\t\tSociOptions: types.SociOptions{\n\t\t\t\tSpanSize:     sociSpanSize,\n\t\t\t\tMinLayerSize: sociMinLayerSize,\n\t\t\t\tPlatforms:    platforms,\n\t\t\t\tAllPlatforms: allPlatforms,\n\t\t\t},\n\t\t},\n\t\tProgressOutput: progressOutput,\n\t\tStdout:         cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc imageConvertAction(cmd *cobra.Command, args []string) error {\n\toptions, err := convertOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsrcRawRef := args[0]\n\tdestRawRef := args[1]\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.Convert(ctx, client, srcRawRef, destRawRef, options)\n}\n\nfunc imageConvertShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_convert_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestImageConvert(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\t// FIXME: windows does not support stargz\n\t\t\trequire.Not(require.Windows),\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t),\n\t\tNoParallel: true,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"--all-platforms\", testutil.CommonImage)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"esgz\",\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--oci\", \"--estargz\",\n\t\t\t\t\t\ttestutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"nydus\",\n\t\t\t\tRequire: require.All(\n\t\t\t\t\trequire.Binary(\"nydus-image\"),\n\t\t\t\t),\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--oci\", \"--nydus\",\n\t\t\t\t\t\ttestutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"zstd\",\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--oci\", \"--zstd\", \"--zstd-compression-level\", \"3\",\n\t\t\t\t\t\ttestutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"zstdchunked\",\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--oci\", \"--zstdchunked\", \"--zstdchunked-compression-level\", \"3\",\n\t\t\t\t\t\ttestutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"soci\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tRequire: require.All(\n\t\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\t\tnerdtest.Soci,\n\t\t\t\t\tnerdtest.SociVersion(\"0.10.0\"),\n\t\t\t\t),\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--soci\",\n\t\t\t\t\t\t\"--soci-span-size\", \"2097152\",\n\t\t\t\t\t\t\"--soci-min-layer-size\", \"0\",\n\t\t\t\t\t\ttestutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"soci with all-platforms\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tRequire: require.All(\n\t\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\t\tnerdtest.Soci,\n\t\t\t\t\tnerdtest.SociVersion(\"0.10.0\"),\n\t\t\t\t),\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--soci\", \"--all-platforms\",\n\t\t\t\t\t\t\"--soci-span-size\", \"2097152\",\n\t\t\t\t\t\t\"--soci-min-layer-size\", \"0\",\n\t\t\t\t\t\ttestutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n\n}\n\nfunc TestImageConvertNydusVerify(t *testing.T) {\n\tnerdtest.Setup()\n\n\tconst remoteImageKey = \"remoteImageKey\"\n\n\tvar reg *registry.Server\n\n\t// It is unclear what is problematic here, but we use the kernel version to discriminate against EL\n\t// See: https://github.com/containerd/nerdctl/issues/4332\n\ttestutil.RequireKernelVersion(t, \">= 6.0.0-0\")\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\trequire.Binary(\"nydus-image\"),\n\t\t\trequire.Binary(\"nydusify\"),\n\t\t\trequire.Binary(\"nydusd\"),\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\tnerdtest.Rootful,\n\t\t\tnerdtest.Registry,\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\treg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)\n\t\t\treg.Setup(data, helpers)\n\n\t\t\tdata.Labels().Set(remoteImageKey, fmt.Sprintf(\"%s:%d/nydusd-image:test\", \"localhost\", reg.Port))\n\t\t\thelpers.Ensure(\"image\", \"convert\", \"--nydus\", \"--oci\", testutil.CommonImage, data.Identifier(\"converted-image\"))\n\t\t\thelpers.Ensure(\"tag\", data.Identifier(\"converted-image\"), data.Labels().Get(remoteImageKey))\n\t\t\thelpers.Ensure(\"push\", data.Labels().Get(remoteImageKey))\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"converted-image\"))\n\t\t\tif reg != nil {\n\t\t\t\treg.Cleanup(data, helpers)\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(remoteImageKey))\n\t\t\t}\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tcmd := helpers.Custom(\"nydusify\",\n\t\t\t\t\"check\",\n\t\t\t\t\"--work-dir\",\n\t\t\t\tdata.Temp().Dir(\"nydusify-temp\"),\n\t\t\t\t\"--source\",\n\t\t\t\ttestutil.CommonImage,\n\t\t\t\t\"--target\",\n\t\t\t\tdata.Labels().Get(remoteImageKey),\n\t\t\t\t\"--source-insecure\",\n\t\t\t\t\"--target-insecure\",\n\t\t\t)\n\t\t\tcmd.WithTimeout(30 * time.Second)\n\t\t\treturn cmd\n\t\t},\n\t\tExpected: test.Expects(0, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_cryptutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\n// registerImgcryptFlags register flags that correspond to parseImgcryptFlags().\n// Platform flags are registered too.\n//\n// From:\n// - https://github.com/containerd/imgcrypt/blob/v1.1.2/cmd/ctr/commands/flags/flags.go#L23-L44 (except skip-decrypt-auth)\n// - https://github.com/containerd/imgcrypt/blob/v1.1.2/cmd/ctr/commands/images/encrypt.go#L52-L55\nfunc registerImgcryptFlags(cmd *cobra.Command, encrypt bool) {\n\tflags := cmd.Flags()\n\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tflags.StringSlice(\"platform\", []string{}, \"Convert content for a specific platform\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tflags.Bool(\"all-platforms\", false, \"Convert content for all platforms\")\n\t// #endregion\n\n\tflags.String(\"gpg-homedir\", \"\", \"The GPG homedir to use; by default gpg uses ~/.gnupg\")\n\tflags.String(\"gpg-version\", \"\", \"The GPG version (\\\"v1\\\" or \\\"v2\\\"), default will make an educated guess\")\n\tflags.StringSlice(\"key\", []string{}, \"A secret key's filename and an optional password separated by colon; this option may be provided multiple times\")\n\t// While --recipient can be specified only for `nerdctl image encrypt`,\n\t// --dec-recipient can be specified for both `nerdctl image encrypt` and `nerdctl image decrypt`.\n\tflags.StringSlice(\"dec-recipient\", []string{}, \"Recipient of the image; used only for PKCS7 and must be an x509 certificate\")\n\n\tif encrypt {\n\t\t// recipient is defined as StringSlice, not StringArray, to allow specifying \"--recipient=jwe:FILE1,jwe:FILE2\"\n\t\tflags.StringSlice(\"recipient\", []string{}, \"Recipient of the image is the person who can decrypt it in the form specified above (i.e. jwe:/path/to/pubkey)\")\n\t}\n}\n\nfunc cryptOptions(cmd *cobra.Command, args []string, encrypt bool) (types.ImageCryptOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tplatforms, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tallPlatforms, err := cmd.Flags().GetBool(\"all-platforms\")\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tgpgHomeDir, err := cmd.Flags().GetString(\"gpg-homedir\")\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tgpgVersion, err := cmd.Flags().GetString(\"gpg-version\")\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tkeys, err := cmd.Flags().GetStringSlice(\"key\")\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tdecRecipients, err := cmd.Flags().GetStringSlice(\"dec-recipient\")\n\tif err != nil {\n\t\treturn types.ImageCryptOptions{}, err\n\t}\n\tvar recipients []string\n\tif encrypt {\n\t\trecipients, err = cmd.Flags().GetStringSlice(\"recipient\")\n\t\tif err != nil {\n\t\t\treturn types.ImageCryptOptions{}, err\n\t\t}\n\t}\n\treturn types.ImageCryptOptions{\n\t\tGOptions:      globalOptions,\n\t\tPlatforms:     platforms,\n\t\tAllPlatforms:  allPlatforms,\n\t\tGpgHomeDir:    gpgHomeDir,\n\t\tGpgVersion:    gpgVersion,\n\t\tKeys:          keys,\n\t\tDecRecipients: decRecipients,\n\t\tRecipients:    recipients,\n\t\tStdout:        cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc getImgcryptAction(encrypt bool) func(cmd *cobra.Command, args []string) error {\n\treturn func(cmd *cobra.Command, args []string) error {\n\t\toptions, err := cryptOptions(cmd, args, encrypt)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsrcRawRef := args[0]\n\t\ttargetRawRef := args[1]\n\n\t\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer cancel()\n\n\t\treturn image.Crypt(ctx, client, srcRawRef, targetRawRef, encrypt, options)\n\t}\n}\n\nfunc imgcryptShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_decrypt.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nconst imageDecryptHelp = `Decrypt an image locally.\n\nUse '--key' to specify the private keys.\nPrivate keys in PEM format may be encrypted and the password may be passed\nalong in any of the following formats:\n- <filename>:<password>\n- <filename>:pass=<password>\n- <filename>:fd=<file descriptor> (not available for rootless mode)\n- <filename>:filename=<password file>\n\nUse '--platform' to define the platforms to decrypt. Defaults to the host platform.\nWhen '--all-platforms' is given all images in a manifest list must be available.\nUnspecified platforms are omitted from the output image.\n\nExample (encrypt):\n  openssl genrsa -out mykey.pem\n  openssl rsa -in mykey.pem -pubout -out mypubkey.pem\n  nerdctl image encrypt --recipient=jwe:mypubkey.pem --platform=linux/amd64,linux/arm64 foo example.com/foo:encrypted\n  nerdctl push example.com/foo:encrypted\n\nExample (decrypt):\n  nerdctl pull --unpack=false example.com/foo:encrypted\n  nerdctl image decrypt --key=mykey.pem example.com/foo:encrypted foo:decrypted\n`\n\nfunc decryptCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"decrypt [flags] <source_ref> <target_ref>...\",\n\t\tShort:             \"decrypt an image\",\n\t\tLong:              imageDecryptHelp,\n\t\tArgs:              cobra.MinimumNArgs(2),\n\t\tRunE:              getImgcryptAction(false),\n\t\tValidArgsFunction: imgcryptShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tregisterImgcryptFlags(cmd, false)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_encrypt.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nconst imageEncryptHelp = `Encrypt image layers.\n\nUse '--recipient' to specify the recipients.\nThe following protocol prefixes are supported:\n- pgp:<email-address>\n- jwe:<public-key-file-path>\n- pkcs7:<x509-file-path>\n\nUse '--platform' to define the platforms to encrypt. Defaults to the host platform.\nWhen '--all-platforms' is given all images in a manifest list must be available.\nUnspecified platforms are omitted from the output image.\n\nExample:\n  openssl genrsa -out mykey.pem\n  openssl rsa -in mykey.pem -pubout -out mypubkey.pem\n  nerdctl image encrypt --recipient=jwe:mypubkey.pem --platform=linux/amd64,linux/arm64 foo example.com/foo:encrypted\n  nerdctl push example.com/foo:encrypted\n\nTo run the encrypted image, put the private key file (mykey.pem) to /etc/containerd/ocicrypt/keys (rootful) or ~/.config/containerd/ocicrypt/keys (rootless).\ncontainerd before v1.4 requires extra configuration steps, see https://github.com/containerd/nerdctl/blob/main/docs/ocicrypt.md\n\nCAUTION: This command only encrypts image layers, but does NOT encrypt container configuration such as 'Env' and 'Cmd'.\nTo see non-encrypted information, run 'nerdctl image inspect --mode=native --platform=PLATFORM example.com/foo:encrypted' .\n`\n\nfunc encryptCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"encrypt [flags] <source_ref> <target_ref>...\",\n\t\tShort:             \"encrypt image layers\",\n\t\tLong:              imageEncryptHelp,\n\t\tArgs:              cobra.MinimumNArgs(2),\n\t\tRunE:              getImgcryptAction(true),\n\t\tValidArgsFunction: imgcryptShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tregisterImgcryptFlags(cmd, true)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_encrypt_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestImageEncryptJWE(t *testing.T) {\n\tnerdtest.Setup()\n\n\tvar reg *registry.Server\n\n\tconst remoteImageKey = \"remoteImageKey\"\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t// This test needs to rmi the common image\n\t\t\tnerdtest.Private,\n\t\t\tnerdtest.Registry,\n\t\t),\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif reg != nil {\n\t\t\t\treg.Cleanup(data, helpers)\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(remoteImageKey))\n\t\t\t}\n\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"decrypted\"))\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tpri, pub := nerdtest.GenerateJWEKeyPair(data, helpers)\n\t\t\tdata.Labels().Set(\"private\", pri)\n\t\t\tdata.Labels().Set(\"public\", pub)\n\n\t\t\treg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)\n\t\t\treg.Setup(data, helpers)\n\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\tencryptImageRef := fmt.Sprintf(\"127.0.0.1:%d/%s:encrypted\", reg.Port, data.Identifier())\n\t\t\thelpers.Ensure(\"image\", \"encrypt\", \"--recipient=jwe:\"+pub, testutil.CommonImage, encryptImageRef)\n\t\t\tinspector := helpers.Capture(\"image\", \"inspect\", \"--mode=native\", \"--format={{len .Index.Manifests}}\", encryptImageRef)\n\t\t\tassert.Equal(t, inspector, \"1\\n\")\n\t\t\tinspector = helpers.Capture(\"image\", \"inspect\", \"--mode=native\", \"--format={{json .Manifest.Layers}}\", encryptImageRef)\n\t\t\tassert.Assert(t, strings.Contains(inspector, \"org.opencontainers.image.enc.keys.jwe\"))\n\t\t\thelpers.Ensure(\"push\", encryptImageRef)\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", encryptImageRef)\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\tdata.Labels().Set(remoteImageKey, encryptImageRef)\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\thelpers.Fail(\"pull\", data.Labels().Get(remoteImageKey))\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"--unpack=false\", data.Labels().Get(remoteImageKey))\n\t\t\thelpers.Fail(\"image\", \"decrypt\", \"--key=\"+data.Labels().Get(\"public\"), data.Labels().Get(remoteImageKey), data.Identifier(\"decrypted\")) // decryption needs prv key, not pub key\n\t\t\treturn helpers.Command(\"image\", \"decrypt\", \"--key=\"+data.Labels().Get(\"private\"), data.Labels().Get(remoteImageKey), data.Identifier(\"decrypted\"))\n\t\t},\n\t\tExpected: test.Expects(0, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_history.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/image-spec/identity\"\n\t\"github.com/spf13/cobra\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\nfunc HistoryCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"history [flags] IMAGE\",\n\t\tShort:             \"Show the history of an image\",\n\t\tArgs:              helpers.IsExactArgs(1),\n\t\tRunE:              historyAction,\n\t\tValidArgsFunction: historyShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\taddHistoryFlags(cmd)\n\treturn cmd\n}\n\nfunc addHistoryFlags(cmd *cobra.Command) {\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only show numeric IDs\")\n\tcmd.Flags().BoolP(\"human\", \"H\", true, \"Print sizes and dates in human readable format (default true)\")\n\tcmd.Flags().Bool(\"no-trunc\", false, \"Don't truncate output\")\n}\n\ntype historyPrintable struct {\n\tcreationTime *time.Time\n\tsize         int64\n\n\tSnapshot     string\n\tCreatedAt    string\n\tCreatedSince string\n\tCreatedBy    string\n\tSize         string\n\tComment      string\n}\n\nfunc historyAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\twalker := &imagewalker.ImageWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found imagewalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\timg := containerd.NewImage(client, found.Image)\n\t\t\timageConfig, _, err := imgutil.ReadImageConfig(ctx, img)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to ReadImageConfig: %w\", err)\n\t\t\t}\n\t\t\tconfigHistories := imageConfig.History\n\t\t\tlayerCounter := 0\n\t\t\tdiffIDs, err := img.RootFS(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to get diffIDS: %w\", err)\n\t\t\t}\n\t\t\tvar historys []historyPrintable\n\t\t\tfor _, h := range configHistories {\n\t\t\t\tvar size int64\n\t\t\t\tvar snapshotName string\n\t\t\t\tif !h.EmptyLayer {\n\t\t\t\t\tif len(diffIDs) <= layerCounter {\n\t\t\t\t\t\treturn fmt.Errorf(\"too many non-empty layers in History section\")\n\t\t\t\t\t}\n\t\t\t\t\tdiffIDs := diffIDs[0 : layerCounter+1]\n\t\t\t\t\tchainID := identity.ChainID(diffIDs).String()\n\n\t\t\t\t\ts := client.SnapshotService(globalOptions.Snapshotter)\n\t\t\t\t\tstat, err := s.Stat(ctx, chainID)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to get stat: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\tuse, err := s.Usage(ctx, chainID)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to get usage: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\tsize = use.Size\n\t\t\t\t\tsnapshotName = stat.Name\n\t\t\t\t\tlayerCounter++\n\t\t\t\t} else {\n\t\t\t\t\tsize = 0\n\t\t\t\t\tsnapshotName = \"<missing>\"\n\t\t\t\t}\n\t\t\t\thistory := historyPrintable{\n\t\t\t\t\tcreationTime: h.Created,\n\t\t\t\t\tsize:         size,\n\t\t\t\t\tSnapshot:     snapshotName,\n\t\t\t\t\tCreatedBy:    h.CreatedBy,\n\t\t\t\t\tComment:      h.Comment,\n\t\t\t\t}\n\t\t\t\thistorys = append(historys, history)\n\t\t\t}\n\t\t\terr = printHistory(cmd, historys)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed printHistory: %w\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, args, true)\n}\n\ntype historyPrinter struct {\n\tw                     io.Writer\n\tquiet, noTrunc, human bool\n\ttmpl                  *template.Template\n}\n\nfunc printHistory(cmd *cobra.Command, historys []historyPrintable) error {\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoTrunc, err := cmd.Flags().GetBool(\"no-trunc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\thuman, err := cmd.Flags().GetBool(\"human\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar w io.Writer\n\tw = os.Stdout\n\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar tmpl *template.Template\n\tswitch format {\n\tcase \"\", \"table\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !quiet {\n\t\t\tfmt.Fprintln(w, \"SNAPSHOT\\tCREATED\\tCREATED BY\\tSIZE\\tCOMMENT\")\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\tquiet = false\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tprinter := &historyPrinter{\n\t\tw:       w,\n\t\tquiet:   quiet,\n\t\tnoTrunc: noTrunc,\n\t\thuman:   human,\n\t\ttmpl:    tmpl,\n\t}\n\n\tfor index := len(historys) - 1; index >= 0; index-- {\n\t\tif err := printer.printHistory(historys[index]); err != nil {\n\t\t\tlog.L.Warn(err)\n\t\t}\n\t}\n\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n\nfunc (x *historyPrinter) printHistory(printable historyPrintable) error {\n\t// Truncate long values unless --no-trunc is passed\n\tif !x.noTrunc {\n\t\tif len(printable.CreatedBy) > 45 {\n\t\t\tprintable.CreatedBy = printable.CreatedBy[0:44] + \"…\"\n\t\t}\n\t\t// Do not truncate snapshot id if quiet is being passed\n\t\tif !x.quiet && len(printable.Snapshot) > 45 {\n\t\t\tprintable.Snapshot = printable.Snapshot[0:44] + \"…\"\n\t\t}\n\t}\n\n\t// Format date and size for display based on --human preference\n\tprintable.CreatedAt = printable.creationTime.Local().Format(time.RFC3339)\n\tif x.human {\n\t\tprintable.CreatedSince = formatter.TimeSinceInHuman(*printable.creationTime)\n\t\tprintable.Size = units.HumanSize(float64(printable.size))\n\t} else {\n\t\tprintable.CreatedSince = printable.CreatedAt\n\t\tprintable.Size = strconv.FormatInt(printable.size, 10)\n\t}\n\n\tif x.tmpl != nil {\n\t\tvar b bytes.Buffer\n\t\tif err := x.tmpl.Execute(&b, printable); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := fmt.Fprintln(x.w, b.String()); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if x.quiet {\n\t\tif _, err := fmt.Fprintln(x.w, printable.Snapshot); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif _, err := fmt.Fprintf(x.w, \"%s\\t%s\\t%s\\t%s\\t%s\\n\",\n\t\t\tprintable.Snapshot,\n\t\t\tprintable.CreatedSince,\n\t\t\tprintable.CreatedBy,\n\t\t\tprintable.Size,\n\t\t\tprintable.Comment,\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc historyShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_history_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\ntype historyObj struct {\n\tSnapshot     string\n\tCreatedAt    string\n\tCreatedSince string\n\tCreatedBy    string\n\tSize         string\n\tComment      string\n}\n\nconst createdAt1 = \"2021-03-31T10:21:21-07:00\"\nconst createdAt2 = \"2021-03-31T10:21:23-07:00\"\n\n// Expected content of the common image on arm64\nvar (\n\tcreatedAtTime, _ = time.Parse(time.RFC3339, createdAt2)\n\texpectedHistory  = []historyObj{\n\t\t{\n\t\t\tCreatedBy:    \"/bin/sh -c #(nop)  CMD [\\\"/bin/sh\\\"]\",\n\t\t\tSize:         \"0B\",\n\t\t\tCreatedAt:    createdAt2,\n\t\t\tSnapshot:     \"<missing>\",\n\t\t\tComment:      \"\",\n\t\t\tCreatedSince: formatter.TimeSinceInHuman(createdAtTime),\n\t\t},\n\t\t{\n\t\t\tCreatedBy:    \"/bin/sh -c #(nop) ADD file:3b16ffee2b26d8af5…\",\n\t\t\tSize:         \"5.947MB\",\n\t\t\tCreatedAt:    createdAt1,\n\t\t\tSnapshot:     \"sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c…\",\n\t\t\tComment:      \"\",\n\t\t\tCreatedSince: formatter.TimeSinceInHuman(createdAtTime),\n\t\t},\n\t}\n\texpectedHistoryNoTrunc = []historyObj{\n\t\t{\n\t\t\tSnapshot: \"<missing>\",\n\t\t\tSize:     \"0\",\n\t\t},\n\t\t{\n\t\t\tSnapshot:  \"sha256:56bf55b8eed1f0b4794a30386e4d1d3da949c25bcb5155e898097cd75dc77c2a\",\n\t\t\tCreatedBy: \"/bin/sh -c #(nop) ADD file:3b16ffee2b26d8af5db152fcc582aaccd9e1ec9e3343874e9969a205550fe07d in / \",\n\t\t\tSize:      \"5947392\",\n\t\t},\n\t}\n)\n\nfunc decode(stdout string) ([]historyObj, error) {\n\tdec := json.NewDecoder(strings.NewReader(stdout))\n\tobject := []historyObj{}\n\tfor {\n\t\tvar v historyObj\n\t\tif err := dec.Decode(&v); err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn nil, errors.New(\"failed to decode history object\")\n\t\t}\n\t\tobject = append(object, v)\n\t}\n\n\treturn object, nil\n}\n\nfunc TestImageHistory(t *testing.T) {\n\t// Here are the current issues with regard to docker true compatibility:\n\t// - we have a different definition of what a layer id is (snapshot vs. id)\n\t//     this will require indepth convergence when moby will handle multi-platform images\n\t// - our definition of size is different\n\t//     this requires some investigation to figure out why it differs\n\t//     possibly one is unpacked on the filessystem while the other is the tar file size?\n\t// - we do not truncate ids when --quiet has been provided\n\t//     this is a conscious decision here - truncating with --quiet does not make much sense\n\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t// XXX the results here are obviously platform dependent - and it seems like windows cannot pull a linux image?\n\t\t\trequire.Not(require.Windows),\n\t\t\t// XXX Currently, history does not work on non-native platform, so, we cannot test reliably on other platforms\n\t\t\trequire.Arm64,\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t// XXX: despite efforts to isolate this test, it keeps on having side effects linked to\n\t\t\t// https://github.com/containerd/nerdctl/issues/3512\n\t\t\t// Isolating it into a completely different root is the last ditched attempt at avoiding the issue\n\t\t\thelpers.Write(nerdtest.DataRoot, test.ConfigValue(data.Temp().Path()))\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"--platform\", \"linux/arm64\", testutil.CommonImage)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"trunc, no quiet, human\",\n\t\t\t\tCommand:     test.Command(\"image\", \"history\", \"--human=true\", \"--format=json\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\thistory, err := decode(stdout)\n\t\t\t\t\tassert.NilError(t, err, \"decode should not fail\")\n\t\t\t\t\tassert.Equal(t, len(history), 2, \"history should be 2 in length\")\n\n\t\t\t\t\th0Time, _ := time.Parse(time.RFC3339, history[0].CreatedAt)\n\t\t\t\t\th1Time, _ := time.Parse(time.RFC3339, history[1].CreatedAt)\n\t\t\t\t\tcomp0Time, _ := time.Parse(time.RFC3339, expectedHistory[0].CreatedAt)\n\t\t\t\t\tcomp1Time, _ := time.Parse(time.RFC3339, expectedHistory[1].CreatedAt)\n\n\t\t\t\t\tassert.Equal(t, h0Time.UTC().String(), comp0Time.UTC().String())\n\t\t\t\t\tassert.Equal(t, history[0].CreatedBy, expectedHistory[0].CreatedBy)\n\t\t\t\t\tassert.Equal(t, history[0].Size, expectedHistory[0].Size)\n\t\t\t\t\tassert.Equal(t, history[0].CreatedSince, expectedHistory[0].CreatedSince)\n\t\t\t\t\tassert.Equal(t, history[0].Snapshot, expectedHistory[0].Snapshot)\n\t\t\t\t\tassert.Equal(t, history[0].Comment, expectedHistory[0].Comment)\n\n\t\t\t\t\tassert.Equal(t, h1Time.UTC().String(), comp1Time.UTC().String())\n\t\t\t\t\tassert.Equal(t, history[1].CreatedBy, expectedHistory[1].CreatedBy)\n\t\t\t\t\tassert.Equal(t, history[1].Size, expectedHistory[1].Size)\n\t\t\t\t\tassert.Equal(t, history[1].CreatedSince, expectedHistory[1].CreatedSince)\n\t\t\t\t\tassert.Equal(t, history[1].Snapshot, expectedHistory[1].Snapshot)\n\t\t\t\t\tassert.Equal(t, history[1].Comment, expectedHistory[1].Comment)\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"no human - dates and sizes are not prettyfied\",\n\t\t\t\tCommand:     test.Command(\"image\", \"history\", \"--human=false\", \"--format=json\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\thistory, err := decode(stdout)\n\t\t\t\t\tassert.NilError(t, err, \"decode should not fail\")\n\t\t\t\t\tassert.Equal(t, history[0].Size, expectedHistoryNoTrunc[0].Size)\n\t\t\t\t\tassert.Equal(t, history[0].CreatedSince, history[0].CreatedAt)\n\t\t\t\t\tassert.Equal(t, history[1].Size, expectedHistoryNoTrunc[1].Size)\n\t\t\t\t\tassert.Equal(t, history[1].CreatedSince, history[1].CreatedAt)\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"no trunc - do not truncate sha or cmd\",\n\t\t\t\tCommand:     test.Command(\"image\", \"history\", \"--human=false\", \"--no-trunc\", \"--format=json\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\thistory, err := decode(stdout)\n\t\t\t\t\tassert.NilError(t, err, \"decode should not fail\")\n\t\t\t\t\tassert.Equal(t, history[1].Snapshot, expectedHistoryNoTrunc[1].Snapshot)\n\t\t\t\t\tassert.Equal(t, history[1].CreatedBy, expectedHistoryNoTrunc[1].CreatedBy)\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Quiet has no effect with format, so, go no-json, no-trunc\",\n\t\t\t\tCommand:     test.Command(\"image\", \"history\", \"--human=false\", \"--no-trunc\", \"--quiet\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\tassert.Equal(t, stdout, expectedHistoryNoTrunc[0].Snapshot+\"\\n\"+expectedHistoryNoTrunc[1].Snapshot+\"\\n\")\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"With quiet, trunc has no effect\",\n\t\t\t\tCommand:     test.Command(\"image\", \"history\", \"--human=false\", \"--no-trunc\", \"--quiet\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\tassert.Equal(t, stdout, expectedHistoryNoTrunc[0].Snapshot+\"\\n\"+expectedHistoryNoTrunc[1].Snapshot+\"\\n\")\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_import.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nfunc ImportCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]\",\n\t\tShort:             \"Import the contents from a tarball to create a filesystem image\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              importAction,\n\t\tValidArgsFunction: imageImportShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\tcmd.Flags().StringP(\"message\", \"m\", \"\", \"Set commit message for imported image\")\n\tcmd.Flags().String(\"platform\", \"\", \"Set platform for imported image (e.g., linux/amd64)\")\n\treturn cmd\n}\n\nfunc importOptions(cmd *cobra.Command, args []string) (types.ImageImportOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImageImportOptions{}, err\n\t}\n\tmessage, err := cmd.Flags().GetString(\"message\")\n\tif err != nil {\n\t\treturn types.ImageImportOptions{}, err\n\t}\n\tplatform, err := cmd.Flags().GetString(\"platform\")\n\tif err != nil {\n\t\treturn types.ImageImportOptions{}, err\n\t}\n\tvar reference string\n\tif len(args) > 1 {\n\t\treference = args[1]\n\t}\n\n\tvar in io.ReadCloser\n\tsrc := args[0]\n\tswitch {\n\tcase src == \"-\":\n\t\tin = io.NopCloser(cmd.InOrStdin())\n\tcase hasHTTPPrefix(src):\n\t\tresp, err := http.Get(src)\n\t\tif err != nil {\n\t\t\treturn types.ImageImportOptions{}, err\n\t\t}\n\t\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\t\tdefer resp.Body.Close()\n\t\t\treturn types.ImageImportOptions{}, fmt.Errorf(\"failed to download %s: %s\", src, resp.Status)\n\t\t}\n\t\tin = resp.Body\n\tdefault:\n\t\tf, err := os.Open(src)\n\t\tif err != nil {\n\t\t\treturn types.ImageImportOptions{}, err\n\t\t}\n\t\tin = f\n\t}\n\n\treturn types.ImageImportOptions{\n\t\tStdout:    cmd.OutOrStdout(),\n\t\tStdin:     in,\n\t\tGOptions:  globalOptions,\n\t\tSource:    args[0],\n\t\tReference: reference,\n\t\tMessage:   message,\n\t\tPlatform:  platform,\n\t}, nil\n}\n\nfunc importAction(cmd *cobra.Command, args []string) error {\n\topt, err := importOptions(cmd, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), opt.GOptions.Namespace, opt.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\tdefer func() {\n\t\tif rc, ok := opt.Stdin.(io.ReadCloser); ok {\n\t\t\t_ = rc.Close()\n\t\t}\n\t}()\n\n\tname, err := image.Import(ctx, client, opt)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = cmd.OutOrStdout().Write([]byte(name + \"\\n\"))\n\treturn err\n}\n\nfunc imageImportShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n\nfunc hasHTTPPrefix(s string) bool {\n\treturn strings.HasPrefix(s, \"http://\") || strings.HasPrefix(s, \"https://\")\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_import_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"archive/tar\"\n\t\"bytes\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// minimalRootfsTar returns a valid tar archive with no files.\nfunc minimalRootfsTar(t *testing.T) *bytes.Buffer {\n\tt.Helper()\n\tbuf := new(bytes.Buffer)\n\ttw := tar.NewWriter(buf)\n\tassert.NilError(t, tw.Close())\n\treturn buf\n}\n\nfunc TestImageImportErrors(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"TestImageImportErrors\",\n\t\tRequire:     require.Linux,\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"import\", \"\", \"image:tag\")\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tExitCode: 1,\n\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t}\n\t\t},\n\t\tData: test.WithLabels(map[string]string{\n\t\t\t\"error\": \"no such file or directory\",\n\t\t}),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImageImport(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tvar stopServer func()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"image import from stdin\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"import\", \"-\", data.Identifier())\n\t\t\t\tcmd.Feed(bytes.NewReader(minimalRootfsTar(t).Bytes()))\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgs := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgs, identifier))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"image import from file\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tp := filepath.Join(data.Temp().Path(), \"rootfs.tar\")\n\t\t\t\tassert.NilError(t, os.WriteFile(p, minimalRootfsTar(t).Bytes(), 0644))\n\t\t\t\tdata.Labels().Set(\"tar\", p)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"import\", data.Labels().Get(\"tar\"), data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgs := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgs, identifier))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"image import with message\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"import\", \"-m\", \"A message\", \"-\", data.Identifier())\n\t\t\t\tcmd.Feed(bytes.NewReader(minimalRootfsTar(t).Bytes()))\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tidentifier := data.Identifier() + \":latest\"\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timg := nerdtest.InspectImage(helpers, identifier)\n\t\t\t\t\t\t\tassert.Equal(t, img.Comment, \"A message\")\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"image import with platform\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"import\", \"--platform\", \"linux/amd64\", \"-\", data.Identifier())\n\t\t\t\tcmd.Feed(bytes.NewReader(minimalRootfsTar(t).Bytes()))\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tidentifier := data.Identifier() + \":latest\"\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timg := nerdtest.InspectImage(helpers, identifier)\n\t\t\t\t\t\t\tassert.Equal(t, img.Architecture, \"amd64\")\n\t\t\t\t\t\t\tassert.Equal(t, img.Os, \"linux\")\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"image import from URL\",\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif stopServer != nil {\n\t\t\t\t\tstopServer()\n\t\t\t\t\tstopServer = nil\n\t\t\t\t}\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tw.Header().Set(\"Content-Type\", \"application/x-tar\")\n\t\t\t\t\t_, _ = w.Write(minimalRootfsTar(t).Bytes())\n\t\t\t\t})\n\t\t\t\turl, stop, err := nerdtest.StartHTTPServer(handler)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tstopServer = stop\n\t\t\t\tdata.Labels().Set(\"url\", url)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"import\", data.Labels().Get(\"url\"), data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgs := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgs, identifier))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"inspect [flags] IMAGE [IMAGE...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Display detailed information on one or more images.\",\n\t\tLong:              \"Hint: set `--mode=native` for showing the full output\",\n\t\tRunE:              imageInspectAction,\n\t\tValidArgsFunction: imageInspectShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"mode\", \"dockercompat\", `Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output`)\n\tcmd.RegisterFlagCompletionFunc(\"mode\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"dockercompat\", \"native\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\n\t// #region platform flags\n\tcmd.Flags().String(\"platform\", \"\", \"Inspect a specific platform\") // not a slice, and there is no --all-platforms\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\t// #endregion\n\n\treturn cmd\n}\n\nfunc InspectOptions(cmd *cobra.Command, platform *string) (types.ImageInspectOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImageInspectOptions{}, err\n\t}\n\tmode, err := cmd.Flags().GetString(\"mode\")\n\tif err != nil {\n\t\treturn types.ImageInspectOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.ImageInspectOptions{}, err\n\t}\n\tif platform == nil {\n\t\ttempPlatform, err := cmd.Flags().GetString(\"platform\")\n\t\tif err != nil {\n\t\t\treturn types.ImageInspectOptions{}, err\n\t\t}\n\t\tplatform = &tempPlatform\n\t}\n\treturn types.ImageInspectOptions{\n\t\tGOptions: globalOptions,\n\t\tMode:     mode,\n\t\tFormat:   format,\n\t\tPlatform: *platform,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc imageInspectAction(cmd *cobra.Command, args []string) error {\n\toptions, err := InspectOptions(cmd, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Verify we have a valid mode\n\tif options.Mode != \"native\" && options.Mode != \"dockercompat\" {\n\t\treturn fmt.Errorf(\"unknown mode %q\", options.Mode)\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClientWithPlatform(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address, options.Platform)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tentries, err := image.Inspect(ctx, client, args, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Display\n\tif len(entries) > 0 {\n\t\tif formatErr := formatter.FormatSlice(options.Format, options.Stdout, entries); formatErr != nil {\n\t\t\tlog.G(ctx).Error(formatErr)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc imageInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_inspect_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestImageInspectSimpleCases(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Contains some stuff\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\tassert.Assert(t, len(dc[0].RootFS.Layers) > 0, \"there should be at least one rootfs layer\\n\")\n\t\t\t\t\tassert.Assert(t, dc[0].Architecture != \"\", \"architecture should be set\\n\")\n\t\t\t\t\tassert.Assert(t, dc[0].Size > 0, \"size should be > 0 \\n\")\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"RawFormat support (.Id)\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", testutil.CommonImage, \"--format\", \"{{.Id}}\"),\n\t\t\t\tExpected:    test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"typedFormat support (.ID)\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", testutil.CommonImage, \"--format\", \"{{.ID}}\"),\n\t\t\t\tExpected:    test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Config.Image field is set\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", testutil.CommonImage),\n\t\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\tassert.Assert(t, dc[0].Config != nil, \"image Config should not be nil\")\n\t\t\t\t\tassert.Assert(t, dc[0].Config.Image != \"\", \"Config.Image should not be empty\")\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Error for image not found\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", \"dne:latest\", \"dne2:latest\"),\n\t\t\t\tExpected: test.Expects(1, []error{\n\t\t\t\t\terrors.New(\"no such image: dne:latest\"),\n\t\t\t\t\terrors.New(\"no such image: dne2:latest\"),\n\t\t\t\t}, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\ttestCase.Require = nerdtest.IsFlaky(\"https://github.com/containerd/nerdctl/issues/3524\")\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImageInspectDifferentValidReferencesForTheSameImage(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttags := []string{\n\t\t\"\",\n\t\t\":latest\",\n\t}\n\tnames := []string{\n\t\t\"busybox\",\n\t\t\"docker.io/library/busybox\",\n\t\t\"registry-1.docker.io/library/busybox\",\n\t}\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t// FIXME: this test depends on hub images that do not have windows versions\n\t\t\trequire.Not(require.Windows),\n\t\t\t// We need a clean slate\n\t\t\tnerdtest.Private,\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"alpine\")\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"busybox\")\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"registry-1.docker.io/library/busybox\")\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"name and tags +/- sha combinations\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", \"busybox\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\t\treference := dc[0].ID\n\t\t\t\t\t\t\tsha := strings.TrimPrefix(dc[0].RepoDigests[0], \"busybox@sha256:\")\n\n\t\t\t\t\t\t\tfor _, name := range names {\n\t\t\t\t\t\t\t\tfor _, tag := range tags {\n\t\t\t\t\t\t\t\t\tit := nerdtest.InspectImage(helpers, name+tag)\n\t\t\t\t\t\t\t\t\tassert.Equal(t, it.ID, reference)\n\t\t\t\t\t\t\t\t\tit = nerdtest.InspectImage(helpers, name+tag+\"@sha256:\"+sha)\n\t\t\t\t\t\t\t\t\tassert.Equal(t, it.ID, reference)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"by digest, short or long, with or without prefix\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", \"busybox\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\t\treference := dc[0].ID\n\t\t\t\t\t\t\tsha := strings.TrimPrefix(dc[0].RepoDigests[0], \"busybox@sha256:\")\n\n\t\t\t\t\t\t\tfor _, id := range []string{\"sha256:\" + sha, sha, sha[0:8], \"sha256:\" + sha[0:8]} {\n\t\t\t\t\t\t\t\tit := nerdtest.InspectImage(helpers, id)\n\t\t\t\t\t\t\t\tassert.Equal(t, it.ID, reference)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Now, tag alpine with a short id\n\t\t\t\t\t\t\t// Build reference values for comparison\n\t\t\t\t\t\t\talpine := nerdtest.InspectImage(helpers, \"alpine\")\n\n\t\t\t\t\t\t\t// Demonstrate image name precedence over digest lookup\n\t\t\t\t\t\t\t// Using the shortened sha should no longer get busybox, but rather the newly tagged Alpine\n\t\t\t\t\t\t\t// FIXME: this is triggering https://github.com/containerd/nerdctl/issues/3016\n\t\t\t\t\t\t\t// We cannot get rid of that image now, which does break local testing\n\t\t\t\t\t\t\thelpers.Ensure(\"tag\", \"alpine\", sha[0:8])\n\t\t\t\t\t\t\tit := nerdtest.InspectImage(helpers, sha[0:8])\n\t\t\t\t\t\t\tassert.Equal(t, it.ID, alpine.ID)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"prove that wrong references with correct digest do not get resolved\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", \"busybox\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\t\tsha := strings.TrimPrefix(dc[0].RepoDigests[0], \"busybox@sha256:\")\n\n\t\t\t\t\t\t\tfor _, id := range []string{\"doesnotexist\", \"doesnotexist:either\", \"busybox:bogustag\"} {\n\t\t\t\t\t\t\t\tcmd := helpers.Command(\"image\", \"inspect\", id+\"@sha256:\"+sha)\n\t\t\t\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\t\t\tErrors:   []error{fmt.Errorf(\"no such image: %s@sha256:%s\", id, sha)},\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"prove that invalid reference return no result without crashing\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", \"busybox\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\n\t\t\t\t\t\t\tfor _, id := range []string{\"∞∞∞∞∞∞∞∞∞∞\", \"busybox:∞∞∞∞∞∞∞∞∞∞\"} {\n\t\t\t\t\t\t\t\tcmd := helpers.Command(\"image\", \"inspect\", id)\n\t\t\t\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\t\t\tErrors:   []error{fmt.Errorf(\"invalid reference format: %s\", id)},\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"retrieving multiple entries at once\",\n\t\t\t\tCommand:     test.Command(\"image\", \"inspect\", \"busybox\", \"busybox\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tvar dc []dockercompat.Image\n\t\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\t\tassert.Equal(t, 2, len(dc), \"Unexpectedly did not get 2 results\\n\")\n\t\t\t\t\t\t\treference := nerdtest.InspectImage(helpers, \"busybox\")\n\t\t\t\t\t\t\tassert.Equal(t, dc[0].ID, reference.ID)\n\t\t\t\t\t\t\tassert.Equal(t, dc[1].ID, reference.ID)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc ImagesCommand() *cobra.Command {\n\tshortHelp := \"List images\"\n\tlongHelp := shortHelp + `\n\nProperties:\n- REPOSITORY: Repository\n- TAG:        Tag\n- NAME:       Name of the image, --names for skip parsing as repository and tag.\n- IMAGE ID:   OCI Digest. Usually different from Docker image ID. Shared for multi-platform images.\n- CREATED:    Created time\n- PLATFORM:   Platform\n- SIZE:       Size of the unpacked snapshots\n- BLOB SIZE:  Size of the blobs (such as layer tarballs) in the content store\n`\n\tvar cmd = &cobra.Command{\n\t\tUse:                   \"images [flags] [REPOSITORY[:TAG]]\",\n\t\tShort:                 shortHelp,\n\t\tLong:                  longHelp,\n\t\tArgs:                  cobra.MaximumNArgs(1),\n\t\tRunE:                  imagesAction,\n\t\tValidArgsFunction:     imagesShellComplete,\n\t\tSilenceUsage:          true,\n\t\tSilenceErrors:         true,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only show numeric IDs\")\n\tcmd.Flags().Bool(\"no-trunc\", false, \"Don't truncate output\")\n\t// Alias \"-f\" is reserved for \"--filter\"\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}', 'wide'\")\n\tcmd.Flags().StringSliceP(\"filter\", \"f\", []string{}, \"Filter output based on conditions provided\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\", \"table\", \"wide\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().Bool(\"digests\", false, \"Show digests (compatible with Docker, unlike ID)\")\n\tcmd.Flags().Bool(\"names\", false, \"Show image names\")\n\tcmd.Flags().BoolP(\"all\", \"a\", true, \"(unimplemented yet, always true)\")\n\n\treturn cmd\n}\n\nfunc listOptions(cmd *cobra.Command, args []string) (*types.ImageListOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar filters []string\n\tif len(args) > 0 {\n\t\tparsedReference, err := referenceutil.Parse(args[0])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfilters = []string{fmt.Sprintf(\"name==%s\", parsedReference)}\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnoTrunc, err := cmd.Flags().GetBool(\"no-trunc\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar inputFilters []string\n\tif cmd.Flags().Changed(\"filter\") {\n\t\tinputFilters, err = cmd.Flags().GetStringSlice(\"filter\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tdigests, err := cmd.Flags().GetBool(\"digests\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnames, err := cmd.Flags().GetBool(\"names\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &types.ImageListOptions{\n\t\tGOptions:         globalOptions,\n\t\tQuiet:            quiet,\n\t\tNoTrunc:          noTrunc,\n\t\tFormat:           format,\n\t\tFilters:          inputFilters,\n\t\tNameAndRefFilter: filters,\n\t\tDigests:          digests,\n\t\tNames:            names,\n\t\tAll:              true,\n\t\tStdout:           cmd.OutOrStdout(),\n\t}, nil\n\n}\n\nfunc imagesAction(cmd *cobra.Command, args []string) error {\n\toptions, err := listOptions(cmd, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !options.All {\n\t\toptions.All = true\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.ListCommandHandler(ctx, client, options)\n}\n\nfunc imagesShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tif len(args) == 0 {\n\t\t// show image names\n\t\treturn completion.ImageNames(cmd)\n\t}\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_list_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/tabutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestImages(t *testing.T) {\n\tnerdtest.Setup()\n\n\tcommonImage, _ := referenceutil.Parse(testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.Not(nerdtest.Docker),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", commonImage.String())\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.NginxAlpineImage)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"No params\",\n\t\t\t\tCommand:     test.Command(\"images\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\tassert.Assert(t, len(lines) >= 2, \"there should be at least two lines\\n\")\n\t\t\t\t\t\t\theader := \"REPOSITORY\\tTAG\\tIMAGE ID\\tCREATED\\tPLATFORM\\tSIZE\\tBLOB SIZE\"\n\t\t\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\t\t\theader = \"REPOSITORY\\tTAG\\tIMAGE ID\\tCREATED\\tSIZE\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttab := tabutil.NewReader(header)\n\t\t\t\t\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\t\t\t\t\tassert.NilError(t, err, \"ParseHeader should not fail\\n\")\n\t\t\t\t\t\t\tfound := false\n\t\t\t\t\t\t\tfor _, line := range lines[1:] {\n\t\t\t\t\t\t\t\trepo, _ := tab.ReadRow(line, \"REPOSITORY\")\n\t\t\t\t\t\t\t\ttag, _ := tab.ReadRow(line, \"TAG\")\n\t\t\t\t\t\t\t\tif repo+\":\"+tag == commonImage.FamiliarName()+\":\"+commonImage.Tag {\n\t\t\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, found, \"we should have found an image\\n\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"With names\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--names\", commonImage.String()),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\texpect.Contains(commonImage.String()),\n\t\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) >= 2, \"there should be at least two lines\\n\")\n\t\t\t\t\t\t\t\ttab := tabutil.NewReader(\"NAME\\tIMAGE ID\\tCREATED\\tPLATFORM\\tSIZE\\tBLOB SIZE\")\n\t\t\t\t\t\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\t\t\t\t\t\tassert.NilError(t, err, \"ParseHeader should not fail\\n\")\n\t\t\t\t\t\t\t\tfound := false\n\t\t\t\t\t\t\t\tfor _, line := range lines[1:] {\n\t\t\t\t\t\t\t\t\tname, _ := tab.ReadRow(line, \"NAME\")\n\t\t\t\t\t\t\t\t\tif name == commonImage.String() {\n\t\t\t\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tassert.Assert(t, found, \"we should have found an image\\n\")\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"CheckCreatedTime\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--format\", \"'{{json .CreatedAt}}'\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\tassert.Assert(t, len(lines) >= 2, \"there should be at least two lines\\n\")\n\t\t\t\t\t\t\tcreatedTimes := lines\n\t\t\t\t\t\t\tslices.Reverse(createdTimes)\n\t\t\t\t\t\t\tassert.Assert(t, slices.IsSorted(createdTimes), \"created times should be sorted\\n\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\ttestCase.Require = require.All(\n\t\t\ttestCase.Require,\n\t\t\tnerdtest.IsFlaky(\"https://github.com/containerd/nerdctl/issues/3524\"),\n\t\t)\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImagesFilter(t *testing.T) {\n\tnerdtest.Setup()\n\n\tcommonImage, _ := referenceutil.Parse(testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", commonImage.String())\n\t\t\thelpers.Ensure(\"tag\", commonImage.String(), \"taggedimage:one-fragment-one\")\n\t\t\thelpers.Ensure(\"tag\", commonImage.String(), \"taggedimage:two-fragment-two\")\n\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"] \\n\nLABEL foo=bar\nLABEL version=0.1\nRUN echo \"actually creating a layer so that docker sets the createdAt time\"\n`, commonImage.String())\n\t\t\tbuildCtx := data.Temp().Path()\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", buildCtx)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", \"taggedimage:one-fragment-one\")\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", \"taggedimage:two-fragment-two\")\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tdata.Labels().Set(\"builtImageID\", data.Identifier())\n\t\t\treturn helpers.Command(\"build\", \"-t\", data.Identifier(), data.Labels().Get(\"buildCtx\"))\n\t\t},\n\t\tExpected: test.Expects(0, nil, nil),\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"label=foo=bar\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"label=foo=bar\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"label=foo=bar1\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"label=foo=bar1\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.DoesNotContain(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"label=foo=bar label=version=0.1\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"label=foo=bar\", \"--filter\", \"label=version=0.1\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"label=foo=bar label=version=0.2\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"label=foo=bar\", \"--filter\", \"label=version=0.2\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.DoesNotContain(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"label=version\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"label=version\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"reference=ID*\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"images\", \"--filter\", fmt.Sprintf(\"reference=%s*\", data.Labels().Get(\"builtImageID\")))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"reference=tagged*:*fragment*\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"reference=tagged*:*fragment*\"),\n\t\t\t\tExpected: test.Expects(\n\t\t\t\t\t0,\n\t\t\t\t\tnil,\n\t\t\t\t\texpect.Contains(\"one-\", \"two-\"),\n\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"before=ID:latest\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"images\", \"--filter\", fmt.Sprintf(\"before=%s:latest\", data.Labels().Get(\"builtImageID\")))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\texpect.Contains(commonImage.FamiliarName(), commonImage.Tag),\n\t\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"since=\" + commonImage.String(),\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", fmt.Sprintf(\"since=%s\", commonImage.String())),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t\t\texpect.DoesNotMatch(regexp.MustCompile(commonImage.FamiliarName()+\"[\\\\s]+\"+commonImage.Tag)),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"since=\" + commonImage.String() + \" \" + commonImage.String(),\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", fmt.Sprintf(\"since=%s\", commonImage.String()), commonImage.String()),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"builtImageID\")),\n\t\t\t\t\t\t\texpect.DoesNotMatch(regexp.MustCompile(commonImage.FamiliarName()+\"[\\\\s]+\"+commonImage.Tag)),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"since=non-exists-image\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"since=non-exists-image\"),\n\t\t\t\tExpected:    test.Expects(expect.ExitCodeGenericFail, []error{errors.New(\"no such image: \")}, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"before=non-exists-image\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"before=non-exists-image\"),\n\t\t\t\tExpected:    test.Expects(expect.ExitCodeGenericFail, []error{errors.New(\"no such image: \")}, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImagesFilterDangling(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"TestImagesFilterDangling\",\n\t\t// This test relies on a clean slate and the ability to GC everything\n\t\tNoParallel: true,\n\t\tRequire:    nerdtest.Build,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-notag-string\"]\n\t`, testutil.CommonImage)\n\t\t\tbuildCtx := data.Temp().Path()\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tdata.Labels().Set(\"buildCtx\", buildCtx)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"container\", \"prune\", \"-f\")\n\t\t\thelpers.Anyhow(\"image\", \"prune\", \"--all\", \"-f\")\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"build\", data.Labels().Get(\"buildCtx\"))\n\t\t},\n\t\tExpected: test.Expects(0, nil, nil),\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"dangling\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"dangling=true\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"<none>\")),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"not dangling\",\n\t\t\t\tCommand:     test.Command(\"images\", \"--filter\", \"dangling=false\"),\n\t\t\t\tExpected:    test.Expects(0, nil, expect.DoesNotContain(\"<none>\")),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImagesKubeWithKubeHideDupe(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\tnerdtest.OnlyKubernetes,\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"The same imageID will not print no-repo:tag in k8s.io with kube-hide-dupe\",\n\t\t\t\tCommand:     test.Command(\"--kube-hide-dupe\", \"images\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tvar imageID string\n\t\t\t\t\t\t\tvar skipLine int\n\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\theader := \"REPOSITORY\\tTAG\\tIMAGE ID\\tCREATED\\tPLATFORM\\tSIZE\\tBLOB SIZE\"\n\t\t\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\t\t\theader = \"REPOSITORY\\tTAG\\tIMAGE ID\\tCREATED\\tSIZE\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttab := tabutil.NewReader(header)\n\t\t\t\t\t\t\terr := tab.ParseHeader(lines[0])\n\t\t\t\t\t\t\tassert.NilError(t, err, \"ParseHeader should not fail\\n\")\n\t\t\t\t\t\t\tfound := true\n\t\t\t\t\t\t\tfor i, line := range lines[1:] {\n\t\t\t\t\t\t\t\trepo, _ := tab.ReadRow(line, \"REPOSITORY\")\n\t\t\t\t\t\t\t\ttag, _ := tab.ReadRow(line, \"TAG\")\n\t\t\t\t\t\t\t\tif repo+\":\"+tag == testutil.BusyboxImage {\n\t\t\t\t\t\t\t\t\tskipLine = i\n\t\t\t\t\t\t\t\t\timageID, _ = tab.ReadRow(line, \"IMAGE ID\")\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor i, line := range lines[1:] {\n\t\t\t\t\t\t\t\tif i == skipLine {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tid, _ := tab.ReadRow(line, \"IMAGE ID\")\n\t\t\t\t\t\t\t\tif id == imageID {\n\t\t\t\t\t\t\t\t\tfound = false\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Assert(t, found, \"We should have found the image\\n\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"the same imageId will print no-repo:tag in k8s.io without kube-hide-dupe\",\n\t\t\t\tCommand:     test.Command(\"images\"),\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: expect.Contains(\"<none>\"),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_load.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/load\"\n)\n\nfunc LoadCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"load\",\n\t\tArgs:          cobra.NoArgs,\n\t\tShort:         \"Load an image from a tar archive or STDIN\",\n\t\tLong:          \"Supports both Docker Image Spec v1.2 and OCI Image Spec v1.0.\",\n\t\tRunE:          loadAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.Flags().StringP(\"input\", \"i\", \"\", \"Read from tar archive file, instead of STDIN\")\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Suppress the load output\")\n\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tcmd.Flags().StringSlice(\"platform\", []string{}, \"Import content for a specific platform\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tcmd.Flags().Bool(\"all-platforms\", false, \"Import content for all platforms\")\n\t// #endregion\n\n\treturn cmd\n}\n\nfunc processLoadCommandFlags(cmd *cobra.Command) (types.ImageLoadOptions, error) {\n\tinput, err := cmd.Flags().GetString(\"input\")\n\tif err != nil {\n\t\treturn types.ImageLoadOptions{}, err\n\t}\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImageLoadOptions{}, err\n\t}\n\tallPlatforms, err := cmd.Flags().GetBool(\"all-platforms\")\n\tif err != nil {\n\t\treturn types.ImageLoadOptions{}, err\n\t}\n\tplatform, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.ImageLoadOptions{}, err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.ImageLoadOptions{}, err\n\t}\n\treturn types.ImageLoadOptions{\n\t\tGOptions:     globalOptions,\n\t\tInput:        input,\n\t\tPlatform:     platform,\n\t\tAllPlatforms: allPlatforms,\n\t\tStdout:       cmd.OutOrStdout(),\n\t\tStdin:        cmd.InOrStdin(),\n\t\tQuiet:        quiet,\n\t}, nil\n}\n\nfunc loadAction(cmd *cobra.Command, _ []string) error {\n\toptions, err := processLoadCommandFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\t_, err = load.FromArchive(ctx, client, options)\n\treturn err\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_load_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestLoadStdinFromPipe(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"TestLoadStdinFromPipe\",\n\t\tRequire:     require.Linux,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tidentifier := data.Identifier()\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, identifier)\n\t\t\thelpers.Ensure(\"save\", identifier, \"-o\", filepath.Join(data.Temp().Path(), \"common.tar\"))\n\t\t\thelpers.Ensure(\"rmi\", \"-f\", identifier)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\tcmd := helpers.Command(\"load\")\n\t\t\treader, err := os.Open(filepath.Join(data.Temp().Path(), \"common.tar\"))\n\t\t\tassert.NilError(t, err, \"failed to open common.tar\")\n\t\t\tcmd.Feed(reader)\n\t\t\treturn cmd\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\tidentifier := data.Identifier()\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: expect.All(\n\t\t\t\t\texpect.Contains(identifier),\n\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(helpers.Capture(\"images\"), identifier))\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestLoadStdinEmpty(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"TestLoadStdinEmpty\",\n\t\tRequire:     require.Linux,\n\t\tCommand:     test.Command(\"load\"),\n\t\tExpected:    test.Expects(1, nil, nil),\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestLoadQuiet(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tDescription: \"TestLoadQuiet\",\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tidentifier := data.Identifier()\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, identifier)\n\t\t\thelpers.Ensure(\"save\", identifier, \"-o\", filepath.Join(data.Temp().Path(), \"common.tar\"))\n\t\t\thelpers.Ensure(\"rmi\", \"-f\", identifier)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"load\", \"--quiet\", \"--input\", filepath.Join(data.Temp().Path(), \"common.tar\"))\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: expect.All(\n\t\t\t\t\texpect.Contains(data.Identifier()),\n\t\t\t\t\texpect.DoesNotContain(\"Loading layer\"),\n\t\t\t\t),\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nfunc pruneCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"prune [flags]\",\n\t\tShort:         \"Remove unused images\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          imagePruneAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Remove all unused images, not just dangling ones\")\n\tcmd.Flags().StringSlice(\"filter\", []string{}, \"Filter output based on conditions provided\")\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\treturn cmd\n}\n\nfunc pruneOptions(cmd *cobra.Command) (types.ImagePruneOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImagePruneOptions{}, err\n\t}\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn types.ImagePruneOptions{}, err\n\t}\n\n\tvar filters []string\n\tif cmd.Flags().Changed(\"filter\") {\n\t\tfilters, err = cmd.Flags().GetStringSlice(\"filter\")\n\t\tif err != nil {\n\t\t\treturn types.ImagePruneOptions{}, err\n\t\t}\n\t}\n\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn types.ImagePruneOptions{}, err\n\t}\n\n\treturn types.ImagePruneOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tAll:      all,\n\t\tFilters:  filters,\n\t\tForce:    force,\n\t}, err\n}\n\nfunc imagePruneAction(cmd *cobra.Command, _ []string) error {\n\toptions, err := pruneOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !options.Force {\n\t\tvar msg string\n\t\tif !options.All {\n\t\t\tmsg = \"This will remove all dangling images.\"\n\t\t} else {\n\t\t\tmsg = \"This will remove all images without at least one container associated to them.\"\n\t\t}\n\n\t\tif confirmed, err := helpers.Confirm(cmd, fmt.Sprintf(\"WARNING! %s.\", msg)); err != nil || !confirmed {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.Prune(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_prune_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestImagePrune(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Cannot use a custom namespace with buildkitd right now, so, no parallel it is\n\ttestCase.NoParallel = true\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\t// Stop and remove all running containers. This is to ensure we can remove all\n\t\tcontList := strings.TrimSpace(helpers.Capture(\"ps\", \"-aq\"))\n\t\tif contList != \"\" {\n\t\t\thelpers.Ensure(append([]string{\"rm\", \"-f\"}, strings.Split(contList, \"\\n\")...)...)\n\t\t}\n\n\t\t// We need to delete everything here for prune to make any sense\n\t\timgList := strings.TrimSpace(helpers.Capture(\"images\", \"--no-trunc\", \"-aq\"))\n\t\tif imgList != \"\" {\n\t\t\thelpers.Ensure(append([]string{\"rmi\", \"-f\"}, strings.Split(imgList, \"\\n\")...)...)\n\t\t}\n\t}\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"without all\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\t// This never worked with Docker - the only reason we ever got <none> was side effects from other tests\n\t\t\t\t// See inline comments.\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\tnerdtest.Build,\n\t\t\t),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\n\t\t\t\tCMD [\"echo\", \"nerdctl-test-image-prune\"]\n\t\t\t\t\t`, testutil.CommonImage)\n\n\t\t\t\tbuildCtx := data.Temp().Path()\n\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\thelpers.Ensure(\"build\", buildCtx)\n\t\t\t\t// After we rebuild with tag, docker will no longer show the <none> version from above\n\t\t\t\t// Swapping order does not change anything.\n\t\t\t\thelpers.Ensure(\"build\", \"-t\", identifier, buildCtx)\n\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\tassert.Assert(t, strings.Contains(imgList, \"<none>\"), \"Missing <none>\")\n\t\t\t\tassert.Assert(t, strings.Contains(imgList, identifier), \"Missing \"+identifier)\n\t\t\t},\n\t\t\tCommand: test.Command(\"image\", \"prune\", \"--force\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, identifier))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(imgList, \"<none>\"), imgList)\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgList, identifier))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"with all\",\n\t\t\tRequire: require.All(\n\t\t\t\t// Same as above\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\tnerdtest.Build,\n\t\t\t),\n\t\t\t// Cannot use a custom namespace with buildkitd right now, so, no parallel it is\n\t\t\tNoParallel: true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", identifier)\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", identifier)\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\n\t\t\t\tCMD [\"echo\", \"nerdctl-test-image-prune\"]\n\t\t\t\t\t`, testutil.CommonImage)\n\n\t\t\t\tbuildCtx := data.Temp().Path()\n\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\thelpers.Ensure(\"build\", buildCtx)\n\t\t\t\thelpers.Ensure(\"build\", \"-t\", identifier, buildCtx)\n\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\tassert.Assert(t, strings.Contains(imgList, \"<none>\"), \"Missing <none>\")\n\t\t\t\tassert.Assert(t, strings.Contains(imgList, identifier), \"Missing \"+identifier)\n\t\t\t\thelpers.Ensure(\"run\", \"--name\", identifier, identifier)\n\t\t\t},\n\t\t\tCommand: test.Command(\"image\", \"prune\", \"--force\", \"--all\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, data.Identifier()))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgList, data.Identifier()))\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(imgList, \"<none>\"), imgList)\n\t\t\t\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\t\t\tremoved := helpers.Capture(\"image\", \"prune\", \"--force\", \"--all\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(removed, data.Identifier()))\n\t\t\t\t\t\t\timgList = helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(imgList, data.Identifier()))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"with filter label\",\n\t\t\tRequire:     nerdtest.Build,\n\t\t\t// Cannot use a custom namespace with buildkitd right now, so, no parallel it is\n\t\t\tNoParallel: true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-test-image-prune-filter-label\"]\nLABEL foo=bar\nLABEL version=0.1`, testutil.CommonImage)\n\t\t\t\tbuildCtx := data.Temp().Path()\n\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(), buildCtx)\n\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\tassert.Assert(t, strings.Contains(imgList, data.Identifier()), \"Missing \"+data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"image\", \"prune\", \"--force\", \"--all\", \"--filter\", \"label=foo=baz\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, data.Identifier()))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgList, data.Identifier()))\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\tprune := helpers.Capture(\"image\", \"prune\", \"--force\", \"--all\", \"--filter\", \"label=foo=bar\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(prune, data.Identifier()))\n\t\t\t\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(imgList, data.Identifier()))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"with until\",\n\t\t\tRequire:     nerdtest.Build,\n\t\t\t// Cannot use a custom namespace with buildkitd right now, so, no parallel it is\n\t\t\tNoParallel: true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nRUN echo \"Anything, so that we create actual content for docker to set the current time for CreatedAt\"\nCMD [\"echo\", \"nerdctl-test-image-prune-until\"]`, testutil.CommonImage)\n\t\t\t\tbuildCtx := data.Temp().Path()\n\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(), buildCtx)\n\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\tassert.Assert(t, strings.Contains(imgList, data.Identifier()), \"Missing \"+data.Identifier())\n\t\t\t\tdata.Labels().Set(\"imageID\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"image\", \"prune\", \"--force\", \"--all\", \"--filter\", \"until=12h\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"imageID\")),\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\t\t\t\tassert.Assert(t, strings.Contains(imgList, data.Labels().Get(\"imageID\")))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t\tSubTests: []*test.Case{\n\t\t\t\t{\n\t\t\t\t\tDescription: \"Wait and remove until=10ms\",\n\t\t\t\t\tNoParallel:  true,\n\t\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t\t},\n\t\t\t\t\tCommand: test.Command(\"image\", \"prune\", \"--force\", \"--all\", \"--filter\", \"until=10ms\"),\n\t\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"imageID\")),\n\t\t\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\t\timgList := helpers.Capture(\"images\")\n\t\t\t\t\t\t\t\t\tassert.Assert(t, !strings.Contains(imgList, data.Labels().Get(\"imageID\")), imgList)\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_pull.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc PullCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"pull [flags] NAME[:TAG]\",\n\t\tShort:         \"Pull an image from a registry. Optionally specify \\\"ipfs://\\\" or \\\"ipns://\\\" scheme to pull image from IPFS.\",\n\t\tArgs:          helpers.IsExactArgs(1),\n\t\tRunE:          pullAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().String(\"unpack\", \"auto\", \"Unpack the image for the current single platform (auto/true/false)\")\n\tcmd.RegisterFlagCompletionFunc(\"unpack\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"auto\", \"true\", \"false\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tcmd.Flags().StringSlice(\"platform\", nil, \"Pull content for a specific platform\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tcmd.Flags().Bool(\"all-platforms\", false, \"Pull content for all platforms\")\n\t// #endregion\n\n\t// #region verify flags\n\tcmd.Flags().String(\"verify\", \"none\", \"Verify the image (none|cosign|notation)\")\n\tcmd.RegisterFlagCompletionFunc(\"verify\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"none\", \"cosign\", \"notation\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"cosign-key\", \"\", \"Path to the public key file, KMS, URI or Kubernetes Secret for --verify=cosign\")\n\tcmd.Flags().String(\"cosign-certificate-identity\", \"\", \"The identity expected in a valid Fulcio certificate for --verify=cosign. Valid values include email address, DNS names, IP addresses, and URIs. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\")\n\tcmd.Flags().String(\"cosign-certificate-identity-regexp\", \"\", \"A regular expression alternative to --cosign-certificate-identity for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\")\n\tcmd.Flags().String(\"cosign-certificate-oidc-issuer\", \"\", \"The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign,, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\")\n\tcmd.Flags().String(\"cosign-certificate-oidc-issuer-regexp\", \"\", \"A regular expression alternative to --certificate-oidc-issuer for --verify=cosign,. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\")\n\t// #endregion\n\n\t// #region socipull flags\n\tcmd.Flags().String(\"soci-index-digest\", \"\", \"Specify a particular index digest for SOCI. If left empty, SOCI will automatically use the index determined by the selection policy.\")\n\t// #endregion\n\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Suppress verbose output\")\n\n\tcmd.Flags().String(\"ipfs-address\", \"\", \"multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\")\n\n\treturn cmd\n}\n\nfunc processPullCommandFlags(cmd *cobra.Command) (types.ImagePullOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\tallPlatforms, err := cmd.Flags().GetBool(\"all-platforms\")\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\tplatform, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\n\tociSpecPlatform, err := platformutil.NewOCISpecPlatformSlice(allPlatforms, platform)\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\n\tunpackStr, err := cmd.Flags().GetString(\"unpack\")\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\tunpack, err := strutil.ParseBoolOrAuto(unpackStr)\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\tipfsAddressStr, err := cmd.Flags().GetString(\"ipfs-address\")\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\n\tsociIndexDigest, err := cmd.Flags().GetString(\"soci-index-digest\")\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\n\tverifyOptions, err := helpers.VerifyOptions(cmd)\n\tif err != nil {\n\t\treturn types.ImagePullOptions{}, err\n\t}\n\treturn types.ImagePullOptions{\n\t\tGOptions:        globalOptions,\n\t\tVerifyOptions:   verifyOptions,\n\t\tOCISpecPlatform: ociSpecPlatform,\n\t\tUnpack:          unpack,\n\t\tMode:            \"always\",\n\t\tQuiet:           quiet,\n\t\tIPFSAddress:     ipfsAddressStr,\n\t\tRFlags: types.RemoteSnapshotterFlags{\n\t\t\tSociIndexDigest: sociIndexDigest,\n\t\t},\n\t\tStdout:                 cmd.OutOrStdout(),\n\t\tStderr:                 cmd.OutOrStderr(),\n\t\tProgressOutputToStdout: true,\n\t}, nil\n}\n\nfunc pullAction(cmd *cobra.Command, args []string) error {\n\toptions, err := processPullCommandFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.Pull(ctx, client, args[0], options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_pull_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestImagePullWithCosign(t *testing.T) {\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\tnerdtest.Setup()\n\n\tvar reg *registry.Server\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\tnerdtest.Build,\n\t\t\trequire.Binary(\"cosign\"),\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\tnerdtest.Registry,\n\t\t),\n\n\t\tEnv: map[string]string{\n\t\t\t\"COSIGN_PASSWORD\": \"1\",\n\t\t},\n\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\tpri, pub := nerdtest.GenerateCosignKeyPair(data, helpers, \"1\")\n\t\t\treg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)\n\t\t\treg.Setup(data, helpers)\n\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\", \"127.0.0.1\", reg.Port, data.Identifier())\n\t\t\tbuildCtx := data.Temp().Path()\n\n\t\t\thelpers.Ensure(\"build\", \"-t\", testImageRef+\":one\", buildCtx)\n\t\t\thelpers.Ensure(\"build\", \"-t\", testImageRef+\":two\", buildCtx)\n\t\t\thelpers.Ensure(\"push\", \"--sign=cosign\", \"--cosign-key=\"+pri, testImageRef+\":one\")\n\t\t\thelpers.Ensure(\"push\", \"--sign=cosign\", \"--cosign-key=\"+pri, testImageRef+\":two\")\n\n\t\t\tdata.Labels().Set(\"public_key\", pub)\n\t\t\tdata.Labels().Set(\"image_ref\", testImageRef)\n\t\t},\n\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif reg != nil {\n\t\t\t\treg.Cleanup(data, helpers)\n\t\t\t\ttestImageRef := data.Labels().Get(\"image_ref\")\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testImageRef+\":one\")\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testImageRef+\":two\")\n\t\t\t}\n\t\t},\n\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Pull with the correct key\",\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\n\t\t\t\t\t\t\"pull\", \"--quiet\", \"--verify=cosign\",\n\t\t\t\t\t\t\"--cosign-key=\"+data.Labels().Get(\"public_key\"),\n\t\t\t\t\t\tdata.Labels().Get(\"image_ref\")+\":one\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Pull with unrelated key\",\n\t\t\t\tEnv: map[string]string{\n\t\t\t\t\t\"COSIGN_PASSWORD\": \"2\",\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t_, pub := nerdtest.GenerateCosignKeyPair(data, helpers, \"2\")\n\t\t\t\t\treturn helpers.Command(\"pull\", \"--quiet\", \"--verify=cosign\", \"--cosign-key=\"+pub, data.Labels().Get(\"image_ref\")+\":two\")\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(12, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImagePullPlainHttpWithDefaultPort(t *testing.T) {\n\tnerdtest.Setup()\n\n\tvar reg *registry.Server\n\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, testutil.CommonImage)\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\tnerdtest.Build,\n\t\t\tnerdtest.Registry,\n\t\t),\n\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\t\t\treg = nerdtest.RegistryWithNoAuth(data, helpers, 80, false)\n\t\t\treg.Setup(data, helpers)\n\t\t\ttestImageRef := fmt.Sprintf(\"%s/%s\",\n\t\t\t\treg.IP.String(), data.Identifier())\n\t\t\tbuildCtx := data.Temp().Path()\n\n\t\t\thelpers.Ensure(\"build\", \"-t\", testImageRef, buildCtx)\n\t\t\thelpers.Ensure(\"--insecure-registry\", \"push\", testImageRef)\n\t\t\thelpers.Ensure(\"rmi\", \"-f\", testImageRef)\n\n\t\t\tdata.Labels().Set(\"image_ref\", testImageRef)\n\t\t},\n\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"--insecure-registry\", \"pull\", data.Labels().Get(\"image_ref\"))\n\t\t},\n\n\t\tExpected: test.Expects(0, nil, nil),\n\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif reg != nil {\n\t\t\t\treg.Cleanup(data, helpers)\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"image_ref\"))\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImagePullSoci(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\trequire.Not(nerdtest.Docker),\n\t\t\tnerdtest.Soci,\n\t\t),\n\n\t\t// NOTE: these tests cannot be run in parallel, as they depend on the output of host `mount`\n\t\t// They also feel prone to raciness...\n\t\tNoParallel: true,\n\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Run without specifying SOCI index\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\t\"remoteSnapshotsExpectedCount\": \"11\",\n\t\t\t\t\t\"sociIndexDigest\":              \"\",\n\t\t\t\t}),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tcmd := helpers.Custom(\"mount\")\n\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tdata.Labels().Set(\"remoteSnapshotsInitialCount\", strconv.Itoa(strings.Count(stdout, \"fuse.rawBridge\")))\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\thelpers.Ensure(\"--snapshotter=soci\", \"pull\", testutil.FfmpegSociImage)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.FfmpegSociImage)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Custom(\"mount\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tremoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get(\"remoteSnapshotsInitialCount\"))\n\t\t\t\t\t\t\tremoteSnapshotsActualCount := strings.Count(stdout, \"fuse.rawBridge\")\n\t\t\t\t\t\t\tassert.Equal(t,\n\t\t\t\t\t\t\t\tdata.Labels().Get(\"remoteSnapshotsExpectedCount\"),\n\t\t\t\t\t\t\t\tstrconv.Itoa(remoteSnapshotsActualCount-remoteSnapshotsInitialCount),\n\t\t\t\t\t\t\t\t\"expected remote snapshot count to match\",\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Run with bad SOCI index\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\t\"remoteSnapshotsExpectedCount\": \"11\",\n\t\t\t\t\t\"sociIndexDigest\":              \"sha256:thisisabadindex0000000000000000000000000000000000000000000000000\",\n\t\t\t\t}),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tcmd := helpers.Custom(\"mount\")\n\t\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tdata.Labels().Set(\"remoteSnapshotsInitialCount\", strconv.Itoa(strings.Count(stdout, \"fuse.rawBridge\")))\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\thelpers.Ensure(\"--snapshotter=soci\", \"pull\", testutil.FfmpegSociImage)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.FfmpegSociImage)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Custom(\"mount\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tremoteSnapshotsInitialCount, _ := strconv.Atoi(data.Labels().Get(\"remoteSnapshotsInitialCount\"))\n\t\t\t\t\t\t\tremoteSnapshotsActualCount := strings.Count(stdout, \"fuse.rawBridge\")\n\t\t\t\t\t\t\tassert.Equal(t,\n\t\t\t\t\t\t\t\tdata.Labels().Get(\"remoteSnapshotsExpectedCount\"),\n\t\t\t\t\t\t\t\tstrconv.Itoa(remoteSnapshotsActualCount-remoteSnapshotsInitialCount),\n\t\t\t\t\t\t\t\t\"expected remote snapshot count to match\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestImagePullProcessOutput(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"Pull Image - output should be in stdout\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.BusyboxImage)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"pull\", testutil.BusyboxImage)\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.Contains(testutil.BusyboxImage)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"Run Container with image pull - output should be in stderr\",\n\t\t\t\tNoParallel:  true,\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.BusyboxImage)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"run\", \"--rm\", testutil.BusyboxImage)\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, expect.DoesNotContain(testutil.BusyboxImage)),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nconst (\n\tallowNonDistFlag = \"allow-nondistributable-artifacts\"\n)\n\nfunc PushCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"push [flags] NAME[:TAG]\",\n\t\tShort:             \"Push an image or a repository to a registry. Optionally specify \\\"ipfs://\\\" or \\\"ipns://\\\" scheme to push image to IPFS.\",\n\t\tArgs:              helpers.IsExactArgs(1),\n\t\tRunE:              pushAction,\n\t\tValidArgsFunction: pushShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tcmd.Flags().StringSlice(\"platform\", []string{}, \"Push content for a specific platform\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tcmd.Flags().Bool(\"all-platforms\", false, \"Push content for all platforms\")\n\t// #endregion\n\n\tcmd.Flags().Bool(\"estargz\", false, \"Convert the image into eStargz\")\n\tcmd.Flags().Bool(\"ipfs-ensure-image\", true, \"Ensure the entire contents of the image is locally available before push\")\n\tcmd.Flags().String(\"ipfs-address\", \"\", \"multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\")\n\n\t// #region sign flags\n\tcmd.Flags().String(\"sign\", \"none\", \"Sign the image (none|cosign|notation\")\n\tcmd.RegisterFlagCompletionFunc(\"sign\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"none\", \"cosign\", \"notation\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"cosign-key\", \"\", \"Path to the private key file, KMS URI or Kubernetes Secret for --sign=cosign\")\n\tcmd.Flags().String(\"notation-key-name\", \"\", \"Signing key name for a key previously added to notation's key list for --sign=notation\")\n\t// #endregion\n\n\t// #region soci flags\n\tcmd.Flags().Int64(\"soci-span-size\", -1, \"Span size that soci index uses to segment layer data. Default is 4 MiB.\")\n\tcmd.Flags().Int64(\"soci-min-layer-size\", -1, \"Minimum layer size to build zTOC for. Smaller layers won't have zTOC and not lazy pulled. Default is 10 MiB.\")\n\t// #endregion\n\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Suppress verbose output\")\n\n\tcmd.Flags().Bool(allowNonDistFlag, false, \"Allow pushing images with non-distributable blobs\")\n\n\treturn cmd\n}\n\nfunc pushOptions(cmd *cobra.Command) (types.ImagePushOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tplatform, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tallPlatforms, err := cmd.Flags().GetBool(\"all-platforms\")\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\testargz, err := cmd.Flags().GetBool(\"estargz\")\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tipfsEnsureImage, err := cmd.Flags().GetBool(\"ipfs-ensure-image\")\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tipfsAddress, err := cmd.Flags().GetString(\"ipfs-address\")\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tallowNonDist, err := cmd.Flags().GetBool(allowNonDistFlag)\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tsignOptions, err := signOptions(cmd)\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\tsociOptions, err := sociOptions(cmd)\n\tif err != nil {\n\t\treturn types.ImagePushOptions{}, err\n\t}\n\treturn types.ImagePushOptions{\n\t\tGOptions:                       globalOptions,\n\t\tSignOptions:                    signOptions,\n\t\tSociOptions:                    sociOptions,\n\t\tPlatforms:                      platform,\n\t\tAllPlatforms:                   allPlatforms,\n\t\tEstargz:                        estargz,\n\t\tIpfsEnsureImage:                ipfsEnsureImage,\n\t\tIpfsAddress:                    ipfsAddress,\n\t\tQuiet:                          quiet,\n\t\tAllowNondistributableArtifacts: allowNonDist,\n\t\tStdout:                         cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc pushAction(cmd *cobra.Command, args []string) error {\n\toptions, err := pushOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\trawRef := args[0]\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.Push(ctx, client, rawRef, options)\n}\n\nfunc pushShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n\nfunc signOptions(cmd *cobra.Command) (opt types.ImageSignOptions, err error) {\n\tif opt.Provider, err = cmd.Flags().GetString(\"sign\"); err != nil {\n\t\treturn\n\t}\n\tif opt.CosignKey, err = cmd.Flags().GetString(\"cosign-key\"); err != nil {\n\t\treturn\n\t}\n\tif opt.NotationKeyName, err = cmd.Flags().GetString(\"notation-key-name\"); err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\nfunc sociOptions(cmd *cobra.Command) (opt types.SociOptions, err error) {\n\tif opt.SpanSize, err = cmd.Flags().GetInt64(\"soci-span-size\"); err != nil {\n\t\treturn\n\t}\n\tif opt.MinLayerSize, err = cmd.Flags().GetInt64(\"soci-min-layer-size\"); err != nil {\n\t\treturn\n\t}\n\treturn\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_push_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestPush(t *testing.T) {\n\tnerdtest.Setup()\n\n\tvar registryNoAuthHTTPRandom, registryNoAuthHTTPDefault, registryTokenAuthHTTPSRandom *registry.Server\n\tvar tokenServer *registry.TokenAuthServer\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\tnerdtest.Registry,\n\t\t\tnerdtest.IsFlaky(\"https://github.com/containerd/nerdctl/issues/4470\"),\n\t\t),\n\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tregistryNoAuthHTTPRandom = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)\n\t\t\tregistryNoAuthHTTPRandom.Setup(data, helpers)\n\t\t\tregistryNoAuthHTTPDefault = nerdtest.RegistryWithNoAuth(data, helpers, 80, false)\n\t\t\tregistryNoAuthHTTPDefault.Setup(data, helpers)\n\t\t\tregistryTokenAuthHTTPSRandom, tokenServer = nerdtest.RegistryWithTokenAuth(data, helpers, \"admin\", \"badmin\", 0, true)\n\t\t\ttokenServer.Setup(data, helpers)\n\t\t\tregistryTokenAuthHTTPSRandom.Setup(data, helpers)\n\t\t},\n\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif registryNoAuthHTTPRandom != nil {\n\t\t\t\tregistryNoAuthHTTPRandom.Cleanup(data, helpers)\n\t\t\t}\n\t\t\tif registryNoAuthHTTPDefault != nil {\n\t\t\t\tregistryNoAuthHTTPDefault.Cleanup(data, helpers)\n\t\t\t}\n\t\t\tif registryTokenAuthHTTPSRandom != nil {\n\t\t\t\tregistryTokenAuthHTTPSRandom.Cleanup(data, helpers)\n\t\t\t}\n\t\t\tif tokenServer != nil {\n\t\t\t\ttokenServer.Cleanup(data, helpers)\n\t\t\t}\n\t\t},\n\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"plain http\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(1, []error{errors.New(\"server gave HTTP response to HTTPS client\")}, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"plain http with insecure\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--insecure-registry\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"plain http with localhost\",\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\t\"127.0.0.1\", registryNoAuthHTTPRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"plain http with insecure, default port\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s/%s\",\n\t\t\t\t\t\tregistryNoAuthHTTPDefault.IP.String(), data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--insecure-registry\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"with insecure, with login\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, testImageRef)\n\t\t\t\t\thelpers.Ensure(\"--insecure-registry\", \"login\", \"-u\", \"admin\", \"-p\", \"badmin\",\n\t\t\t\t\t\tfmt.Sprintf(\"%s:%d\", registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port))\n\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--insecure-registry\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"with hosts dir, with login\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, testImageRef)\n\t\t\t\t\thelpers.Ensure(\"--hosts-dir\", registryTokenAuthHTTPSRandom.HostsDir, \"login\", \"-u\", \"admin\", \"-p\", \"badmin\",\n\t\t\t\t\t\tfmt.Sprintf(\"%s:%d\", registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port))\n\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--hosts-dir\", registryTokenAuthHTTPSRandom.HostsDir, data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"non distributable artifacts\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.NonDistBlobImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.NonDistBlobImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--insecure-registry\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tblobURL := fmt.Sprintf(\"http://%s:%d/v2/%s/blobs/%s\", registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), testutil.NonDistBlobDigest)\n\t\t\t\t\t\t\tresp, err := http.Get(blobURL)\n\t\t\t\t\t\t\tassert.Assert(t, err, \"error making http request\")\n\t\t\t\t\t\t\tif resp.Body != nil {\n\t\t\t\t\t\t\t\t_ = resp.Body.Close()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Equal(t, resp.StatusCode, http.StatusNotFound, \"non-distributable blob should not be available\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"non distributable artifacts (with)\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.NonDistBlobImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.NonDistBlobImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--insecure-registry\", \"--allow-nondistributable-artifacts\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\tblobURL := fmt.Sprintf(\"http://%s:%d/v2/%s/blobs/%s\", registryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier(), testutil.NonDistBlobDigest)\n\t\t\t\t\t\t\tresp, err := http.Get(blobURL)\n\t\t\t\t\t\t\tassert.Assert(t, err, \"error making http request\")\n\t\t\t\t\t\t\tif resp.Body != nil {\n\t\t\t\t\t\t\t\t_ = resp.Body.Close()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.Equal(t, resp.StatusCode, http.StatusOK, \"non-distributable blob should be available\")\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"soci\",\n\t\t\t\tRequire: require.All(\n\t\t\t\t\tnerdtest.Soci,\n\t\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\t),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.UbuntuImage)\n\t\t\t\t\ttestImageRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryNoAuthHTTPRandom.IP.String(), registryNoAuthHTTPRandom.Port, data.Identifier())\n\t\t\t\t\tdata.Labels().Set(\"testImageRef\", testImageRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.UbuntuImage, testImageRef)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tif data.Labels().Get(\"testImageRef\") != \"\" {\n\t\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", \"--snapshotter=soci\", \"--insecure-registry\", \"--soci-span-size=2097152\", \"--soci-min-layer-size=20971520\", data.Labels().Get(\"testImageRef\"))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nfunc RmiCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"rmi [flags] IMAGE [IMAGE, ...]\",\n\t\tShort:             \"Remove one or more images\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              rmiAction,\n\t\tValidArgsFunction: rmiShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Force removal of the image\")\n\t// Alias `-a` is reserved for `--all`. Should be compatible with `podman rmi --all`.\n\tcmd.Flags().Bool(\"async\", false, \"Asynchronous mode\")\n\treturn cmd\n}\n\nfunc removeOptions(cmd *cobra.Command) (types.ImageRemoveOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImageRemoveOptions{}, err\n\t}\n\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn types.ImageRemoveOptions{}, err\n\t}\n\tasync, err := cmd.Flags().GetBool(\"async\")\n\tif err != nil {\n\t\treturn types.ImageRemoveOptions{}, err\n\t}\n\n\treturn types.ImageRemoveOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tForce:    force,\n\t\tAsync:    async,\n\t}, nil\n}\n\nfunc rmiAction(cmd *cobra.Command, args []string) error {\n\toptions, err := removeOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.Remove(ctx, client, args, options)\n}\n\nfunc rmiShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_remove_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestRemove(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst (\n\t\timgShortIDKey = \"imgShortID\"\n\t)\n\n\trepoName, _ := imgutil.ParseRepoTag(testutil.CommonImage)\n\tnginxRepoName, _ := imgutil.ParseRepoTag(testutil.NginxAlpineImage)\n\t// NOTES:\n\t// - since all of these are rmi-ing the common image, we need private mode\n\ttestCase.Require = nerdtest.Private\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Remove image with stopped container - without -f\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"image is being used\")},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with stopped container - with -f\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", \"-f\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.DoesNotContain(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with running container - without -f\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"image is being used\")},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with running container - with -f\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\n\t\t\t\timg := nerdtest.InspectImage(helpers, testutil.CommonImage)\n\t\t\t\trepoName, _ := imgutil.ParseRepoTag(testutil.CommonImage)\n\t\t\t\timgShortID := strings.TrimPrefix(img.RepoDigests[0], repoName+\"@sha256:\")[0:8]\n\n\t\t\t\tdata.Labels().Set(imgShortIDKey, imgShortID)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(imgShortIDKey))\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", \"-f\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(\"<none>\"),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with created container - without -f\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"create\", \"--quiet\", \"--pull\", \"always\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"image is being used\")},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with created container - with -f\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.NginxAlpineImage)\n\t\t\t\thelpers.Ensure(\"create\", \"--quiet\", \"--pull\", \"always\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"rmi\", testutil.NginxAlpineImage)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", \"-f\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\t// a created container with removed image doesn't impact other `rmi` command\n\t\t\t\t\t\t\tOutput: expect.DoesNotContain(repoName, nginxRepoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with paused container - without -f\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\tnerdtest.CGroup,\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"pause\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"image is being used\")},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with paused container - with -f\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.CGroup,\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"pause\", data.Identifier())\n\n\t\t\t\timg := nerdtest.InspectImage(helpers, testutil.CommonImage)\n\t\t\t\trepoName, _ := imgutil.ParseRepoTag(testutil.CommonImage)\n\t\t\t\timgShortID := strings.TrimPrefix(img.RepoDigests[0], repoName+\"@sha256:\")[0:8]\n\n\t\t\t\tdata.Labels().Set(imgShortIDKey, imgShortID)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(imgShortIDKey))\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", \"-f\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(\"<none>\"),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with killed container - without -f\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"kill\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(\"image is being used\")},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.Contains(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Remove image with killed container - with -f\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"--quiet\", \"--pull\", \"always\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"kill\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", \"-f\", testutil.CommonImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.DoesNotContain(repoName),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestIssue3016 tests https://github.com/containerd/nerdctl/issues/3016\nfunc TestIssue3016(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst (\n\t\ttagIDKey = \"tagID\"\n\t)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Issue #3016 - Tags created using the short digest ids of container images cannot be deleted using the nerdctl rmi command.\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.NginxAlpineImage)\n\n\t\t\t\timg := nerdtest.InspectImage(helpers, testutil.NginxAlpineImage)\n\t\t\t\trepoName, _ := imgutil.ParseRepoTag(testutil.NginxAlpineImage)\n\t\t\t\ttagID := strings.TrimPrefix(img.RepoDigests[0], repoName+\"@sha256:\")[0:8]\n\n\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, tagID)\n\n\t\t\t\tdata.Labels().Set(tagIDKey, tagID)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"rmi\", data.Labels().Get(tagIDKey))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"images\", data.Labels().Get(tagIDKey)).Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tassert.Equal(t, len(strings.Split(stdout, \"\\n\")), 2)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestRemoveKubeWithKubeHideDupe(t *testing.T) {\n\tvar numTags, numNoTags int\n\ttestCase := nerdtest.Setup()\n\ttestCase.NoParallel = true\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"--kube-hide-dupe\", \"rmi\", \"-f\", testutil.BusyboxImage)\n\t}\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tnumTags = len(strings.Split(strings.TrimSpace(helpers.Capture(\"--kube-hide-dupe\", \"images\")), \"\\n\"))\n\t\tnumNoTags = len(strings.Split(strings.TrimSpace(helpers.Capture(\"images\")), \"\\n\"))\n\t}\n\ttestCase.Require = require.All(\n\t\tnerdtest.OnlyKubernetes,\n\t)\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"After removing the tag without kube-hide-dupe, repodigest is shown as <none>\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t\t\t},\n\t\t\tCommand: test.Command(\"rmi\", \"-f\", testutil.BusyboxImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numTags+1)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numNoTags+1)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"If there are other tags, the Repodigest will not be deleted\",\n\t\t\tNoParallel:  true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"--kube-hide-dupe\", \"rmi\", data.Identifier())\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t\t\t\thelpers.Ensure(\"tag\", testutil.BusyboxImage, data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"--kube-hide-dupe\", \"rmi\", testutil.BusyboxImage),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numTags+1)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numNoTags+2)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"After deleting all repo:tag entries, all repodigests will be cleaned up\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t\t\t\thelpers.Ensure(\"tag\", testutil.BusyboxImage, data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\thelpers.Ensure(\"--kube-hide-dupe\", \"rmi\", \"-f\", testutil.BusyboxImage)\n\t\t\t\treturn helpers.Command(\"--kube-hide-dupe\", \"rmi\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numTags)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numNoTags)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test multiple IDs found with provided prefix and force with shortID\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t\t\t\thelpers.Ensure(\"tag\", testutil.BusyboxImage, data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"--kube-hide-dupe\", \"images\", testutil.BusyboxImage, \"-q\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"rmi\", stdout[0:12]).Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\tErrors:   []error{errors.New(\"multiple IDs found with provided prefix: \")},\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"rmi\", \"--force\", stdout[0:12]).Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numNoTags)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Test remove image with digestID\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.BusyboxImage)\n\t\t\t\thelpers.Ensure(\"tag\", testutil.BusyboxImage, data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"--kube-hide-dupe\", \"images\", testutil.BusyboxImage, \"-q\", \"--no-trunc\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\timgID := strings.Split(stdout, \"\\n\")\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"rmi\", imgID[0]).Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\tErrors:   []error{errors.New(\"multiple IDs found with provided prefix: \")},\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"--kube-hide-dupe\", \"rmi\", \"--force\", imgID[0]).Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\t})\n\t\t\t\t\t\thelpers.Command(\"images\").Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t\t\tlines := strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\t\t\tassert.Assert(t, len(lines) == numNoTags)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_save.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/mattn/go-isatty\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nfunc SaveCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"save [flags] IMAGE [IMAGE...]\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Save one or more images to a tar archive (streamed to STDOUT by default)\",\n\t\tLong:              \"The archive implements both Docker Image Spec v1.2 and OCI Image Spec v1.0.\",\n\t\tRunE:              saveAction,\n\t\tValidArgsFunction: saveShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().StringP(\"output\", \"o\", \"\", \"Write to a file, instead of STDOUT\")\n\n\t// #region platform flags\n\t// platform is defined as StringSlice, not StringArray, to allow specifying \"--platform=amd64,arm64\"\n\tcmd.Flags().StringSlice(\"platform\", []string{}, \"Export content for a specific platform\")\n\tcmd.RegisterFlagCompletionFunc(\"platform\", completion.Platforms)\n\tcmd.Flags().Bool(\"all-platforms\", false, \"Export content for all platforms\")\n\t// #endregion\n\n\treturn cmd\n}\n\nfunc saveOptions(cmd *cobra.Command) (types.ImageSaveOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ImageSaveOptions{}, err\n\t}\n\n\tallPlatforms, err := cmd.Flags().GetBool(\"all-platforms\")\n\tif err != nil {\n\t\treturn types.ImageSaveOptions{}, err\n\t}\n\tplatform, err := cmd.Flags().GetStringSlice(\"platform\")\n\tif err != nil {\n\t\treturn types.ImageSaveOptions{}, err\n\t}\n\n\treturn types.ImageSaveOptions{\n\t\tGOptions:     globalOptions,\n\t\tAllPlatforms: allPlatforms,\n\t\tPlatform:     platform,\n\t}, err\n}\n\nfunc saveAction(cmd *cobra.Command, args []string) error {\n\toptions, err := saveOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toutput := cmd.OutOrStdout()\n\toutputPath, err := cmd.Flags().GetString(\"output\")\n\tif err != nil {\n\t\treturn err\n\t} else if outputPath != \"\" {\n\t\tf, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY, 0644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\toutput = f\n\t\tdefer f.Close()\n\t} else if out, ok := output.(*os.File); ok && isatty.IsTerminal(out.Fd()) {\n\t\treturn fmt.Errorf(\"cowardly refusing to save to a terminal. Use the -o flag or redirect\")\n\t}\n\toptions.Stdout = output\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\tif err = image.Save(ctx, client, args, options); err != nil && outputPath != \"\" {\n\t\tos.Remove(outputPath)\n\t}\n\treturn err\n}\n\nfunc saveShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show image names\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_save_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\ttesthelpers \"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestSaveContent(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttestCase := &test.Case{\n\t\t// FIXME: move to busybox for windows?\n\t\tRequire: require.Not(require.Windows),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"save\", \"-o\", filepath.Join(data.Temp().Path(), \"out.tar\"), testutil.CommonImage)\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\trootfsPath := filepath.Join(data.Temp().Path(), \"rootfs\")\n\t\t\t\t\terr := testhelpers.ExtractDockerArchive(filepath.Join(data.Temp().Path(), \"out.tar\"), rootfsPath)\n\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\tetcOSReleasePath := filepath.Join(rootfsPath, \"/etc/os-release\")\n\t\t\t\t\tetcOSReleaseBytes, err := os.ReadFile(etcOSReleasePath)\n\t\t\t\t\tassert.NilError(t, err)\n\t\t\t\t\tetcOSRelease := string(etcOSReleaseBytes)\n\t\t\t\t\tassert.Assert(t, strings.Contains(etcOSRelease, \"Alpine\"))\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestSave(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// This test relies on the fact that we can remove the common image, which definitely conflicts with others,\n\t// hence the private mode.\n\t// Further note though, that this will hide the fact this the save command could fail if some layers are missing.\n\t// See https://github.com/containerd/nerdctl/issues/3425 and others for details.\n\ttestCase.Require = nerdtest.Private\n\n\tif runtime.GOOS == \"windows\" {\n\t\ttestCase.Require = nerdtest.IsFlaky(\"https://github.com/containerd/nerdctl/issues/3524\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Single image, by id\",\n\t\t\tNoParallel:  true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(\"id\") != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"id\"))\n\t\t\t\t}\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\timg := nerdtest.InspectImage(helpers, testutil.CommonImage)\n\t\t\t\tvar id string\n\t\t\t\t// Docker and Nerdctl do not agree on what is the definition of an image ID\n\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\tid = img.ID\n\t\t\t\t} else {\n\t\t\t\t\tid = strings.Split(img.RepoDigests[0], \":\")[1]\n\t\t\t\t}\n\t\t\t\ttarPath := filepath.Join(data.Temp().Path(), \"out.tar\")\n\t\t\t\thelpers.Ensure(\"save\", \"-o\", tarPath, id)\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Ensure(\"load\", \"-i\", tarPath)\n\t\t\t\tdata.Labels().Set(\"id\", id)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"id\"), \"sh\", \"-euxc\", \"echo foo\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foo\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Image with different names, by id\",\n\t\t\tNoParallel:  true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(\"id\") != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"id\"))\n\t\t\t\t}\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\timg := nerdtest.InspectImage(helpers, testutil.CommonImage)\n\t\t\t\tvar id string\n\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\tid = img.ID\n\t\t\t\t} else {\n\t\t\t\t\tid = strings.Split(img.RepoDigests[0], \":\")[1]\n\t\t\t\t}\n\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, data.Identifier())\n\t\t\t\ttarPath := filepath.Join(data.Temp().Path(), \"out.tar\")\n\t\t\t\thelpers.Ensure(\"save\", \"-o\", tarPath, id)\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", testutil.CommonImage)\n\t\t\t\thelpers.Ensure(\"load\", \"-i\", tarPath)\n\t\t\t\tdata.Labels().Set(\"id\", id)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(\"id\"), \"sh\", \"-euxc\", \"echo foo\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foo\\n\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestSaveMultipleImagesWithSameIDAndLoad tests https://github.com/containerd/nerdctl/issues/3806\nfunc TestSaveMultipleImagesWithSameIDAndLoad(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// This test relies on the fact that we can remove the common image, which definitely conflicts with others,\n\t// hence the private mode.\n\t// Further note though, that this will hide the fact this the save command could fail if some layers are missing.\n\t// See https://github.com/containerd/nerdctl/issues/3425 and others for details.\n\ttestCase.Require = nerdtest.Private\n\n\tif runtime.GOOS == \"windows\" {\n\t\ttestCase.Require = nerdtest.IsFlaky(\"https://github.com/containerd/nerdctl/issues/3524\")\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Issue #3568 - Save multiple container images with the same image ID but different image names\",\n\t\t\tNoParallel:  true,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(\"id\") != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(\"id\"))\n\t\t\t\t}\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\timg := nerdtest.InspectImage(helpers, testutil.CommonImage)\n\t\t\t\tvar id string\n\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\tid = img.ID\n\t\t\t\t} else {\n\t\t\t\t\tid = strings.Split(img.RepoDigests[0], \":\")[1]\n\t\t\t\t}\n\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, data.Identifier())\n\t\t\t\ttarPath := filepath.Join(data.Temp().Path(), \"out.tar\")\n\t\t\t\thelpers.Ensure(\"save\", \"-o\", tarPath, testutil.CommonImage, data.Identifier())\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", id)\n\t\t\t\thelpers.Ensure(\"load\", \"-i\", tarPath)\n\t\t\t\tdata.Labels().Set(\"id\", id)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"images\", \"--no-trunc\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   []error{},\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Equal(t, strings.Count(stdout, data.Labels().Get(\"id\")), 2)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_tag.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n)\n\nfunc TagCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"tag [flags] SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]\",\n\t\tShort:             \"Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE\",\n\t\tArgs:              helpers.IsExactArgs(2),\n\t\tRunE:              tagAction,\n\t\tValidArgsFunction: tagShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc tagAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toptions := types.ImageTagOptions{\n\t\tGOptions: globalOptions,\n\t\tSource:   args[0],\n\t\tTarget:   args[1],\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn image.Tag(ctx, client, options)\n}\n\nfunc tagShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tif len(args) < 2 {\n\t\t// show image names\n\t\treturn completion.ImageNames(cmd)\n\t}\n\treturn nil, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/image/image_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/inspect/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage inspect\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\tcontainercmd \"github.com/containerd/nerdctl/v2/cmd/nerdctl/container\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\timagecmd \"github.com/containerd/nerdctl/v2/cmd/nerdctl/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"inspect\",\n\t\tShort:             \"Return low-level information on objects.\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              inspectAction,\n\t\tValidArgsFunction: inspectShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\n\taddInspectFlags(cmd)\n\n\treturn cmd\n}\n\nvar validInspectType = map[string]bool{\n\t\"container\": true,\n\t\"image\":     true,\n}\n\nfunc addInspectFlags(cmd *cobra.Command) {\n\tcmd.Flags().BoolP(\"size\", \"s\", false, \"Display total file sizes (for containers)\")\n\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"type\", \"\", \"Return JSON for specified type\")\n\tcmd.RegisterFlagCompletionFunc(\"type\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"image\", \"container\", \"\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().String(\"mode\", \"dockercompat\", `Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output`)\n\tcmd.RegisterFlagCompletionFunc(\"mode\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"dockercompat\", \"native\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnamespace := globalOptions.Namespace\n\taddress := globalOptions.Address\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tinspectType, err := cmd.Flags().GetString(\"type\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(inspectType) > 0 && !validInspectType[inspectType] {\n\t\treturn fmt.Errorf(\"%q is not a valid value for --type\", inspectType)\n\t}\n\n\t// container and image inspect can share the same client, since no `platform`\n\t// flag will be passed for image inspect.\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), namespace, address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\timagewalker := &imagewalker.ImageWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found imagewalker.Found) error {\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcontainerwalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tinspectImage := len(inspectType) == 0 || inspectType == \"image\"\n\tinspectContainer := len(inspectType) == 0 || inspectType == \"container\"\n\n\tvar imageInspectOptions types.ImageInspectOptions\n\tvar containerInspectOptions types.ContainerInspectOptions\n\tif inspectImage {\n\t\tplatform := \"\"\n\t\timageInspectOptions, err = imagecmd.InspectOptions(cmd, &platform)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif inspectContainer {\n\t\tcontainerInspectOptions, err = containercmd.InspectOptions(cmd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar errs []error\n\tvar entries []interface{}\n\tfor _, req := range args {\n\t\tvar ni int\n\t\tvar nc int\n\n\t\tif inspectImage {\n\t\t\tni, err = imagewalker.Walk(ctx, req)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif inspectContainer {\n\t\t\tnc, err = containerwalker.Walk(ctx, req)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif ni == 0 && nc == 0 {\n\t\t\terrs = append(errs, fmt.Errorf(\"no such object %s\", req))\n\t\t} else if ni > 0 {\n\t\t\tif imageEntries, err := image.Inspect(ctx, client, []string{req}, imageInspectOptions); err != nil {\n\t\t\t\terrs = append(errs, err)\n\t\t\t} else {\n\t\t\t\tentries = append(entries, imageEntries...)\n\t\t\t}\n\t\t} else if nc > 0 {\n\t\t\tif containerEntries, err := container.Inspect(ctx, client, []string{req}, containerInspectOptions); err != nil {\n\t\t\t\terrs = append(errs, err)\n\t\t\t} else {\n\t\t\t\tentries = append(entries, containerEntries...)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"%d errors: %v\", len(errs), errs)\n\t}\n\n\tif formatErr := formatter.FormatSlice(format, cmd.OutOrStdout(), entries); formatErr != nil {\n\t\tlog.G(ctx).Error(formatErr)\n\t}\n\n\treturn nil\n}\n\nfunc inspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show container names\n\tcontainers, _ := completion.ContainerNames(cmd, nil)\n\t// show image names\n\timages, _ := completion.ImageNames(cmd)\n\treturn append(containers, images...), cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/inspect/inspect_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage inspect\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n\nfunc TestInspectSimpleCase(t *testing.T) {\n\tnerdtest.Setup()\n\ttestCase := &test.Case{\n\t\tDescription: \"inspect container and image return one single json array\",\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tidentifier := data.Identifier()\n\t\t\thelpers.Ensure(\"run\", \"-d\", \"--quiet\", \"--name\", identifier, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tidentifier := data.Identifier()\n\t\t\thelpers.Anyhow(\"rm\", \"-f\", identifier)\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"inspect\", testutil.CommonImage, data.Identifier())\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tvar inspectResult []json.RawMessage\n\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &inspectResult)\n\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\tassert.Equal(t, len(inspectResult), 2, \"Unexpectedly got multiple results\\n\")\n\n\t\t\t\t\tvar dci dockercompat.Image\n\t\t\t\t\terr = json.Unmarshal(inspectResult[0], &dci)\n\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\tinspecti := nerdtest.InspectImage(helpers, testutil.CommonImage)\n\t\t\t\t\tassert.Equal(t, dci.ID, inspecti.ID, \"id should match\\n\")\n\n\t\t\t\t\tvar dcc dockercompat.Container\n\t\t\t\t\terr = json.Unmarshal(inspectResult[1], &dcc)\n\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\tinspectc := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\t\tassert.Equal(t, dcc.ID, inspectc.ID, \"id should match\\n\")\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/internal/internal.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage internal\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc Command() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"internal\",\n\t\tShort:         \"DO NOT EXECUTE MANUALLY\",\n\t\tHidden:        true,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.AddCommand(\n\t\tnewInternalOCIHookCommandCommand(),\n\t)\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/internal/internal_oci_hook.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage internal\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ocihook\"\n)\n\nfunc newInternalOCIHookCommandCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"oci-hook\",\n\t\tShort:         \"OCI hook\",\n\t\tRunE:          internalOCIHookAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\treturn cmd\n}\n\nfunc internalOCIHookAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := \"\"\n\tif len(args) > 0 {\n\t\tevent = args[0]\n\t}\n\tif event == \"\" {\n\t\treturn errors.New(\"event type needs to be passed\")\n\t}\n\tdataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcniPath := globalOptions.CNIPath\n\tcniNetconfpath := globalOptions.CNINetConfPath\n\tbridgeIP := globalOptions.BridgeIP\n\treturn ocihook.Run(os.Stdin, os.Stderr, event,\n\t\tdataStore,\n\t\tcniPath,\n\t\tcniNetconfpath,\n\t\tbridgeIP,\n\t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc NewIPFSCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"ipfs\",\n\t\tShort:         \"Distributing images on IPFS\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tnewIPFSRegistryCommand(),\n\t)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_compose_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\nfunc TestIPFSCompNoBuild(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst ipfsAddrKey = \"ipfsAddrKey\"\n\n\tvar ipfsRegistry *registry.Server\n\n\ttestCase.Require = require.All(\n\t\trequire.Linux,\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.Registry,\n\t\tnerdtest.IPFS,\n\t\tnerdtest.IsFlaky(\"https://github.com/containerd/nerdctl/issues/3510\"),\n\t\t// See note below\n\t\t// nerdtest.Private,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// Start Kubo\n\t\tipfsRegistry = registry.NewKuboRegistry(data, helpers, t, nil, 0, nil)\n\t\tipfsRegistry.Setup(data, helpers)\n\t\tdata.Labels().Set(ipfsAddrKey, fmt.Sprintf(\"/ip4/%s/tcp/%d\", ipfsRegistry.IP, ipfsRegistry.Port))\n\n\t\t// Ensure we have the images\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.WordpressImage)\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.MariaDBImage)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\tsubtestTestIPFSCompNoB(t, false, false),\n\t\tsubtestTestIPFSCompNoB(t, true, false),\n\t\tsubtestTestIPFSCompNoB(t, false, true),\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif ipfsRegistry != nil {\n\t\t\tipfsRegistry.Cleanup(data, helpers)\n\t\t}\n\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.WordpressImage)\n\t\thelpers.Anyhow(\"rmi\", \"-f\", testutil.MariaDBImage)\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc subtestTestIPFSCompNoB(t *testing.T, stargz bool, byAddr bool) *test.Case {\n\tt.Helper()\n\n\tconst ipfsAddrKey = \"ipfsAddrKey\"\n\tconst mariaImageCIDKey = \"mariaImageCIDKey\"\n\tconst wordpressImageCIDKey = \"wordpressImageCIDKey\"\n\tconst composeExtraKey = \"composeExtraKey\"\n\n\ttestCase := &test.Case{}\n\n\ttestCase.Description += \"with\"\n\n\tif !stargz {\n\t\ttestCase.Description += \"-no\"\n\t}\n\ttestCase.Description += \"-stargz\"\n\n\tif !byAddr {\n\t\ttestCase.Description += \"-no\"\n\t}\n\ttestCase.Description += \"-byAddr\"\n\n\tif stargz {\n\t\ttestCase.Require = nerdtest.Stargz\n\t}\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar ipfsCIDWP, ipfsCIDMD string\n\t\tif stargz {\n\t\t\tipfsCIDWP = pushToIPFS(helpers, testutil.WordpressImage, \"--estargz\")\n\t\t\tipfsCIDMD = pushToIPFS(helpers, testutil.MariaDBImage, \"--estargz\")\n\t\t} else if byAddr {\n\t\t\tipfsCIDWP = pushToIPFS(helpers, testutil.WordpressImage, \"--ipfs-address=\"+data.Labels().Get(ipfsAddrKey))\n\t\t\tipfsCIDMD = pushToIPFS(helpers, testutil.MariaDBImage, \"--ipfs-address=\"+data.Labels().Get(ipfsAddrKey))\n\t\t\tdata.Labels().Set(composeExtraKey, \"--ipfs-address=\"+data.Labels().Get(ipfsAddrKey))\n\t\t} else {\n\t\t\tipfsCIDWP = pushToIPFS(helpers, testutil.WordpressImage)\n\t\t\tipfsCIDMD = pushToIPFS(helpers, testutil.MariaDBImage)\n\t\t}\n\t\tdata.Labels().Set(wordpressImageCIDKey, ipfsCIDWP)\n\t\tdata.Labels().Set(mariaImageCIDKey, ipfsCIDMD)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\t// NOTE:\n\t\t// Removing these images locally forces tests to be sequentials (as IPFS being content addressable,\n\t\t// they have the same cid - except for the estargz version obviously)\n\t\t// Deliberately electing to not remove them here so that we can parallelize and cut down the running time\n\t\t/*\n\t\t\tif data.Labels().Get(mariaImageCIDKey) != \"\" {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mariaImageCIDKey))\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(wordpressImageCIDKey))\n\t\t\t}\n\t\t*/\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\tsafePort, err := portlock.Acquire(0)\n\t\tassert.NilError(helpers.T(), err)\n\t\tdata.Labels().Set(\"wordpressPort\", strconv.Itoa(safePort))\n\t\tcomposeUP(data, helpers, fmt.Sprintf(`\nversion: '3.1'\n\nservices:\n\n  wordpress:\n    image: ipfs://%s\n    restart: always\n    ports:\n      - %d:80\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    # FIXME: this is flaky and will make the container fail on occasions\n    volumes:\n      - wordpress:/var/www/html\n\n  db:\n    image: ipfs://%s\n    restart: always\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n`, data.Labels().Get(wordpressImageCIDKey), safePort, data.Labels().Get(mariaImageCIDKey)), data.Labels().Get(composeExtraKey))\n\t\t// FIXME: need to break down composeUP into testable commands instead\n\t\t// Right now, this is just a dummy placeholder\n\t\treturn helpers.Command(\"info\")\n\t}\n\n\ttestCase.Expected = test.Expects(0, nil, nil)\n\n\treturn testCase\n}\n\nfunc TestIPFSCompBuild(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tvar ipfsServer test.TestableCommand\n\tvar comp *testutil.ComposeDir\n\n\tconst mainImageCIDKey = \"mainImageCIDKey\"\n\tsafePort, err := portlock.Acquire(0)\n\tassert.NilError(t, err)\n\tvar listenAddr = \"localhost:\" + strconv.Itoa(safePort)\n\n\ttestCase.Require = require.All(\n\t\t// Linux only\n\t\trequire.Linux,\n\t\t// Obviously not docker supported\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.Build,\n\t\tnerdtest.IPFS,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\t// Get alpine\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.NginxAlpineImage)\n\t\t// Start a local ipfs backed registry\n\t\t// FIXME: this is bad and likely to collide with other tests\n\t\tipfsServer = helpers.Command(\"ipfs\", \"registry\", \"serve\", \"--listen-registry\", listenAddr)\n\t\t// This should not take longer than that\n\t\tipfsServer.WithTimeout(30 * time.Second)\n\t\tipfsServer.Background()\n\t\t// Apparently necessary to let it start...\n\t\ttime.Sleep(time.Second)\n\n\t\t// Save nginx to ipfs\n\t\tdata.Labels().Set(mainImageCIDKey, pushToIPFS(helpers, testutil.NginxAlpineImage))\n\n\t\tconst dockerComposeYAML = `\nservices:\n  web:\n    build: .\n    ports:\n    - 8081:80\n`\n\t\tdockerfile := fmt.Sprintf(`FROM %s/ipfs/%s\nCOPY index.html /usr/share/nginx/html/index.html\n`, listenAddr, data.Labels().Get(mainImageCIDKey))\n\n\t\tcomp = testutil.NewComposeDir(t, dockerComposeYAML)\n\t\tcomp.WriteFile(\"Dockerfile\", dockerfile)\n\t\tcomp.WriteFile(\"index.html\", data.Identifier(\"indexhtml\"))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif ipfsServer != nil {\n\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\tipfsServer.Signal(os.Kill)\n\t\t}\n\t\tif comp != nil {\n\t\t\thelpers.Anyhow(\"compose\", \"-f\", comp.YAMLFullPath(), \"down\", \"-v\")\n\t\t\tcomp.CleanUp()\n\t\t}\n\t}\n\n\ttestCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\treturn helpers.Command(\"compose\", \"-f\", comp.YAMLFullPath(), \"up\", \"-d\", \"--build\")\n\t}\n\n\ttestCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\treturn &test.Expected{\n\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\tresp, err := nettestutil.HTTPGet(\"http://127.0.0.1:8081\", 5, false)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\trespBody, err := io.ReadAll(resp.Body)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tt.Log(fmt.Sprintf(\"respBody=%q\", respBody))\n\t\t\t\tassert.Assert(t, strings.Contains(string(respBody), data.Identifier(\"indexhtml\")))\n\t\t\t},\n\t\t}\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc composeUP(data test.Data, helpers test.Helpers, dockerComposeYAML string, opts string) {\n\tcomp := testutil.NewComposeDir(helpers.T(), dockerComposeYAML)\n\t// defer comp.CleanUp()\n\n\t// Because it might or might not happen, and\n\thelpers.Anyhow(\"compose\", \"-f\", comp.YAMLFullPath(), \"down\", \"-v\")\n\tdefer helpers.Anyhow(\"compose\", \"-f\", comp.YAMLFullPath(), \"down\", \"-v\")\n\n\tprojectName := comp.ProjectName()\n\n\targs := []string{\"compose\", \"-f\", comp.YAMLFullPath()}\n\tif opts != \"\" {\n\t\targs = append(args, opts)\n\t}\n\n\thelpers.Ensure(append(args, \"up\", \"--quiet-pull\", \"-d\")...)\n\n\thelpers.Ensure(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName))\n\thelpers.Ensure(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName))\n\n\tcheckWordpress := func() error {\n\t\t// FIXME: see other notes on using the same port repeatedly\n\t\tresp, err := nettestutil.HTTPGet(\"http://127.0.0.1:\"+data.Labels().Get(\"wordpressPort\"), 5, false)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trespBody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !strings.Contains(string(respBody), testutil.WordpressIndexHTMLSnippet) {\n\t\t\treturn fmt.Errorf(\"respBody does not contain %q (%s)\", testutil.WordpressIndexHTMLSnippet, string(respBody))\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar wordpressWorking bool\n\tvar err error\n\t// 15 seconds is long enough\n\tfor i := 0; i < 5; i++ {\n\t\terr = checkWordpress()\n\t\tif err == nil {\n\t\t\twordpressWorking = true\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(3 * time.Second)\n\t}\n\n\tif !wordpressWorking {\n\t\tccc := helpers.Capture(\"ps\", \"-a\")\n\t\thelpers.T().Log(ccc)\n\t\thelpers.T().Log(helpers.Err(\"logs\", projectName+\"-wordpress-1\"))\n\t\thelpers.T().Log(fmt.Sprintf(\"wordpress is not working %v\", err))\n\t\thelpers.T().FailNow()\n\t}\n\n\thelpers.Ensure(\"compose\", \"-f\", comp.YAMLFullPath(), \"down\", \"-v\")\n\thelpers.Fail(\"volume\", \"inspect\", fmt.Sprintf(\"%s_db\", projectName))\n\thelpers.Fail(\"network\", \"inspect\", fmt.Sprintf(\"%s_default\", projectName))\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_kubo_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestIPFSAddrWithKubo(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst mainImageCIDKey = \"mainImagemainImageCIDKey\"\n\tconst ipfsAddrKey = \"ipfsAddrKey\"\n\n\tvar ipfsRegistry *registry.Server\n\n\ttestCase.Require = require.All(\n\t\trequire.Linux,\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.Registry,\n\t\tnerdtest.Private,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\n\t\tipfsRegistry = registry.NewKuboRegistry(data, helpers, t, nil, 0, nil)\n\t\tipfsRegistry.Setup(data, helpers)\n\t\tipfsAddr := fmt.Sprintf(\"/ip4/%s/tcp/%d\", ipfsRegistry.IP, ipfsRegistry.Port)\n\t\tdata.Labels().Set(ipfsAddrKey, ipfsAddr)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif ipfsRegistry != nil {\n\t\t\tipfsRegistry.Cleanup(data, helpers)\n\t\t}\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with default snapshotter\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tipfsCID := pushToIPFS(helpers, testutil.CommonImage, fmt.Sprintf(\"--ipfs-address=%s\", data.Labels().Get(ipfsAddrKey)))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"--ipfs-address\", data.Labels().Get(ipfsAddrKey), \"ipfs://\"+ipfsCID)\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, ipfsCID)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(mainImageCIDKey), \"echo\", \"hello\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with stargz snapshotter\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.Stargz,\n\t\t\t\tnerdtest.Private,\n\t\t\t\tnerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/3475\"),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tipfsCID := pushToIPFS(helpers, testutil.CommonImage, fmt.Sprintf(\"--ipfs-address=%s\", data.Labels().Get(ipfsAddrKey)), \"--estargz\")\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"--ipfs-address\", data.Labels().Get(ipfsAddrKey), \"ipfs://\"+ipfsCID)\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, ipfsCID)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(mainImageCIDKey), \"ls\", \"/.stargz-snapshotter\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"sha256:.*[.]json[\\n]\"))),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_registry.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc newIPFSRegistryCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"registry\",\n\t\tShort:         \"Manage read-only registry backed by IPFS\",\n\t\tPreRunE:       helpers.CheckExperimental(\"ipfs\"),\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tnewIPFSRegistryServeCommand(),\n\t)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_registry_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc pushToIPFS(helpers test.Helpers, name string, opts ...string) string {\n\tvar ipfsCID string\n\tcmd := helpers.Command(\"push\", \"ipfs://\"+name)\n\tcmd.WithArgs(opts...)\n\tcmd.Run(&test.Expected{\n\t\tOutput: func(stdout string, t tig.T) {\n\t\t\tlines := strings.Split(stdout, \"\\n\")\n\t\t\tassert.Equal(t, len(lines) >= 2, true)\n\t\t\tipfsCID = lines[len(lines)-2]\n\t\t},\n\t})\n\treturn ipfsCID\n}\n\nfunc TestIPFSNerdctlRegistry(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// FIXME: this is bad and likely to collide with other tests\n\tconst listenAddr = \"localhost:5555\"\n\n\tconst ipfsImageURLKey = \"ipfsImageURLKey\"\n\n\tvar ipfsServer test.TestableCommand\n\n\ttestCase.Require = require.All(\n\t\trequire.Linux,\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.IPFS,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\n\t\t// Start a local ipfs backed registry\n\t\tipfsServer = helpers.Command(\"ipfs\", \"registry\", \"serve\", \"--listen-registry\", listenAddr)\n\t\t// This should not take longer than that\n\t\tipfsServer.WithTimeout(30 * time.Second)\n\t\tipfsServer.Background()\n\t\t// Apparently necessary to let it start...\n\t\ttime.Sleep(time.Second)\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif ipfsServer != nil {\n\t\t\t// Close the server once done\n\t\t\tipfsServer.Signal(os.Kill)\n\t\t}\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with default snapshotter\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(ipfsImageURLKey, listenAddr+\"/ipfs/\"+pushToIPFS(helpers, testutil.CommonImage))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", data.Labels().Get(ipfsImageURLKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(ipfsImageURLKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(ipfsImageURLKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(ipfsImageURLKey), \"echo\", \"hello\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with stargz snapshotterr\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire:     nerdtest.Stargz,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(ipfsImageURLKey, listenAddr+\"/ipfs/\"+pushToIPFS(helpers, testutil.CommonImage, \"--estargz\"))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", data.Labels().Get(ipfsImageURLKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(ipfsImageURLKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(ipfsImageURLKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(ipfsImageURLKey), \"ls\", \"/.stargz-snapshotter\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"sha256:.*[.]json[\\n]\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with build\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire:     nerdtest.Build,\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"built-image\"))\n\t\t\t\tif data.Labels().Get(ipfsImageURLKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(ipfsImageURLKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(ipfsImageURLKey, listenAddr+\"/ipfs/\"+pushToIPFS(helpers, testutil.CommonImage))\n\n\t\t\t\tdockerfile := fmt.Sprintf(`FROM %s\nCMD [\"echo\", \"nerdctl-build-test-string\"]\n\t`, data.Labels().Get(ipfsImageURLKey))\n\n\t\t\t\tbuildCtx := data.Temp().Path()\n\t\t\t\tdata.Temp().Save(dockerfile, \"Dockerfile\")\n\n\t\t\t\thelpers.Ensure(\"build\", \"-t\", data.Identifier(\"built-image\"), buildCtx)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Identifier(\"built-image\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"nerdctl-build-test-string\\n\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_registry_serve.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/ipfs\"\n)\n\nconst (\n\tdefaultIPFSRegistry            = \"localhost:5050\"\n\tdefaultIPFSReadRetryNum        = 0\n\tdefaultIPFSReadTimeoutDuration = 0\n)\n\nfunc newIPFSRegistryServeCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"serve\",\n\t\tShort:         \"serve read-only registry backed by IPFS on localhost.\",\n\t\tRunE:          ipfsRegistryServeAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\thelpers.AddStringFlag(cmd, \"listen-registry\", nil, defaultIPFSRegistry, \"IPFS_REGISTRY_SERVE_LISTEN_REGISTRY\", \"address to listen\")\n\thelpers.AddStringFlag(cmd, \"ipfs-address\", nil, \"\", \"IPFS_REGISTRY_SERVE_IPFS_ADDRESS\", \"multiaddr of IPFS API (default is pulled from $IPFS_PATH/api file. If $IPFS_PATH env var is not present, it defaults to ~/.ipfs)\")\n\thelpers.AddIntFlag(cmd, \"read-retry-num\", nil, defaultIPFSReadRetryNum, \"IPFS_REGISTRY_SERVE_READ_RETRY_NUM\", \"times to retry query on IPFS. Zero or lower means no retry.\")\n\thelpers.AddDurationFlag(cmd, \"read-timeout\", nil, defaultIPFSReadTimeoutDuration, \"IPFS_REGISTRY_SERVE_READ_TIMEOUT\", \"timeout duration of a read request to IPFS. Zero means no timeout.\")\n\n\treturn cmd\n}\n\nfunc processIPFSRegistryServeOptions(cmd *cobra.Command) (opts types.IPFSRegistryServeOptions, err error) {\n\tipfsAddressStr, err := cmd.Flags().GetString(\"ipfs-address\")\n\tif err != nil {\n\t\treturn types.IPFSRegistryServeOptions{}, err\n\t}\n\tlistenAddress, err := cmd.Flags().GetString(\"listen-registry\")\n\tif err != nil {\n\t\treturn types.IPFSRegistryServeOptions{}, err\n\t}\n\treadTimeout, err := cmd.Flags().GetDuration(\"read-timeout\")\n\tif err != nil {\n\t\treturn types.IPFSRegistryServeOptions{}, err\n\t}\n\treadRetryNum, err := cmd.Flags().GetInt(\"read-retry-num\")\n\tif err != nil {\n\t\treturn types.IPFSRegistryServeOptions{}, err\n\t}\n\treturn types.IPFSRegistryServeOptions{\n\t\tListenRegistry: listenAddress,\n\t\tIPFSAddress:    ipfsAddressStr,\n\t\tReadTimeout:    readTimeout,\n\t\tReadRetryNum:   readRetryNum,\n\t}, nil\n}\n\nfunc ipfsRegistryServeAction(cmd *cobra.Command, args []string) error {\n\toptions, err := processIPFSRegistryServeOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn ipfs.RegistryServe(options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_simple_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestIPFSSimple(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst mainImageCIDKey = \"mainImageCIDKey\"\n\tconst transformedImageCIDKey = \"transformedImageCIDKey\"\n\n\ttestCase.Require = require.All(\n\t\trequire.Linux,\n\t\trequire.Not(nerdtest.Docker),\n\t\tnerdtest.IPFS,\n\t\t// We constantly rmi the image by its CID which is shared across tests, so, we make this group private\n\t\t// and every subtest NoParallel\n\t\tnerdtest.Private,\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"pull\", \"--quiet\", testutil.CommonImage)\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with default snapshotter\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, pushToIPFS(helpers, testutil.CommonImage))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(mainImageCIDKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(mainImageCIDKey), \"echo\", \"hello\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with stargz snapshotter\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.Stargz,\n\t\t\t\tnerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/3475\"),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, pushToIPFS(helpers, testutil.CommonImage, \"--estargz\"))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(mainImageCIDKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(mainImageCIDKey), \"ls\", \"/.stargz-snapshotter\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"sha256:.*[.]json[\\n]\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with commit and push\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, pushToIPFS(helpers, testutil.CommonImage))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(mainImageCIDKey))\n\n\t\t\t\t// Run a container that does modify something, then commit and push it\n\t\t\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(\"commit-container\"), data.Labels().Get(mainImageCIDKey), \"sh\", \"-c\", \"--\", \"echo hello > /hello\")\n\t\t\t\thelpers.Ensure(\"commit\", data.Identifier(\"commit-container\"), data.Identifier(\"commit-image\"))\n\t\t\t\tdata.Labels().Set(transformedImageCIDKey, pushToIPFS(helpers, data.Identifier(\"commit-image\")))\n\n\t\t\t\t// Clean-up\n\t\t\t\thelpers.Ensure(\"rm\", data.Identifier(\"commit-container\"))\n\t\t\t\thelpers.Ensure(\"rmi\", data.Identifier(\"commit-image\"))\n\n\t\t\t\t// Pull back the committed image\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(transformedImageCIDKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"commit-container\"))\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"commit-image\"))\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(transformedImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(transformedImageCIDKey), \"cat\", \"/hello\")\n\t\t\t},\n\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"hello\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with commit and push, stargz lazy pulling\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire: require.All(\n\t\t\t\tnerdtest.Stargz,\n\t\t\t\tnerdtest.NerdctlNeedsFixing(\"https://github.com/containerd/nerdctl/issues/3475\"),\n\t\t\t),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, pushToIPFS(helpers, testutil.CommonImage, \"--estargz\"))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(mainImageCIDKey))\n\n\t\t\t\t// Run a container that does modify something, then commit and push it\n\t\t\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(\"commit-container\"), data.Labels().Get(mainImageCIDKey), \"sh\", \"-c\", \"--\", \"echo hello > /hello\")\n\t\t\t\thelpers.Ensure(\"commit\", data.Identifier(\"commit-container\"), data.Identifier(\"commit-image\"))\n\t\t\t\tdata.Labels().Set(transformedImageCIDKey, pushToIPFS(helpers, data.Identifier(\"commit-image\")))\n\n\t\t\t\t// Clean-up\n\t\t\t\thelpers.Ensure(\"rm\", data.Identifier(\"commit-container\"))\n\t\t\t\thelpers.Ensure(\"rmi\", data.Identifier(\"commit-image\"))\n\n\t\t\t\t// Pull back the image\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(transformedImageCIDKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"commit-container\"))\n\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier(\"commit-image\"))\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(transformedImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", data.Labels().Get(transformedImageCIDKey), \"sh\", \"-c\", \"--\", \"cat /hello && ls /.stargz-snapshotter\")\n\t\t\t},\n\n\t\t\tExpected: test.Expects(0, nil, expect.Match(regexp.MustCompile(\"hello[\\n]sha256:.*[.]json[\\n]\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with encryption\",\n\t\t\tNoParallel:  true,\n\t\t\tRequire:     require.Binary(\"openssl\"),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(mainImageCIDKey, pushToIPFS(helpers, testutil.CommonImage))\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"ipfs://\"+data.Labels().Get(mainImageCIDKey))\n\n\t\t\t\t// Prep a key pair\n\t\t\t\tpri, pub := nerdtest.GenerateJWEKeyPair(data, helpers)\n\t\t\t\tdata.Labels().Set(\"prv\", pri)\n\t\t\t\tdata.Labels().Set(\"pub\", pub)\n\n\t\t\t\t// Encrypt the image, and verify it is encrypted\n\t\t\t\thelpers.Ensure(\"image\", \"encrypt\", \"--recipient=jwe:\"+pub, data.Labels().Get(mainImageCIDKey), data.Identifier(\"encrypted\"))\n\t\t\t\tcmd := helpers.Command(\"image\", \"inspect\", \"--mode=native\", \"--format={{len .Index.Manifests}}\", data.Identifier(\"encrypted\"))\n\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\tOutput: expect.Equals(\"1\\n\"),\n\t\t\t\t})\n\t\t\t\tcmd = helpers.Command(\"image\", \"inspect\", \"--mode=native\", \"--format={{json (index .Manifest.Layers 0) }}\", data.Identifier(\"encrypted\"))\n\t\t\t\tcmd.Run(&test.Expected{\n\t\t\t\t\tOutput: expect.Contains(\"org.opencontainers.image.enc.keys.jwe\"),\n\t\t\t\t})\n\n\t\t\t\t// Push the encrypted image and save the CID\n\t\t\t\tdata.Labels().Set(transformedImageCIDKey, pushToIPFS(helpers, data.Identifier(\"encrypted\")))\n\n\t\t\t\t// Remove both images locally\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\thelpers.Ensure(\"rmi\", \"-f\", data.Labels().Get(transformedImageCIDKey))\n\n\t\t\t\t// Pull back without unpacking\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", \"--unpack=false\", \"ipfs://\"+data.Labels().Get(transformedImageCIDKey))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(mainImageCIDKey) != \"\" {\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(mainImageCIDKey))\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Labels().Get(transformedImageCIDKey))\n\t\t\t\t}\n\t\t\t},\n\t\t\tSubTests: []*test.Case{\n\t\t\t\t{\n\t\t\t\t\tDescription: \"decrypt with pub key does not work\",\n\t\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"decrypted\"))\n\t\t\t\t\t},\n\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\treturn helpers.Command(\"image\", \"decrypt\", \"--key=\"+data.Labels().Get(\"pub\"), data.Labels().Get(transformedImageCIDKey), data.Identifier(\"decrypted\"))\n\t\t\t\t\t},\n\t\t\t\t\tExpected: test.Expects(1, nil, nil),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDescription: \"decrypt with priv key does work\",\n\t\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"decrypted\"))\n\t\t\t\t\t},\n\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\treturn helpers.Command(\"image\", \"decrypt\", \"--key=\"+data.Labels().Get(\"prv\"), data.Labels().Get(transformedImageCIDKey), data.Identifier(\"decrypted\"))\n\t\t\t\t\t},\n\t\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/ipfs/ipfs_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/issues/issues_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package issues is meant to document testing for complex scenarios type of issues that cannot simply be ascribed\n// to a specific package.\npackage issues\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestIssue3425(t *testing.T) {\n\tnerdtest.Setup()\n\n\tvar reg *registry.Server\n\n\ttestCase := &test.Case{\n\t\tRequire: nerdtest.Registry,\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\treg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)\n\t\t\treg.Setup(data, helpers)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif reg != nil {\n\t\t\t\treg.Cleanup(data, helpers)\n\t\t\t}\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"with tag\",\n\t\t\t\tRequire:     nerdtest.Private,\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tidentifier := data.Identifier()\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", identifier, testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"rm\", \"-f\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"tag\", testutil.CommonImage, fmt.Sprintf(\"localhost:%d/%s\", reg.Port, identifier))\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tidentifier := data.Identifier()\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", identifier)\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", fmt.Sprintf(\"localhost:%d/%s\", reg.Port, identifier))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", fmt.Sprintf(\"localhost:%d/%s\", reg.Port, data.Identifier()))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"with commit\",\n\t\t\t\tRequire:     nerdtest.Private,\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\tidentifier := data.Identifier()\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", identifier, testutil.CommonImage, \"touch\", \"/something\")\n\t\t\t\t\thelpers.Ensure(\"image\", \"rm\", \"-f\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"commit\", identifier, fmt.Sprintf(\"localhost:%d/%s\", reg.Port, identifier))\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", fmt.Sprintf(\"localhost:%d/%s\", reg.Port, data.Identifier()))\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"push\", fmt.Sprintf(\"localhost:%d/%s\", reg.Port, data.Identifier()))\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"with save\",\n\t\t\t\tRequire:     nerdtest.Private,\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"rm\", \"-f\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"save\", testutil.CommonImage)\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"with convert\",\n\t\t\t\tRequire: require.All(\n\t\t\t\t\tnerdtest.Private,\n\t\t\t\t\trequire.Not(require.Windows),\n\t\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\t),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"rm\", \"-f\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"convert\", \"--oci\", \"--estargz\", testutil.CommonImage, data.Identifier())\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"with ipfs\",\n\t\t\t\tRequire: require.All(\n\t\t\t\t\tnerdtest.Private,\n\t\t\t\t\tnerdtest.IPFS,\n\t\t\t\t\trequire.Not(require.Windows),\n\t\t\t\t\trequire.Not(nerdtest.Docker),\n\t\t\t\t),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"rm\", \"-f\", testutil.CommonImage)\n\t\t\t\t\thelpers.Ensure(\"image\", \"pull\", \"--quiet\", testutil.CommonImage)\n\t\t\t\t},\n\t\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\t\thelpers.Anyhow(\"rmi\", \"-f\", data.Identifier())\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\treturn helpers.Command(\"image\", \"push\", \"ipfs://\"+testutil.CommonImage)\n\t\t\t\t},\n\t\t\t\tExpected: test.Expects(0, nil, nil),\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/issues/main_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage issues\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n\n// TestIssue108 tests https://github.com/containerd/nerdctl/issues/108\n// (\"`nerdctl run --net=host -it` fails while `nerdctl run -it --net=host` works\")\nfunc TestIssue108(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"-it --net=host\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"--quiet\", \"-it\", \"--rm\", \"--net=host\", testutil.CommonImage, \"echo\", \"this was always working\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"this was always working\\r\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"--net=host -it\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Command(\"run\", \"--quiet\", \"--rm\", \"--net=host\", \"-it\", testutil.CommonImage, \"echo\", \"this was not working due to issue #108\")\n\t\t\t\tcmd.WithPseudoTTY()\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"this was not working due to issue #108\\r\\n\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/login/login.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/login\"\n)\n\nfunc Command() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"login [flags] [SERVER]\",\n\t\tArgs:          cobra.MaximumNArgs(1),\n\t\tShort:         \"Log in to a container registry\",\n\t\tRunE:          loginAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringP(\"username\", \"u\", \"\", \"Username\")\n\tcmd.Flags().StringP(\"password\", \"p\", \"\", \"Password\")\n\tcmd.Flags().Bool(\"password-stdin\", false, \"Take the password from stdin\")\n\treturn cmd\n}\n\nfunc loginOptions(cmd *cobra.Command) (types.LoginCommandOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.LoginCommandOptions{}, err\n\t}\n\n\tusername, err := cmd.Flags().GetString(\"username\")\n\tif err != nil {\n\t\treturn types.LoginCommandOptions{}, err\n\t}\n\tpassword, err := cmd.Flags().GetString(\"password\")\n\tif err != nil {\n\t\treturn types.LoginCommandOptions{}, err\n\t}\n\tpasswordStdin, err := cmd.Flags().GetBool(\"password-stdin\")\n\tif err != nil {\n\t\treturn types.LoginCommandOptions{}, err\n\t}\n\n\tif strings.Contains(username, \":\") {\n\t\treturn types.LoginCommandOptions{}, errors.New(\"username cannot contain colons\")\n\t}\n\n\tif password != \"\" {\n\t\tlog.L.Warn(\"WARNING! Using --password via the CLI is insecure. Use --password-stdin.\")\n\t\tif passwordStdin {\n\t\t\treturn types.LoginCommandOptions{}, errors.New(\"--password and --password-stdin are mutually exclusive\")\n\t\t}\n\t}\n\n\tif passwordStdin {\n\t\tif username == \"\" {\n\t\t\treturn types.LoginCommandOptions{}, errors.New(\"must provide --username with --password-stdin\")\n\t\t}\n\n\t\tcontents, err := io.ReadAll(cmd.InOrStdin())\n\t\tif err != nil {\n\t\t\treturn types.LoginCommandOptions{}, err\n\t\t}\n\n\t\tpassword = strings.TrimSuffix(string(contents), \"\\n\")\n\t\tpassword = strings.TrimSuffix(password, \"\\r\")\n\t}\n\treturn types.LoginCommandOptions{\n\t\tGOptions: globalOptions,\n\t\tUsername: username,\n\t\tPassword: password,\n\t}, nil\n}\n\nfunc loginAction(cmd *cobra.Command, args []string) error {\n\toptions, err := loginOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(args) == 1 {\n\t\toptions.ServerAddress = args[0]\n\t}\n\n\treturn login.Login(cmd.Context(), options, cmd.OutOrStdout())\n}\n"
  },
  {
    "path": "cmd/nerdctl/login/login_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// https://docs.docker.com/reference/cli/dockerd/#insecure-registries\n// Local registries, whose IP address falls in the 127.0.0.0/8 range, are automatically marked as insecure as of Docker 1.3.2.\n// It isn't recommended to rely on this, as it may change in the future.\n// \"--insecure\" means that either the certificates are untrusted, or that the protocol is plain http\npackage login\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/icmd\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/utils\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testca\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry\"\n)\n\ntype Client struct {\n\targs       []string\n\tconfigPath string\n}\n\nfunc (ag *Client) WithInsecure(value bool) *Client {\n\tag.args = append(ag.args, \"--insecure-registry=\"+strconv.FormatBool(value))\n\treturn ag\n}\n\nfunc (ag *Client) WithHostsDir(hostDirs string) *Client {\n\tag.args = append(ag.args, \"--hosts-dir\", hostDirs)\n\treturn ag\n}\n\nfunc (ag *Client) WithCredentials(username, password string) *Client {\n\tif username != \"\" {\n\t\tag.args = append(ag.args, \"--username\", username)\n\t}\n\tif password != \"\" {\n\t\tag.args = append(ag.args, \"--password\", password)\n\t}\n\treturn ag\n}\n\nfunc (ag *Client) WithConfigPath(value string) *Client {\n\tag.configPath = value\n\treturn ag\n}\n\nfunc (ag *Client) GetConfigPath() string {\n\treturn ag.configPath\n}\n\nfunc (ag *Client) Run(base *testutil.Base, host string) *testutil.Cmd {\n\tif ag.configPath == \"\" {\n\t\tag.configPath, _ = os.MkdirTemp(base.T.TempDir(), \"docker-config\")\n\t}\n\targs := []string{\"login\"}\n\tif !nerdtest.IsDocker() {\n\t\targs = append(args, \"--debug-full\")\n\t}\n\targs = append(args, ag.args...)\n\ticmdCmd := icmd.Command(base.Binary, append(base.Args, append(args, host)...)...)\n\ticmdCmd.Env = append(base.Env, \"HOME=\"+os.Getenv(\"HOME\"), \"DOCKER_CONFIG=\"+ag.configPath)\n\n\treturn &testutil.Cmd{\n\t\tCmd:  icmdCmd,\n\t\tBase: base,\n\t}\n}\n\nfunc TestLoginPersistence(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tt.Parallel()\n\n\t// Retrieve from the store\n\ttestCases := []struct {\n\t\tauth string\n\t}{\n\t\t{\n\t\t\t\"basic\",\n\t\t},\n\t\t{\n\t\t\t\"token\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(fmt.Sprintf(\"Server %s\", tc.auth), func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tusername := utils.RandomStringBase64(30) + \"∞\"\n\t\t\tpassword := utils.RandomStringBase64(30) + \":∞\"\n\n\t\t\t// Add the requested authentication\n\t\t\tvar auth testregistry.Auth\n\t\t\tvar dependentCleanup func(error)\n\n\t\t\tauth = &testregistry.NoAuth{}\n\t\t\tif tc.auth == \"basic\" {\n\t\t\t\tauth = &testregistry.BasicAuth{\n\t\t\t\t\tUsername: username,\n\t\t\t\t\tPassword: password,\n\t\t\t\t}\n\t\t\t} else if tc.auth == \"token\" {\n\t\t\t\tauthCa := testca.New(base.T)\n\t\t\t\tas := testregistry.NewAuthServer(base, authCa, 0, username, password, false)\n\t\t\t\tauth = &testregistry.TokenAuth{\n\t\t\t\t\tAddress:  as.Scheme + \"://\" + net.JoinHostPort(as.IP.String(), strconv.Itoa(as.Port)),\n\t\t\t\t\tCertPath: as.CertPath,\n\t\t\t\t}\n\t\t\t\tdependentCleanup = as.Cleanup\n\t\t\t}\n\n\t\t\t// Start the registry with the requested options\n\t\t\treg := testregistry.NewRegistry(base, nil, 0, auth, dependentCleanup)\n\n\t\t\t// Register registry cleanup\n\t\t\tt.Cleanup(func() {\n\t\t\t\treg.Cleanup(nil)\n\t\t\t})\n\n\t\t\t// First, login successfully\n\t\t\tc := (&Client{}).\n\t\t\t\tWithCredentials(username, password)\n\n\t\t\tc.Run(base, fmt.Sprintf(\"localhost:%d\", reg.Port)).\n\t\t\t\tAssertOK()\n\n\t\t\t// Now, log in successfully without passing any explicit credentials\n\t\t\tnc := (&Client{}).\n\t\t\t\tWithConfigPath(c.GetConfigPath())\n\t\t\tnc.Run(base, fmt.Sprintf(\"localhost:%d\", reg.Port)).\n\t\t\t\tAssertOK()\n\n\t\t\t// Now fail while using invalid credentials\n\t\t\tnc.WithCredentials(\"invalid\", \"invalid\").\n\t\t\t\tRun(base, fmt.Sprintf(\"localhost:%d\", reg.Port)).\n\t\t\t\tAssertFail()\n\n\t\t\t// And login again without, reverting to the last saved good state\n\t\t\tnc = (&Client{}).\n\t\t\t\tWithConfigPath(c.GetConfigPath())\n\n\t\t\tnc.Run(base, fmt.Sprintf(\"localhost:%d\", reg.Port)).\n\t\t\t\tAssertOK()\n\t\t})\n\t}\n}\n\n/*\nfunc TestAgainstNoAuth(t *testing.T) {\n\tbase := testutil.NewBase(t)\n\tt.Parallel()\n\n\t// Start the registry with the requested options\n\treg := testregistry.NewRegistry(base, nil, 0, &testregistry.NoAuth{}, nil)\n\n\t// Register registry cleanup\n\tt.Cleanup(func() {\n\t\treg.Cleanup(nil)\n\t})\n\n\tc := (&Client{}).\n\t\tWithCredentials(\"invalid\", \"invalid\")\n\n\tc.Run(base, fmt.Sprintf(\"localhost:%d\", reg.Port)).\n\t\tAssertOK()\n\n\tcontent, _ := os.ReadFile(filepath.Join(c.configPath, \"config.json\"))\n\tfmt.Println(string(content))\n\n\tc.Run(base, fmt.Sprintf(\"localhost:%d\", reg.Port)).\n\t\tAssertFail()\n\n}\n\n*/\n\nfunc TestLoginAgainstVariants(t *testing.T) {\n\t// Skip docker, because Docker doesn't have `--hosts-dir` nor `insecure-registry` option\n\t// This will test access to a wide variety of servers, with or without TLS, with basic or token authentication\n\ttestutil.DockerIncompatible(t)\n\n\tbase := testutil.NewBase(t)\n\tt.Parallel()\n\n\ttestCases := []struct {\n\t\tport int\n\t\ttls  bool\n\t\tauth string\n\t}{\n\t\t// Basic auth, no TLS\n\t\t{\n\t\t\t80,\n\t\t\tfalse,\n\t\t\t\"basic\",\n\t\t},\n\t\t{\n\t\t\t443,\n\t\t\tfalse,\n\t\t\t\"basic\",\n\t\t},\n\t\t{\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t\"basic\",\n\t\t},\n\t\t// Token auth, no TLS\n\t\t{\n\t\t\t80,\n\t\t\tfalse,\n\t\t\t\"token\",\n\t\t},\n\t\t{\n\t\t\t443,\n\t\t\tfalse,\n\t\t\t\"token\",\n\t\t},\n\t\t{\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t\"token\",\n\t\t},\n\t\t// Basic auth, with TLS\n\t\t/*\n\t\t\t// This is not working currently, unless we would force a server https:// in hosts\n\t\t\t// To be fixed with login rewrite\n\t\t\t{\n\t\t\t\t80,\n\t\t\t\ttrue,\n\t\t\t\t\"basic\",\n\t\t\t},\n\t\t*/\n\t\t{\n\t\t\t443,\n\t\t\ttrue,\n\t\t\t\"basic\",\n\t\t},\n\t\t{\n\t\t\t0,\n\t\t\ttrue,\n\t\t\t\"basic\",\n\t\t},\n\t\t// Token auth, with TLS\n\t\t/*\n\t\t\t// This is not working currently, unless we would force a server https:// in hosts\n\t\t\t// To be fixed with login rewrite\n\t\t\t{\n\t\t\t\t80,\n\t\t\t\ttrue,\n\t\t\t\t\"token\",\n\t\t\t},\n\t\t*/\n\t\t{\n\t\t\t443,\n\t\t\ttrue,\n\t\t\t\"token\",\n\t\t},\n\t\t{\n\t\t\t0,\n\t\t\ttrue,\n\t\t\t\"token\",\n\t\t},\n\t}\n\n\t// Iterate through all cases, that will present a variety of port (80, 443, random), TLS (yes or no), and authentication (basic, token) type combinations\n\tfor _, tc := range testCases {\n\t\tport := tc.port\n\t\ttls := tc.tls\n\t\tauth := tc.auth\n\n\t\tt.Run(fmt.Sprintf(\"Login against `tls: %t port: %d auth: %s`\", tls, port, auth), func(t *testing.T) {\n\t\t\t// Tests with fixed ports should not be parallelized (although the port locking mechanism will prevent conflicts)\n\t\t\t// as their children tests are parallelized, and this might deadlock given the way `Parallel` works\n\t\t\tif port == 0 {\n\t\t\t\tt.Parallel()\n\t\t\t}\n\n\t\t\t// Generate credentials that are specific to each registry, so that we never cross hit another one\n\t\t\tusername := utils.RandomStringBase64(30) + \"∞\"\n\t\t\tpassword := utils.RandomStringBase64(30) + \":∞\"\n\n\t\t\t// Get a CA if we want TLS\n\t\t\tvar ca *testca.CA\n\t\t\tif tls {\n\t\t\t\tca = testca.New(base.T)\n\t\t\t}\n\n\t\t\t// Add the requested authenticator\n\t\t\tvar authenticator testregistry.Auth\n\t\t\tvar dependentCleanup func(error)\n\n\t\t\tauthenticator = &testregistry.NoAuth{}\n\t\t\tif auth == \"basic\" {\n\t\t\t\tauthenticator = &testregistry.BasicAuth{\n\t\t\t\t\tUsername: username,\n\t\t\t\t\tPassword: password,\n\t\t\t\t}\n\t\t\t} else if auth == \"token\" {\n\t\t\t\tauthCa := ca\n\t\t\t\t// We could be on !tls, meaning no ca - but we still need a CA to sign jwt tokens\n\t\t\t\tif authCa == nil {\n\t\t\t\t\tauthCa = testca.New(base.T)\n\t\t\t\t}\n\t\t\t\tas := testregistry.NewAuthServer(base, authCa, 0, username, password, tls)\n\t\t\t\tauthenticator = &testregistry.TokenAuth{\n\t\t\t\t\tAddress:  as.Scheme + \"://\" + net.JoinHostPort(as.IP.String(), strconv.Itoa(as.Port)),\n\t\t\t\t\tCertPath: as.CertPath,\n\t\t\t\t}\n\t\t\t\tdependentCleanup = as.Cleanup\n\t\t\t}\n\n\t\t\t// Start the registry with the requested options\n\t\t\treg := testregistry.NewRegistry(base, ca, port, authenticator, dependentCleanup)\n\n\t\t\t// Register registry cleanup\n\t\t\tt.Cleanup(func() {\n\t\t\t\treg.Cleanup(nil)\n\t\t\t})\n\n\t\t\t// Any registry is reachable through its ip+port, and localhost variants\n\t\t\tregHosts := []string{\n\t\t\t\tnet.JoinHostPort(reg.IP.String(), strconv.Itoa(reg.Port)),\n\t\t\t\tnet.JoinHostPort(\"localhost\", strconv.Itoa(reg.Port)),\n\t\t\t\tnet.JoinHostPort(\"127.0.0.1\", strconv.Itoa(reg.Port)),\n\t\t\t\t// TODO: ipv6\n\t\t\t\t// net.JoinHostPort(\"::1\", strconv.Itoa(reg.Port)),\n\t\t\t}\n\n\t\t\t// Registries that use port 443 also allow access without specifying a port\n\t\t\tif reg.Port == 443 {\n\t\t\t\tregHosts = append(regHosts, reg.IP.String())\n\t\t\t\tregHosts = append(regHosts, \"localhost\")\n\t\t\t\tregHosts = append(regHosts, \"127.0.0.1\")\n\t\t\t\t// TODO: ipv6\n\t\t\t\t// regHosts = append(regHosts, \"::1\")\n\t\t\t}\n\n\t\t\t// Iterate through these hosts access points, and create a test per-variant\n\t\t\tfor _, value := range regHosts {\n\t\t\t\tregHost := value\n\t\t\t\tt.Run(regHost, func(t *testing.T) {\n\t\t\t\t\tt.Parallel()\n\n\t\t\t\t\t// 1. test with valid credentials but no access to the CA\n\t\t\t\t\tt.Run(\"1. valid credentials (no CA) \", func(t *testing.T) {\n\t\t\t\t\t\tt.Parallel()\n\n\t\t\t\t\t\tc := (&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, password)\n\n\t\t\t\t\t\trl, _ := dockerconfigresolver.Parse(regHost)\n\t\t\t\t\t\t// a. Insecure flag not being set\n\t\t\t\t\t\t// TODO: remove specialization when we fix the localhost mess\n\t\t\t\t\t\tif rl.IsLocalhost() && !tls {\n\t\t\t\t\t\t\tc.Run(base, regHost).\n\t\t\t\t\t\t\t\tAssertOK()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tc.Run(base, regHost).\n\t\t\t\t\t\t\t\tAssertFail()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// b. Insecure flag set to false\n\t\t\t\t\t\t// TODO: remove specialization when we fix the localhost mess\n\t\t\t\t\t\tif !rl.IsLocalhost() {\n\t\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\t\tWithCredentials(username, password).\n\t\t\t\t\t\t\t\tWithInsecure(false).\n\t\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\t\tAssertFail()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// c. Insecure flag set to true\n\t\t\t\t\t\t// TODO: remove specialization when we fix the localhost mess\n\t\t\t\t\t\tif !rl.IsLocalhost() || !tls {\n\t\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\t\tWithCredentials(username, password).\n\t\t\t\t\t\t\t\tWithInsecure(true).\n\t\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\t\tAssertOK()\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\n\t\t\t\t\t// 2. test with valid credentials AND access to the CA\n\t\t\t\t\tt.Run(\"2. valid credentials (with access to server CA)\", func(t *testing.T) {\n\t\t\t\t\t\tt.Parallel()\n\n\t\t\t\t\t\trl, _ := dockerconfigresolver.Parse(regHost)\n\n\t\t\t\t\t\t// a. Insecure flag not being set\n\t\t\t\t\t\tc := (&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, password).\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir)\n\n\t\t\t\t\t\tif tls || rl.IsLocalhost() {\n\t\t\t\t\t\t\tc.Run(base, regHost).\n\t\t\t\t\t\t\t\tAssertOK()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tc.Run(base, regHost).\n\t\t\t\t\t\t\t\tAssertFail()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// b. Insecure flag set to false\n\t\t\t\t\t\tif tls {\n\t\t\t\t\t\t\tc.WithInsecure(false).\n\t\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\t\tAssertOK()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// TODO: remove specialization when we fix the localhost mess\n\t\t\t\t\t\t\tif !rl.IsLocalhost() {\n\t\t\t\t\t\t\t\tc.WithInsecure(false).\n\t\t\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\t\t\tAssertFail()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// c. Insecure flag set to true\n\t\t\t\t\t\tc.WithInsecure(true).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertOK()\n\t\t\t\t\t})\n\n\t\t\t\t\tt.Run(\"3. valid credentials, any url variant, should always succeed\", func(t *testing.T) {\n\t\t\t\t\t\tt.Parallel()\n\t\t\t\t\t\tc := (&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, password).\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\t// Just use insecure here for all servers - it does not matter for what we are testing here\n\t\t\t\t\t\t\t// in this case, which is whether we can successfully log in against any of these variants\n\t\t\t\t\t\t\tWithInsecure(true)\n\n\t\t\t\t\t\t// TODO: remove specialization when we fix the localhost mess\n\t\t\t\t\t\trl, _ := dockerconfigresolver.Parse(regHost)\n\t\t\t\t\t\tif !rl.IsLocalhost() || !tls {\n\t\t\t\t\t\t\tc.Run(base, \"http://\"+regHost).AssertOK()\n\t\t\t\t\t\t\tc.Run(base, \"https://\"+regHost).AssertOK()\n\t\t\t\t\t\t\tc.Run(base, \"http://\"+regHost+\"/whatever?foo=bar;foo:bar#foo=bar\").AssertOK()\n\t\t\t\t\t\t\tc.Run(base, \"https://\"+regHost+\"/whatever?foo=bar&bar=foo;foo=foo+bar:bar#foo=bar\").AssertOK()\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\n\t\t\t\t\tt.Run(\"4. wrong password should always fail\", func(t *testing.T) {\n\t\t\t\t\t\tt.Parallel()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, \"invalid\").\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, \"invalid\").\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\tWithInsecure(false).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, \"invalid\").\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\tWithInsecure(true).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, \"invalid\").\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, \"invalid\").\n\t\t\t\t\t\t\tWithInsecure(false).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(username, \"invalid\").\n\t\t\t\t\t\t\tWithInsecure(true).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\t\t\t\t\t})\n\n\t\t\t\t\tt.Run(\"5. wrong username should always fail\", func(t *testing.T) {\n\t\t\t\t\t\tt.Parallel()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(\"invalid\", password).\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(\"invalid\", password).\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\tWithInsecure(false).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(\"invalid\", password).\n\t\t\t\t\t\t\tWithHostsDir(reg.HostsDir).\n\t\t\t\t\t\t\tWithInsecure(true).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(\"invalid\", password).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(\"invalid\", password).\n\t\t\t\t\t\t\tWithInsecure(false).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\n\t\t\t\t\t\t(&Client{}).\n\t\t\t\t\t\t\tWithCredentials(\"invalid\", password).\n\t\t\t\t\t\t\tWithInsecure(true).\n\t\t\t\t\t\t\tRun(base, regHost).\n\t\t\t\t\t\t\tAssertFail()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/nerdctl/login/login_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/login/logout.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/logout\"\n)\n\nfunc LogoutCommand() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:               \"logout [flags] [SERVER]\",\n\t\tArgs:              cobra.MaximumNArgs(1),\n\t\tShort:             \"Log out from a container registry\",\n\t\tRunE:              logoutAction,\n\t\tValidArgsFunction: logoutShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n}\n\nfunc logoutAction(cmd *cobra.Command, args []string) error {\n\tlogoutServer := \"\"\n\tif len(args) > 0 {\n\t\tlogoutServer = args[0]\n\t}\n\n\terrGroup, err := logout.Logout(cmd.Context(), logoutServer)\n\tif err != nil {\n\t\tlog.L.WithError(err).Errorf(\"Failed to erase credentials for: %s\", logoutServer)\n\t}\n\tif errGroup != nil {\n\t\tlog.L.Error(\"None of the following entries could be found\")\n\t\tfor _, v := range errGroup {\n\t\t\tlog.L.Errorf(\"%s\", v)\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc logoutShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tcandidates, err := logout.ShellCompletion()\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\n\treturn candidates, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "cmd/nerdctl/main.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/pelletier/go-toml/v2\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/builder\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/checkpoint\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/compose\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/container\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/image\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/inspect\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/internal\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/ipfs\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/login\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/manifest\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/namespace\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/network\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/search\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/system\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/volume\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\tncdefaults \"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n\t\"github.com/containerd/nerdctl/v2/pkg/version\"\n)\n\nvar (\n\t// To print Bold Text\n\tBold = color.New(color.Bold).SprintfFunc()\n)\n\n// usage was derived from https://github.com/spf13/cobra/blob/v1.2.1/command.go#L491-L514\nfunc usage(c *cobra.Command) error {\n\ts := \"Usage: \"\n\tif c.HasSubCommands() {\n\t\ts += c.CommandPath() + \" [command]\\n\"\n\t} else if c.Runnable() {\n\t\ts += c.UseLine() + \"\\n\"\n\t} else {\n\t\ts += c.CommandPath() + \" [command]\\n\"\n\t}\n\ts += \"\\n\"\n\tif len(c.Aliases) > 0 {\n\t\ts += \"Aliases: \" + c.NameAndAliases() + \"\\n\"\n\t}\n\tif c.HasExample() {\n\t\ts += \"Example:\\n\"\n\t\ts += c.Example + \"\\n\"\n\t}\n\n\tvar managementCommands, nonManagementCommands []*cobra.Command\n\tfor _, f := range c.Commands() {\n\t\tf := f\n\t\tif f.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tif f.Annotations[helpers.Category] == helpers.Management {\n\t\t\tmanagementCommands = append(managementCommands, f)\n\t\t} else {\n\t\t\tnonManagementCommands = append(nonManagementCommands, f)\n\t\t}\n\t}\n\tprintCommands := func(title string, commands []*cobra.Command) string {\n\t\tif len(commands) == 0 {\n\t\t\treturn \"\"\n\t\t}\n\t\tvar longest int\n\t\tfor _, f := range commands {\n\t\t\tif l := len(f.Name()); l > longest {\n\t\t\t\tlongest = l\n\t\t\t}\n\t\t}\n\n\t\ttitle = Bold(title)\n\t\tt := title + \":\\n\"\n\t\tfor _, f := range commands {\n\t\t\tt += \"  \"\n\t\t\tt += f.Name()\n\t\t\tt += strings.Repeat(\" \", longest-len(f.Name()))\n\t\t\tt += \"  \" + f.Short + \"\\n\"\n\t\t}\n\t\tt += \"\\n\"\n\t\treturn t\n\t}\n\ts += printCommands(\"Management commands\", managementCommands)\n\ts += printCommands(\"Commands\", nonManagementCommands)\n\n\ts += Bold(\"Flags\") + \":\\n\"\n\ts += c.LocalFlags().FlagUsages() + \"\\n\"\n\n\tif c == c.Root() {\n\t\ts += \"Run '\" + c.CommandPath() + \" COMMAND --help' for more information on a command.\\n\"\n\t} else {\n\t\ts += \"See also '\" + c.Root().CommandPath() + \" --help' for the global flags such as '--namespace', '--snapshotter', and '--cgroup-manager'.\"\n\t}\n\tfmt.Fprintln(c.OutOrStdout(), s)\n\treturn nil\n}\n\nfunc main() {\n\tif err := xmain(); err != nil {\n\t\terrutil.HandleExitCoder(err)\n\t\tlog.L.Fatal(err)\n\t}\n}\n\nfunc xmain() error {\n\tif len(os.Args) == 3 && os.Args[1] == logging.MagicArgv1 {\n\t\t// containerd runtime v2 logging plugin mode.\n\t\t// \"binary://BIN?KEY=VALUE\" URI is parsed into Args {BIN, KEY, VALUE}.\n\t\treturn logging.Main(os.Args[2])\n\t}\n\t// nerdctl CLI mode\n\tapp, err := newApp()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn app.Execute()\n}\n\nfunc initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) (*pflag.FlagSet, error) {\n\tcfg := config.New()\n\tif r, err := os.Open(tomlPath); err == nil {\n\t\tlog.L.Debugf(\"Loading config from %q\", tomlPath)\n\t\tdefer r.Close()\n\t\tdec := toml.NewDecoder(r).DisallowUnknownFields() // set Strict to detect typo\n\t\tif err := dec.Decode(cfg); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to load nerdctl config (not daemon config) from %q (Hint: don't mix up daemon's `config.toml` with `nerdctl.toml`): %w\", tomlPath, err)\n\t\t}\n\t\tlog.L.Debugf(\"Loaded config %+v\", cfg)\n\t} else {\n\t\tlog.L.WithError(err).Debugf(\"Not loading config from %q\", tomlPath)\n\t\tif !errors.Is(err, os.ErrNotExist) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\taliasToBeInherited := pflag.NewFlagSet(rootCmd.Name(), pflag.ExitOnError)\n\n\trootCmd.PersistentFlags().Bool(\"debug\", cfg.Debug, \"debug mode\")\n\trootCmd.PersistentFlags().Bool(\"debug-full\", cfg.DebugFull, \"debug mode (with full output)\")\n\t// -a is aliases (conflicts with nerdctl images -a)\n\thelpers.AddPersistentStringFlag(rootCmd, \"address\", []string{\"a\", \"H\"}, nil, []string{\"host\"}, aliasToBeInherited, cfg.Address, \"CONTAINERD_ADDRESS\", `containerd address, optionally with \"unix://\" prefix`)\n\t// -n is aliases (conflicts with nerdctl logs -n)\n\thelpers.AddPersistentStringFlag(rootCmd, \"namespace\", []string{\"n\"}, nil, nil, aliasToBeInherited, cfg.Namespace, \"CONTAINERD_NAMESPACE\", `containerd namespace, such as \"moby\" for Docker, \"k8s.io\" for Kubernetes`)\n\trootCmd.RegisterFlagCompletionFunc(\"namespace\", completion.NamespaceNames)\n\thelpers.AddPersistentStringFlag(rootCmd, \"snapshotter\", nil, nil, []string{\"storage-driver\"}, aliasToBeInherited, cfg.Snapshotter, \"CONTAINERD_SNAPSHOTTER\", \"containerd snapshotter\")\n\trootCmd.RegisterFlagCompletionFunc(\"snapshotter\", completion.SnapshotterNames)\n\trootCmd.RegisterFlagCompletionFunc(\"storage-driver\", completion.SnapshotterNames)\n\thelpers.AddPersistentStringFlag(rootCmd, \"cni-path\", nil, nil, nil, aliasToBeInherited, cfg.CNIPath, \"CNI_PATH\", \"cni plugins binary directory\")\n\thelpers.AddPersistentStringFlag(rootCmd, \"cni-netconfpath\", nil, nil, nil, aliasToBeInherited, cfg.CNINetConfPath, \"NETCONFPATH\", \"cni config directory\")\n\trootCmd.PersistentFlags().String(\"data-root\", cfg.DataRoot, \"Root directory of persistent nerdctl state (managed by nerdctl, not by containerd)\")\n\trootCmd.PersistentFlags().String(\"cgroup-manager\", cfg.CgroupManager, `Cgroup manager to use (\"cgroupfs\"|\"systemd\")`)\n\trootCmd.RegisterFlagCompletionFunc(\"cgroup-manager\", completion.CgroupManagerNames)\n\trootCmd.PersistentFlags().Bool(\"insecure-registry\", cfg.InsecureRegistry, \"skips verifying HTTPS certs, and allows falling back to plain HTTP\")\n\t// hosts-dir is defined as StringSlice, not StringArray, to allow specifying \"--hosts-dir=/etc/containerd/certs.d,/etc/docker/certs.d\"\n\trootCmd.PersistentFlags().StringSlice(\"hosts-dir\", cfg.HostsDir, \"A directory that contains <HOST:PORT>/hosts.toml (containerd style) or <HOST:PORT>/{ca.cert, cert.pem, key.pem} (docker style)\")\n\t// Experimental enable experimental feature, see in https://github.com/containerd/nerdctl/blob/main/docs/experimental.md\n\thelpers.AddPersistentBoolFlag(rootCmd, \"experimental\", nil, nil, cfg.Experimental, \"NERDCTL_EXPERIMENTAL\", \"Control experimental: https://github.com/containerd/nerdctl/blob/main/docs/experimental.md\")\n\thelpers.AddPersistentStringFlag(rootCmd, \"host-gateway-ip\", nil, nil, nil, aliasToBeInherited, cfg.HostGatewayIP, \"NERDCTL_HOST_GATEWAY_IP\", \"IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host. It has no effect without setting --add-host\")\n\thelpers.AddPersistentStringFlag(rootCmd, \"bridge-ip\", nil, nil, nil, aliasToBeInherited, cfg.BridgeIP, \"NERDCTL_BRIDGE_IP\", \"IP address for the default nerdctl bridge network\")\n\trootCmd.PersistentFlags().Bool(\"kube-hide-dupe\", cfg.KubeHideDupe, \"Deduplicate images for Kubernetes with namespace k8s.io\")\n\trootCmd.PersistentFlags().StringSlice(\"cdi-spec-dirs\", cfg.CDISpecDirs, \"The directories to search for CDI spec files. Defaults to /etc/cdi,/var/run/cdi\")\n\trootCmd.PersistentFlags().String(\"userns-remap\", cfg.UsernsRemap, \"Support idmapping for creating and running containers. This options is only supported on linux. If `host` is passed, no idmapping is done. if a user name is passed, it does idmapping based on the uidmap and gidmap ranges specified in /etc/subuid and /etc/subgid respectively\")\n\thelpers.HiddenPersistentStringArrayFlag(rootCmd, \"global-dns\", cfg.DNS, \"Global DNS servers for containers\")\n\thelpers.HiddenPersistentStringArrayFlag(rootCmd, \"global-dns-opts\", cfg.DNSOpts, \"Global DNS options for containers\")\n\thelpers.HiddenPersistentStringArrayFlag(rootCmd, \"global-dns-search\", cfg.DNSSearch, \"Global DNS search domains for containers\")\n\treturn aliasToBeInherited, nil\n}\n\nfunc newApp() (*cobra.Command, error) {\n\ttomlPath := ncdefaults.NerdctlTOML()\n\tif v, ok := os.LookupEnv(\"NERDCTL_TOML\"); ok {\n\t\ttomlPath = v\n\t}\n\n\tshort := \"nerdctl is a command line interface for containerd\"\n\tlong := fmt.Sprintf(`%s\n\nConfig file ($NERDCTL_TOML): %s\n`, short, tomlPath)\n\tvar rootCmd = &cobra.Command{\n\t\tUse:              \"nerdctl\",\n\t\tShort:            short,\n\t\tLong:             long,\n\t\tVersion:          strings.TrimPrefix(version.GetVersion(), \"v\"),\n\t\tSilenceUsage:     true,\n\t\tSilenceErrors:    true,\n\t\tTraverseChildren: true, // required for global short hands like -a, -H, -n\n\t}\n\n\trootCmd.SetUsageFunc(usage)\n\taliasToBeInherited, err := initRootCmdFlags(rootCmd, tomlPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := resetSavedSETUID(); err != nil {\n\t\treturn nil, err\n\t}\n\n\trootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {\n\t\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdebug := globalOptions.DebugFull\n\t\tif !debug {\n\t\t\tdebug = globalOptions.Debug\n\t\t}\n\t\tif debug {\n\t\t\tlog.SetLevel(log.DebugLevel.String())\n\t\t}\n\t\taddress := globalOptions.Address\n\t\tif strings.Contains(address, \"://\") && !strings.HasPrefix(address, \"unix://\") {\n\t\t\treturn fmt.Errorf(\"invalid address %q\", address)\n\t\t}\n\t\tcgroupManager := globalOptions.CgroupManager\n\t\tif runtime.GOOS == \"linux\" {\n\t\t\tswitch cgroupManager {\n\t\t\tcase \"systemd\", \"cgroupfs\", \"none\":\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"invalid cgroup-manager %q (supported values: \\\"systemd\\\", \\\"cgroupfs\\\", \\\"none\\\")\", cgroupManager)\n\t\t\t}\n\t\t}\n\n\t\t// Since we store containers' stateful information on the filesystem per namespace, we need namespaces to be\n\t\t// valid, safe path segments.\n\t\t// Note that the container runtime will further enforce additional restrictions on namespace names\n\t\t// (containerd treats namespaces as valid identifiers - eg: alphanumericals + dash, starting with a letter)\n\t\t// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#path-segment-names for\n\t\t// considerations about path segments identifiers.\n\t\tif err = store.IsFilesystemSafe(globalOptions.Namespace); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif appNeedsRootlessParentMain(cmd, args) {\n\t\t\t// reexec /proc/self/exe with `nsenter` into RootlessKit namespaces\n\t\t\treturn rootlessutil.ParentMain(globalOptions.HostGatewayIP)\n\t\t}\n\t\treturn nil\n\t}\n\trootCmd.RunE = helpers.UnknownSubcommandAction\n\trootCmd.AddCommand(\n\t\tcontainer.CreateCommand(),\n\t\t// #region Run & Exec\n\t\tcontainer.RunCommand(),\n\t\tcontainer.UpdateCommand(),\n\t\tcontainer.ExecCommand(),\n\t\t// #endregion\n\n\t\t// #region Container management\n\t\tcontainer.PsCommand(),\n\t\tcontainer.LogsCommand(),\n\t\tcontainer.PortCommand(),\n\t\tcontainer.StopCommand(),\n\t\tcontainer.StartCommand(),\n\t\tcontainer.DiffCommand(),\n\t\tcontainer.RestartCommand(),\n\t\tcontainer.KillCommand(),\n\t\tcontainer.RemoveCommand(),\n\t\tcontainer.PauseCommand(),\n\t\tcontainer.UnpauseCommand(),\n\t\tcontainer.CommitCommand(),\n\t\tcontainer.ExportCommand(),\n\t\tcontainer.WaitCommand(),\n\t\tcontainer.RenameCommand(),\n\t\tcontainer.AttachCommand(),\n\t\tcontainer.HealthCheckCommand(),\n\t\t// #endregion\n\n\t\t// Build\n\t\tbuilder.BuildCommand(),\n\n\t\t// #region Image management\n\t\timage.ImagesCommand(),\n\t\timage.PullCommand(),\n\t\timage.PushCommand(),\n\t\timage.LoadCommand(),\n\t\timage.SaveCommand(),\n\t\timage.ImportCommand(),\n\t\timage.TagCommand(),\n\t\timage.RmiCommand(),\n\t\timage.HistoryCommand(),\n\t\tsearch.Command(),\n\t\t// #endregion\n\n\t\t// #region System\n\t\tsystem.EventsCommand(),\n\t\tsystem.InfoCommand(),\n\t\tversionCommand(),\n\t\t// #endregion\n\n\t\t// Inspect\n\t\tinspect.Command(),\n\n\t\t// stats\n\t\tcontainer.TopCommand(),\n\t\tcontainer.StatsCommand(),\n\n\t\t// #region helpers.Management\n\t\tcontainer.Command(),\n\t\timage.Command(),\n\t\tnetwork.Command(),\n\t\tvolume.Command(),\n\t\tsystem.Command(),\n\t\tnamespace.Command(),\n\t\tbuilder.Command(),\n\t\t// #endregion\n\n\t\t// Internal\n\t\tinternal.Command(),\n\n\t\t// login\n\t\tlogin.Command(),\n\n\t\t// Logout\n\t\tlogin.LogoutCommand(),\n\n\t\t// Compose\n\t\tcompose.Command(),\n\n\t\t// IPFS\n\t\tipfs.NewIPFSCommand(),\n\n\t\t// Manifest\n\t\tmanifest.Command(),\n\n\t\t// Checkpoint\n\t\tcheckpoint.Command(),\n\t)\n\taddApparmorCommand(rootCmd)\n\tcontainer.AddCpCommand(rootCmd)\n\n\t// add aliasToBeInherited to subCommand(s) InheritedFlags\n\tfor _, subCmd := range rootCmd.Commands() {\n\t\tsubCmd.InheritedFlags().AddFlagSet(aliasToBeInherited)\n\t}\n\treturn rootCmd, nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/main_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/apparmor\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc appNeedsRootlessParentMain(cmd *cobra.Command, args []string) bool {\n\tcommands := []string{}\n\tfor tcmd := cmd; tcmd != nil; tcmd = tcmd.Parent() {\n\t\tcommands = append(commands, tcmd.Name())\n\t}\n\tcommands = strutil.ReverseStrSlice(commands)\n\n\tif !rootlessutil.IsRootlessParent() {\n\t\treturn false\n\t}\n\tif len(commands) < 2 {\n\t\treturn true\n\t}\n\tswitch commands[1] {\n\t// completion, login, logout, version: false, because it shouldn't require the daemon to be running\n\t// apparmor: false, because it requires the initial mount namespace to access /sys/kernel/security\n\t// cp, compose cp: false, because it requires the initial mount namespace to inspect file owners\n\tcase \"\", \"completion\", \"login\", \"logout\", \"apparmor\", \"cp\", \"version\":\n\t\treturn false\n\tcase \"container\":\n\t\tif len(commands) < 3 {\n\t\t\treturn true\n\t\t}\n\t\tswitch commands[2] {\n\t\tcase \"cp\":\n\t\t\treturn false\n\t\t}\n\tcase \"compose\":\n\t\tif len(commands) < 3 {\n\t\t\treturn true\n\t\t}\n\t\tswitch commands[2] {\n\t\tcase \"cp\":\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc addApparmorCommand(rootCmd *cobra.Command) {\n\trootCmd.AddCommand(apparmor.Command())\n}\n\n// resetSavedSETUID drops the saved UID of a setuid-root process to the original real UID.\n// This ensures the process cannot regain root privileges later.\n// It only performs the operation if the process is currently running with effective UID 0 (root)\n// and was started by a non-root user (i.e., real UID != effective UID).\n// For more info see issue https://github.com/containerd/nerdctl/issues/4098\nfunc resetSavedSETUID() error {\n\tvar err error\n\tuid := unix.Getuid()\n\teuid := unix.Geteuid()\n\tif uid != euid && euid == 0 {\n\t\terr = unix.Setresuid(0, 0, uid)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "cmd/nerdctl/main_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nfunc appNeedsRootlessParentMain(cmd *cobra.Command, args []string) bool {\n\treturn false\n}\n\nfunc addApparmorCommand(rootCmd *cobra.Command) {\n\t// NOP\n}\n\nfunc resetSavedSETUID() error {\n\t// NOP\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nerdctl/main_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n\n// TestUnknownCommand tests https://github.com/containerd/nerdctl/issues/487\nfunc TestUnknownCommand(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tvar cmd = errors.New(\"unknown subcommand\")\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"non-existent-command\",\n\t\t\tCommand:     test.Command(\"non-existent-command\"),\n\t\t\tExpected:    test.Expects(1, []error{cmd}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"non-existent-command info\",\n\t\t\tCommand:     test.Command(\"non-existent-command\", \"info\"),\n\t\t\tExpected:    test.Expects(1, []error{cmd}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"system non-existent-command\",\n\t\t\tCommand:     test.Command(\"system\", \"non-existent-command\"),\n\t\t\tExpected:    test.Expects(1, []error{cmd}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"system non-existent-command info\",\n\t\t\tCommand:     test.Command(\"system\", \"non-existent-command\", \"info\"),\n\t\t\tExpected:    test.Expects(1, []error{cmd}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"system\",\n\t\t\tCommand:     test.Command(\"system\"),\n\t\t\tExpected:    test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"system info\",\n\t\t\tCommand:     test.Command(\"system\", \"info\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"Kernel Version:\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"info\",\n\t\t\tCommand:     test.Command(\"info\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"Kernel Version:\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\n// TestNerdctlConfig validates the configuration precedence [CLI, Env, TOML, Default] and broken config rejection\nfunc TestNerdctlConfig(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Docker does not support nerdctl.toml obviously\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Default\",\n\t\t\tCommand:     test.Command(\"info\", \"-f\", \"{{.Driver}}\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Equals(defaults.DefaultSnapshotter+\"\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"TOML > Default\",\n\t\t\tCommand:     test.Command(\"info\", \"-f\", \"{{.Driver}}\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Equals(\"dummy-snapshotter-via-toml\\n\")),\n\t\t\tConfig:      test.WithConfig(nerdtest.NerdctlToml, `snapshotter = \"dummy-snapshotter-via-toml\"`),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Cli > TOML > Default\",\n\t\t\tCommand:     test.Command(\"info\", \"-f\", \"{{.Driver}}\", \"--snapshotter=dummy-snapshotter-via-cli\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Equals(\"dummy-snapshotter-via-cli\\n\")),\n\t\t\tConfig:      test.WithConfig(nerdtest.NerdctlToml, `snapshotter = \"dummy-snapshotter-via-toml\"`),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Env > TOML > Default\",\n\t\t\tCommand:     test.Command(\"info\", \"-f\", \"{{.Driver}}\"),\n\t\t\tEnv:         map[string]string{\"CONTAINERD_SNAPSHOTTER\": \"dummy-snapshotter-via-env\"},\n\t\t\tExpected:    test.Expects(0, nil, expect.Equals(\"dummy-snapshotter-via-env\\n\")),\n\t\t\tConfig:      test.WithConfig(nerdtest.NerdctlToml, `snapshotter = \"dummy-snapshotter-via-toml\"`),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Cli > Env > TOML > Default\",\n\t\t\tCommand:     test.Command(\"info\", \"-f\", \"{{.Driver}}\", \"--snapshotter=dummy-snapshotter-via-cli\"),\n\t\t\tEnv:         map[string]string{\"CONTAINERD_SNAPSHOTTER\": \"dummy-snapshotter-via-env\"},\n\t\t\tExpected:    test.Expects(0, nil, expect.Equals(\"dummy-snapshotter-via-cli\\n\")),\n\t\t\tConfig:      test.WithConfig(nerdtest.NerdctlToml, `snapshotter = \"dummy-snapshotter-via-toml\"`),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Broken config\",\n\t\t\tCommand:     test.Command(\"info\"),\n\t\t\tExpected:    test.Expects(1, []error{errors.New(\"failed to load nerdctl config\")}, nil),\n\t\t\tConfig: test.WithConfig(nerdtest.NerdctlToml, `# containerd config, not nerdctl config\nversion = 2`),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/main_test_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// TestTest is testing the test tooling itself\nfunc TestTest(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"failure\",\n\t\t\tCommand:     test.Command(\"undefinedcommand\"),\n\t\t\tExpected:    test.Expects(1, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"success\",\n\t\t\tCommand:     test.Command(\"info\"),\n\t\t\tExpected:    test.Expects(0, nil, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"failure with single error testing\",\n\t\t\tCommand:     test.Command(\"undefinedcommand\"),\n\t\t\tExpected:    test.Expects(1, []error{errors.New(\"unknown subcommand\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"success with contains output testing\",\n\t\t\tCommand:     test.Command(\"info\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Contains(\"Kernel\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"success with negative output testing\",\n\t\t\tCommand:     test.Command(\"info\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.DoesNotContain(\"foobar\")),\n\t\t},\n\t\t// Note that docker annoyingly returns 125 in a few conditions like this\n\t\t{\n\t\t\tDescription: \"failure with multiple error testing\",\n\t\t\tCommand:     test.Command(\"-fail\"),\n\t\t\tExpected:    test.Expects(expect.ExitCodeGenericFail, []error{errors.New(\"unknown\"), errors.New(\"shorthand\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"success with exact output testing\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Custom(\"echo\", \"foobar\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"foobar\\n\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"data propagation\",\n\t\t\tData:        test.WithLabels(map[string]string{\"status\": \"uninitialized\"}),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tdata.Labels().Set(\"status\", data.Labels().Get(\"status\")+\"-setup\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tcmd := helpers.Custom(\"printf\", data.Labels().Get(\"status\"))\n\t\t\t\tdata.Labels().Set(\"status\", data.Labels().Get(\"status\")+\"-command\")\n\t\t\t\treturn cmd\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tif data.Labels().Get(\"status\") == \"uninitialized\" {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif data.Labels().Get(\"status\") != \"uninitialized-setup-command\" {\n\t\t\t\t\tlog.Fatalf(\"unexpected status label %q\", data.Labels().Get(\"status\"))\n\t\t\t\t}\n\t\t\t\tdata.Labels().Set(\"status\", data.Labels().Get(\"status\")+\"-cleanup\")\n\t\t\t},\n\t\t\tSubTests: []*test.Case{\n\t\t\t\t{\n\t\t\t\t\tDescription: \"Subtest data propagation\",\n\t\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\t\treturn helpers.Custom(\"printf\", data.Labels().Get(\"status\"))\n\t\t\t\t\t},\n\t\t\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"uninitialized-setup-command\")),\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Equals(\"uninitialized-setup\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"manifest\",\n\t\tShort:         \"Manage image manifests.\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.AddCommand(\n\t\tinspectCommand(),\n\t\tcreateCommand(),\n\t\tannotateCommand(),\n\t\tremoveCommand(),\n\t\tpushCommand(),\n\t)\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_annotate.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/manifest\"\n)\n\nfunc annotateCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"annotate INDEX/MANIFESTLIST MANIFEST\",\n\t\tShort:             \"Add additional information to a local image manifest\",\n\t\tArgs:              cobra.ExactArgs(2),\n\t\tRunE:              annotateAction,\n\t\tValidArgsFunction: annotateShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"os\", \"\", \"Set operating system\")\n\tcmd.Flags().String(\"arch\", \"\", \"Set architecture\")\n\tcmd.Flags().String(\"os-version\", \"\", \"Set operating system version\")\n\tcmd.Flags().String(\"variant\", \"\", \"Set operating system feature\")\n\tcmd.Flags().StringArray(\"os-features\", []string{}, \"Set architecture variant\")\n\treturn cmd\n}\n\nfunc processAnnotateFlags(cmd *cobra.Command) (types.ManifestAnnotateOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ManifestAnnotateOptions{}, err\n\t}\n\n\tos, err := cmd.Flags().GetString(\"os\")\n\tif err != nil {\n\t\treturn types.ManifestAnnotateOptions{}, err\n\t}\n\tarch, err := cmd.Flags().GetString(\"arch\")\n\tif err != nil {\n\t\treturn types.ManifestAnnotateOptions{}, err\n\t}\n\tosVersion, err := cmd.Flags().GetString(\"os-version\")\n\tif err != nil {\n\t\treturn types.ManifestAnnotateOptions{}, err\n\t}\n\tvariant, err := cmd.Flags().GetString(\"variant\")\n\tif err != nil {\n\t\treturn types.ManifestAnnotateOptions{}, err\n\t}\n\tosFeatures, err := cmd.Flags().GetStringArray(\"os-features\")\n\tif err != nil {\n\t\treturn types.ManifestAnnotateOptions{}, err\n\t}\n\n\treturn types.ManifestAnnotateOptions{\n\t\tStdout:     cmd.OutOrStdout(),\n\t\tGOptions:   globalOptions,\n\t\tOs:         os,\n\t\tArch:       arch,\n\t\tOsVersion:  osVersion,\n\t\tVariant:    variant,\n\t\tOsFeatures: osFeatures,\n\t}, nil\n}\n\nfunc annotateAction(cmd *cobra.Command, args []string) error {\n\tannotateOptions, err := processAnnotateFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlistRef := args[0]\n\tmanifestRef := args[1]\n\n\treturn manifest.Annotate(cmd.Context(), listRef, manifestRef, annotateOptions)\n}\n\nfunc annotateShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_annotate_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestManifestAnnotateErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\tmanifestListName := \"test-list:v1\"\n\tmanifestName := \"example.com/alpine:latest\"\n\tinvalidName := \"invalid/name/with/special@chars\"\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"too-few-arguments\",\n\t\t\tCommand:     test.Command(\"manifest\", \"annotate\", manifestListName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-list-name\",\n\t\t\tCommand:     test.Command(\"manifest\", \"annotate\", invalidName, manifestName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"invalid reference format\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-manifest-reference\",\n\t\t\tCommand:     test.Command(\"manifest\", \"annotate\", manifestListName, invalidName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"invalid reference format\",\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestManifestAnnotate(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\tmanifestListName := \"example.com/test-list-annotate:v1\"\n\tmanifestRef := testutil.GetTestImageWithoutTag(\"alpine\") + \"@\" + testutil.GetTestImageManifestDigest(\"alpine\", \"linux/amd64\")\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"annotate-non-existent-manifest\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"manifest\", \"create\", manifestListName, manifestRef)\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: test.Command(\"manifest\", \"annotate\", manifestListName, \"example.com/fake:0.0\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"manifest for image example.com/fake:0.0 does not exist\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"annotate-success\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"manifest\", \"create\", manifestListName+\"-success\", manifestRef)\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: test.Command(\"manifest\", \"annotate\",\n\t\t\t\tmanifestListName+\"-success\",\n\t\t\t\tmanifestRef,\n\t\t\t\t\"--os\", \"freebsd\",\n\t\t\t\t\"--arch\", \"arm\",\n\t\t\t\t\"--os-version\", \"1\",\n\t\t\t\t\"--os-features\", \"feature1\",\n\t\t\t\t\"--variant\", \"v7\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/manifest\"\n)\n\nfunc createCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"create INDEX/MANIFESTLIST MANIFEST [MANIFEST...]\",\n\t\tShort:             \"Create a local index/manifest list for annotating and pushing to a registry\",\n\t\tArgs:              cobra.MinimumNArgs(2),\n\t\tRunE:              createAction,\n\t\tValidArgsFunction: createShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().Bool(\"amend\", false, \"Amend the existing index/manifest list\")\n\tcmd.Flags().Bool(\"insecure\", false, \"Allow communication with an insecure registry\")\n\treturn cmd\n}\n\nfunc processCreateFlags(cmd *cobra.Command) (types.ManifestCreateOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ManifestCreateOptions{}, err\n\t}\n\tamend, err := cmd.Flags().GetBool(\"amend\")\n\tif err != nil {\n\t\treturn types.ManifestCreateOptions{}, err\n\t}\n\tinsecure, err := cmd.Flags().GetBool(\"insecure\")\n\tif err != nil {\n\t\treturn types.ManifestCreateOptions{}, err\n\t}\n\treturn types.ManifestCreateOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tAmend:    amend,\n\t\tInsecure: insecure,\n\t}, nil\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\tcreateOptions, err := processCreateFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlistRef := args[0]\n\tmanifestRefs := args[1:]\n\n\tlistRef, err = manifest.Create(cmd.Context(), listRef, manifestRefs, createOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfmt.Fprintln(createOptions.Stdout, \"Created manifest list\", listRef)\n\n\treturn nil\n}\n\nfunc createShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_create_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestManifestCreateErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\tmanifestListName := \"test-list:v1\"\n\tmanifestName := \"example.com/alpine:latest\"\n\tinvalidName := \"invalid/name/with/special@chars\"\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"too-few-arguments\",\n\t\t\tCommand:     test.Command(\"manifest\", \"create\", manifestListName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"requires at least 2 arg\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-list-name\",\n\t\t\tCommand:     test.Command(\"manifest\", \"create\", invalidName, manifestName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"invalid reference format\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-manifest-reference\",\n\t\t\tCommand:     test.Command(\"manifest\", \"create\", manifestListName, invalidName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"invalid reference format\",\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestManifestCreate(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\tmanifestListName := \"test-list-create:v1\"\n\tmanifestRef := testutil.GetTestImageWithoutTag(\"alpine\") + \"@\" + testutil.GetTestImageManifestDigest(\"alpine\", \"linux/amd64\")\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"create-manifest-list\",\n\t\t\tCommand:     test.Command(\"manifest\", \"create\", manifestListName, manifestRef),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"output\": \"Created manifest list docker.io/library/\" + manifestListName,\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"create-existed-manifest-list-without-amend-flag\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"manifest\", \"create\", manifestListName+\"-without-amend-flag\", manifestRef)\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: test.Command(\"manifest\", \"create\", manifestListName+\"-without-amend-flag\", manifestRef),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"refusing to amend an existing manifest list with no --amend flag\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"create-manifest-list-with-amend-flag\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"manifest\", \"create\", manifestListName+\"-with-amend-flag\", manifestRef)\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: test.Command(\"manifest\", \"create\", \"--amend\", manifestListName+\"-with-amend-flag\", manifestRef),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"output\": \"Created manifest list docker.io/library/\" + manifestListName + \"-with-amend-flag\",\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/manifest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"inspect MANIFEST\",\n\t\tShort:             \"Display the contents of a manifest or image index/manifest list\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              inspectAction,\n\t\tValidArgsFunction: inspectShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().Bool(\"verbose\", false, \"Verbose output additional info including layers and platform\")\n\tcmd.Flags().Bool(\"insecure\", false, \"Allow communication with an insecure registry\")\n\treturn cmd\n}\n\nfunc processInspectFlags(cmd *cobra.Command) (types.ManifestInspectOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ManifestInspectOptions{}, err\n\t}\n\tverbose, err := cmd.Flags().GetBool(\"verbose\")\n\tif err != nil {\n\t\treturn types.ManifestInspectOptions{}, err\n\t}\n\tinsecure, err := cmd.Flags().GetBool(\"insecure\")\n\tif err != nil {\n\t\treturn types.ManifestInspectOptions{}, err\n\t}\n\treturn types.ManifestInspectOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tVerbose:  verbose,\n\t\tInsecure: insecure,\n\t}, nil\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\tinspectOptions, err := processInspectFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\trawRef := args[0]\n\tres, err := manifest.Inspect(cmd.Context(), rawRef, inspectOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Output format: single object for single result, array for multiple results\n\tif len(res) == 1 {\n\t\tjsonStr, err := formatter.ToJSON(res[0], \"\", \"    \")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprint(inspectOptions.Stdout, jsonStr)\n\t} else {\n\t\tif formatErr := formatter.FormatSlice(\"\", inspectOptions.Stdout, res); formatErr != nil {\n\t\t\treturn formatErr\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc inspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_inspect_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/manifesttypes\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nconst (\n\ttestImageName = \"alpine\"\n\ttestPlatform  = \"linux/amd64\"\n)\n\ntype testData struct {\n\timageName      string\n\tplatform       string\n\timageRef       string\n\tmanifestDigest string\n\tconfigDigest   string\n\trawData        string\n}\n\nfunc newTestData(imageName, platform string) *testData {\n\treturn &testData{\n\t\timageName:      imageName,\n\t\tplatform:       platform,\n\t\timageRef:       testutil.GetTestImage(imageName),\n\t\tmanifestDigest: testutil.GetTestImageManifestDigest(imageName, platform),\n\t\tconfigDigest:   testutil.GetTestImageConfigDigest(imageName, platform),\n\t\trawData:        testutil.GetTestImageRaw(imageName, platform),\n\t}\n}\n\nfunc (td *testData) imageWithDigest() string {\n\treturn testutil.GetTestImageWithoutTag(td.imageName) + \"@\" + td.manifestDigest\n}\n\nfunc (td *testData) isAmd64Platform(platform *ocispec.Platform) bool {\n\treturn platform != nil &&\n\t\tplatform.Architecture == \"amd64\" &&\n\t\tplatform.OS == \"linux\"\n}\n\nfunc TestManifestInspect(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\ttd := newTestData(testImageName, testPlatform)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"tag-non-verbose\",\n\t\t\tCommand:     test.Command(\"manifest\", \"inspect\", td.imageRef),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar manifest manifesttypes.DockerManifestListStruct\n\t\t\t\tassert.NilError(t, json.Unmarshal([]byte(stdout), &manifest))\n\n\t\t\t\tassert.Equal(t, manifest.SchemaVersion, testutil.GetTestImageSchemaVersion(td.imageName))\n\t\t\t\tassert.Equal(t, manifest.MediaType, testutil.GetTestImageMediaType(td.imageName))\n\t\t\t\tassert.Assert(t, len(manifest.Manifests) > 0)\n\n\t\t\t\tvar foundManifest *ocispec.Descriptor\n\t\t\t\tfor _, m := range manifest.Manifests {\n\t\t\t\t\tif td.isAmd64Platform(m.Platform) {\n\t\t\t\t\t\tfoundManifest = &m\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.Assert(t, foundManifest != nil, \"should find amd64 platform manifest\")\n\t\t\t\tassert.Equal(t, foundManifest.Digest.String(), td.manifestDigest)\n\t\t\t\tassert.Equal(t, foundManifest.MediaType, testutil.GetTestImagePlatformMediaType(td.imageName, td.platform))\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"tag-verbose\",\n\t\t\tCommand:     test.Command(\"manifest\", \"inspect\", td.imageRef, \"--verbose\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar entries []manifesttypes.DockerManifestEntry\n\t\t\t\tassert.NilError(t, json.Unmarshal([]byte(stdout), &entries))\n\t\t\t\tassert.Assert(t, len(entries) > 0)\n\n\t\t\t\tvar foundEntry *manifesttypes.DockerManifestEntry\n\t\t\t\tfor _, e := range entries {\n\t\t\t\t\tif td.isAmd64Platform(e.Descriptor.Platform) {\n\t\t\t\t\t\tfoundEntry = &e\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.Assert(t, foundEntry != nil, \"should find amd64 platform entry\")\n\n\t\t\t\texpectedRef := td.imageRef + \"@\" + td.manifestDigest\n\t\t\t\tassert.Equal(t, foundEntry.Ref, expectedRef)\n\t\t\t\tassert.Equal(t, foundEntry.Descriptor.Digest.String(), td.manifestDigest)\n\t\t\t\tassert.Equal(t, foundEntry.Descriptor.MediaType, testutil.GetTestImagePlatformMediaType(td.imageName, td.platform))\n\t\t\t\tassert.Equal(t, foundEntry.Raw, td.rawData)\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"digest-non-verbose\",\n\t\t\tCommand:     test.Command(\"manifest\", \"inspect\", td.imageWithDigest()),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar manifest manifesttypes.DockerManifestStruct\n\t\t\t\tassert.NilError(t, json.Unmarshal([]byte(stdout), &manifest))\n\n\t\t\t\tassert.Equal(t, manifest.SchemaVersion, testutil.GetTestImageSchemaVersion(td.imageName))\n\t\t\t\tassert.Equal(t, manifest.MediaType, testutil.GetTestImagePlatformMediaType(td.imageName, td.platform))\n\t\t\t\tassert.Equal(t, manifest.Config.Digest.String(), td.configDigest)\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"digest-verbose\",\n\t\t\tCommand:     test.Command(\"manifest\", \"inspect\", td.imageWithDigest(), \"--verbose\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar entry manifesttypes.DockerManifestEntry\n\t\t\t\tassert.NilError(t, json.Unmarshal([]byte(stdout), &entry))\n\n\t\t\t\tassert.Equal(t, entry.Ref, td.imageWithDigest())\n\t\t\t\tassert.Equal(t, entry.Descriptor.Digest.String(), td.manifestDigest)\n\t\t\t\tassert.Equal(t, entry.Descriptor.MediaType, testutil.GetTestImagePlatformMediaType(td.imageName, td.platform))\n\t\t\t\tassert.Equal(t, entry.Raw, td.rawData)\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/manifest\"\n)\n\nfunc pushCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"push [OPTIONS] INDEX/MANIFESTLIST\",\n\t\tShort:             \"Push a manifest list to a registry\",\n\t\tArgs:              cobra.ExactArgs(1),\n\t\tRunE:              pushAction,\n\t\tValidArgsFunction: pushShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().Bool(\"insecure\", false, \"Allow communication with an insecure registry\")\n\tcmd.Flags().Bool(\"purge\", false, \"Remove the manifest list after pushing\")\n\treturn cmd\n}\n\nfunc processPushFlags(cmd *cobra.Command) (types.ManifestPushOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.ManifestPushOptions{}, err\n\t}\n\n\tinsecure, err := cmd.Flags().GetBool(\"insecure\")\n\tif err != nil {\n\t\treturn types.ManifestPushOptions{}, err\n\t}\n\tpurge, err := cmd.Flags().GetBool(\"purge\")\n\tif err != nil {\n\t\treturn types.ManifestPushOptions{}, err\n\t}\n\n\treturn types.ManifestPushOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tInsecure: insecure,\n\t\tPurge:    purge,\n\t}, nil\n}\n\nfunc pushAction(cmd *cobra.Command, args []string) error {\n\tpushOptions, err := processPushFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = manifest.Push(cmd.Context(), args[0], pushOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc pushShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_push_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc TestManifestPushErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\tinvalidName := \"invalid/name/with/special@chars\"\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"require-one-argument\",\n\t\t\tCommand:     test.Command(\"manifest\", \"push\", \"arg1\", \"arg2\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-list-name\",\n\t\t\tCommand:     test.Command(\"manifest\", \"push\", invalidName),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"invalid reference format\",\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestManifestPush(t *testing.T) {\n\tnerdtest.Setup()\n\n\tvar registryTokenAuthHTTPSRandom *registry.Server\n\tvar tokenServer *registry.TokenAuthServer\n\n\tmanifestRef := testutil.GetTestImageWithoutTag(\"alpine\") + \"@\" + testutil.GetTestImageManifestDigest(\"alpine\", \"linux/amd64\")\n\texpectedDigest := \"sha256:5317ce2da263afa23570c692d62c1b01381285b2198b3ea9739ce64bec22aff2\"\n\n\ttestCase := &test.Case{\n\t\tRequire: require.All(\n\t\t\trequire.Linux,\n\t\t\tnerdtest.Registry,\n\t\t),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tregistryTokenAuthHTTPSRandom, tokenServer = nerdtest.RegistryWithTokenAuth(data, helpers, \"admin\", \"badmin\", 0, true)\n\t\t\ttokenServer.Setup(data, helpers)\n\t\t\tregistryTokenAuthHTTPSRandom.Setup(data, helpers)\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tif registryTokenAuthHTTPSRandom != nil {\n\t\t\t\tregistryTokenAuthHTTPSRandom.Cleanup(data, helpers)\n\t\t\t}\n\t\t\tif tokenServer != nil {\n\t\t\t\ttokenServer.Cleanup(data, helpers)\n\t\t\t}\n\t\t},\n\t\tSubTests: []*test.Case{\n\t\t\t{\n\t\t\t\tDescription: \"push-to-registry\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\ttargetRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, \"test-list-push:v1\")\n\t\t\t\t\thelpers.Ensure(\"pull\", manifestRef)\n\t\t\t\t\thelpers.Ensure(\"tag\", manifestRef, targetRef)\n\t\t\t\t\thelpers.Ensure(\"--hosts-dir\", registryTokenAuthHTTPSRandom.HostsDir, \"login\", \"-u\", \"admin\", \"-p\", \"badmin\",\n\t\t\t\t\t\tfmt.Sprintf(\"%s:%d\", registryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port))\n\t\t\t\t\thelpers.Ensure(\"push\", \"--hosts-dir\", registryTokenAuthHTTPSRandom.HostsDir, targetRef)\n\t\t\t\t\thelpers.Ensure(\"rmi\", targetRef)\n\t\t\t\t\thelpers.Ensure(\"manifest\", \"create\", \"--insecure\", targetRef+\"-success\", targetRef)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\ttargetRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, \"test-list-push:v1\")\n\t\t\t\t\treturn helpers.Command(\"manifest\", \"push\", \"--insecure\", targetRef+\"-success\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 0,\n\t\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\t\"output\": expectedDigest,\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tDescription: \"reject-cross-registry-sources\",\n\t\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t\ttargetRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, \"test-list-push:v1\")\n\t\t\t\t\thelpers.Ensure(\"manifest\", \"create\", \"--insecure\", targetRef+\"-cross\", manifestRef)\n\t\t\t\t},\n\t\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t\ttargetRef := fmt.Sprintf(\"%s:%d/%s\",\n\t\t\t\t\t\tregistryTokenAuthHTTPSRandom.IP.String(), registryTokenAuthHTTPSRandom.Port, \"test-list-push:v1\")\n\t\t\t\t\treturn helpers.Command(\"manifest\", \"push\", \"--insecure\", targetRef+\"-cross\")\n\t\t\t\t},\n\t\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\t\treturn &test.Expected{\n\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\t\"error\": \"cannot use source images from a different registry than the target image:\",\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t}\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/manifest\"\n)\n\nfunc removeCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:               \"rm INDEX/MANIFESTLIST [INDEX/MANIFESTLIST...]\",\n\t\tShort:             \"Remove one or more index/manifest lists\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              removeAction,\n\t\tValidArgsFunction: removeShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc removeAction(cmd *cobra.Command, refs []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar errs []error\n\tfor _, ref := range refs {\n\t\terr := manifest.Remove(cmd.Context(), ref, globalOptions)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn errors.Join(errs...)\n}\n\nfunc removeShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.ImageNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_remove_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestManifestsRemove(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\tmanifestListName1 := \"example.com/test-list-remove:v1\"\n\tmanifestListName2 := \"example.com/test-list-remove:v2\"\n\tmanifestRef1 := testutil.GetTestImageWithoutTag(\"alpine\") + \"@\" + testutil.GetTestImageManifestDigest(\"alpine\", \"linux/amd64\")\n\tmanifestRef2 := testutil.GetTestImageWithoutTag(\"alpine\") + \"@\" + testutil.GetTestImageManifestDigest(\"alpine\", \"linux/arm64\")\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"remove-several-manifestlists\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tcmd := helpers.Command(\"manifest\", \"create\", manifestListName1, manifestRef1)\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t\tcmd = helpers.Command(\"manifest\", \"create\", manifestListName2, manifestRef2)\n\t\t\t\tcmd.Run(&test.Expected{ExitCode: 0})\n\t\t\t},\n\t\t\tCommand: test.Command(\"manifest\", \"rm\", manifestListName1, manifestListName2),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"remove-non-existent-manifestlist\",\n\t\t\tCommand:     test.Command(\"manifest\", \"rm\", \"example.com/non-existent:latest\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errors.New(data.Labels().Get(\"error\"))},\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"error\": \"No such manifest: example.com/non-existent:latest\",\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/manifest/manifest_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"namespace\",\n\t\tAliases:       []string{\"ns\"},\n\t\tShort:         \"Manage containerd namespaces\",\n\t\tLong:          \"Unrelated to Linux namespaces and Kubernetes namespaces\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(listCommand())\n\tcmd.AddCommand(removeCommand())\n\tcmd.AddCommand(createCommand())\n\tcmd.AddCommand(updateCommand())\n\tcmd.AddCommand(inspectCommand())\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/namespace\"\n)\n\nfunc createCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"create NAMESPACE\",\n\t\tShort:         \"Create a new namespace\",\n\t\tArgs:          cobra.MinimumNArgs(1),\n\t\tRunE:          createAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.Flags().StringArrayP(\"label\", \"l\", nil, \"Set labels for a namespace\")\n\treturn cmd\n}\n\nfunc processNamespaceCreateCommandOption(cmd *cobra.Command) (types.NamespaceCreateOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.NamespaceCreateOptions{}, err\n\t}\n\tlabels, err := cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn types.NamespaceCreateOptions{}, err\n\t}\n\treturn types.NamespaceCreateOptions{\n\t\tGOptions: globalOptions,\n\t\tLabels:   labels,\n\t}, nil\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\toptions, err := processNamespaceCreateCommandOption(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn namespace.Create(ctx, client, args[0], options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace_inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/namespace\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"inspect NAMESPACE\",\n\t\tShort:             \"Display detailed information on one or more namespaces.\",\n\t\tRunE:              inspectAction,\n\t\tValidArgsFunction: namespaceInspectShellComplete,\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc inspectOptions(cmd *cobra.Command) (types.NamespaceInspectOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.NamespaceInspectOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.NamespaceInspectOptions{}, err\n\t}\n\treturn types.NamespaceInspectOptions{\n\t\tGOptions: globalOptions,\n\t\tFormat:   format,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\toptions, err := inspectOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn namespace.Inspect(ctx, client, args, options)\n}\n\nfunc namespaceInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.NamespaceNames(cmd, args, toComplete)\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace_list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/namespace\"\n)\n\nfunc listCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"ls\",\n\t\tAliases:       []string{\"list\"},\n\t\tShort:         \"List containerd namespaces\",\n\t\tRunE:          listAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only display names\")\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\treturn cmd\n}\n\nfunc listOptions(cmd *cobra.Command) (types.NamespaceListOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.NamespaceListOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.NamespaceListOptions{}, err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.NamespaceListOptions{}, err\n\t}\n\treturn types.NamespaceListOptions{\n\t\tGOptions: globalOptions,\n\t\tFormat:   format,\n\t\tQuiet:    quiet,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc listAction(cmd *cobra.Command, args []string) error {\n\toptions, err := listOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn namespace.List(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/namespace\"\n)\n\nfunc removeCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"remove [flags] NAMESPACE [NAMESPACE...]\",\n\t\tAliases:           []string{\"rm\"},\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tShort:             \"Remove one or more namespaces\",\n\t\tRunE:              removeAction,\n\t\tValidArgsFunction: namespaceRemoveShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().BoolP(\"cgroup\", \"c\", false, \"delete the namespace's cgroup\")\n\treturn cmd\n}\n\nfunc removeOptions(cmd *cobra.Command) (types.NamespaceRemoveOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.NamespaceRemoveOptions{}, err\n\t}\n\tcgroup, err := cmd.Flags().GetBool(\"cgroup\")\n\tif err != nil {\n\t\treturn types.NamespaceRemoveOptions{}, err\n\t}\n\treturn types.NamespaceRemoveOptions{\n\t\tGOptions: globalOptions,\n\t\tCGroup:   cgroup,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc removeAction(cmd *cobra.Command, args []string) error {\n\toptions, err := removeOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn namespace.Remove(ctx, client, args, options)\n}\n\nfunc namespaceRemoveShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.NamespaceNames(cmd, args, toComplete)\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/namespace/namespace_update.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/namespace\"\n)\n\nfunc updateCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"update [flags] NAMESPACE\",\n\t\tShort:             \"Update labels for a namespace\",\n\t\tRunE:              updateAction,\n\t\tValidArgsFunction: namespaceUpdateShellComplete,\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().StringArrayP(\"label\", \"l\", nil, \"Set labels for a namespace (required)\")\n\tcmd.MarkFlagRequired(\"label\")\n\treturn cmd\n}\n\nfunc processNamespaceUpdateCommandOption(cmd *cobra.Command) (types.NamespaceUpdateOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.NamespaceUpdateOptions{}, err\n\t}\n\tlabels, err := cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn types.NamespaceUpdateOptions{}, err\n\t}\n\treturn types.NamespaceUpdateOptions{\n\t\tGOptions: globalOptions,\n\t\tLabels:   labels,\n\t}, nil\n}\n\nfunc updateAction(cmd *cobra.Command, args []string) error {\n\toptions, err := processNamespaceUpdateCommandOption(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn namespace.Update(ctx, client, args[0], options)\n}\n\nfunc namespaceUpdateShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\treturn completion.NamespaceNames(cmd, args, toComplete)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"network\",\n\t\tShort:         \"Manage networks\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tlistCommand(),\n\t\tinspectCommand(),\n\t\tcreateCommand(),\n\t\tremoveCommand(),\n\t\tpruneCommand(),\n\t)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/network\"\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc createCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"create [flags] NETWORK\",\n\t\tShort:         \"Create a network\",\n\t\tLong:          `NOTE: To isolate CNI bridge, CNI plugin \"firewall\" (>= v1.1.0) is needed.`,\n\t\tArgs:          helpers.IsExactArgs(1),\n\t\tRunE:          createAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringP(\"driver\", \"d\", DefaultNetworkDriver, \"Driver to manage the Network\")\n\tcmd.RegisterFlagCompletionFunc(\"driver\", completion.NetworkDrivers)\n\tcmd.Flags().StringArrayP(\"opt\", \"o\", nil, \"Set driver specific options\")\n\tcmd.RegisterFlagCompletionFunc(\"opt\", completion.NetworkOptions)\n\tcmd.Flags().String(\"ipam-driver\", \"default\", \"IP Address helpers.Management Driver\")\n\tcmd.RegisterFlagCompletionFunc(\"ipam-driver\", completion.IPAMDrivers)\n\tcmd.Flags().StringArray(\"ipam-opt\", nil, \"Set IPAM driver specific options\")\n\tcmd.Flags().StringArray(\"subnet\", nil, `Subnet in CIDR format that represents a network segment, e.g. \"10.5.0.0/16\"`)\n\tcmd.Flags().String(\"gateway\", \"\", `Gateway for the master subnet`)\n\tcmd.Flags().String(\"ip-range\", \"\", `Allocate container ip from a sub-range`)\n\tcmd.Flags().StringArray(\"label\", nil, \"Set metadata for a network\")\n\tcmd.Flags().Bool(\"ipv6\", false, \"Enable IPv6 networking\")\n\tcmd.Flags().Bool(\"internal\", false, \"Restrict external access to the network\")\n\treturn cmd\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tname := args[0]\n\tif err := identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn fmt.Errorf(\"invalid network name: %w\", err)\n\t}\n\tdriver, err := cmd.Flags().GetString(\"driver\")\n\tif err != nil {\n\t\treturn err\n\t}\n\topts, err := cmd.Flags().GetStringArray(\"opt\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tipamDriver, err := cmd.Flags().GetString(\"ipam-driver\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tipamOpts, err := cmd.Flags().GetStringArray(\"ipam-opt\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tsubnets, err := cmd.Flags().GetStringArray(\"subnet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tgatewayStr, err := cmd.Flags().GetString(\"gateway\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tipRangeStr, err := cmd.Flags().GetString(\"ip-range\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tlabels, err := cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tlabels = strutil.DedupeStrSlice(labels)\n\tipv6, err := cmd.Flags().GetBool(\"ipv6\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tinternal, err := cmd.Flags().GetBool(\"internal\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn network.Create(types.NetworkCreateOptions{\n\t\tGOptions:    globalOptions,\n\t\tName:        name,\n\t\tDriver:      driver,\n\t\tOptions:     strutil.ConvertKVStringsToMap(opts),\n\t\tIPAMDriver:  ipamDriver,\n\t\tIPAMOptions: strutil.ConvertKVStringsToMap(ipamOpts),\n\t\tSubnets:     subnets,\n\t\tGateway:     gatewayStr,\n\t\tIPRange:     ipRangeStr,\n\t\tLabels:      labels,\n\t\tIPv6:        ipv6,\n\t\tInternal:    internal,\n\t}, cmd.OutOrStdout())\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_create_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestNetworkCreate(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"vanilla\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\"network\", \"create\", identifier)\n\t\t\t\tnetw := nerdtest.InspectNetwork(helpers, identifier)\n\t\t\t\tassert.Equal(t, len(netw.IPAM.Config), 1)\n\t\t\t\tdata.Labels().Set(\"subnet\", netw.IPAM.Config[0].Subnet)\n\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"1\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier(\"1\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tdata.Labels().Set(\"container2\", helpers.Capture(\"run\", \"--rm\", \"--net\", data.Identifier(\"1\"), testutil.CommonImage, \"ip\", \"route\"))\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(), testutil.CommonImage, \"ip\", \"route\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tErrors:   nil,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, data.Labels().Get(\"subnet\")))\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(data.Labels().Get(\"container2\"), data.Labels().Get(\"subnet\")))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"with MTU\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(), \"--driver\", \"bridge\", \"--opt\", \"com.docker.network.driver.mtu=9216\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(), testutil.CommonImage, \"ifconfig\", \"eth0\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"MTU:9216\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"with ipv6\",\n\t\t\tRequire:     nerdtest.OnlyIPv6,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tsubnetStr := \"2001:db8:8::/64\"\n\t\t\t\tdata.Labels().Set(\"subnetStr\", subnetStr)\n\t\t\t\t_, _, err := net.ParseCIDR(subnetStr)\n\t\t\t\tassert.Assert(t, err == nil)\n\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(), \"--ipv6\", \"--subnet\", subnetStr)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(), testutil.CommonImage, \"ip\", \"addr\", \"show\", \"dev\", \"eth0\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t_, subnet, _ := net.ParseCIDR(data.Labels().Get(\"subnetStr\"))\n\t\t\t\t\t\tip := nerdtest.FindIPv6(stdout)\n\t\t\t\t\t\tassert.Assert(t, subnet.Contains(ip), fmt.Sprintf(\"subnet %s contains ip %s\", subnet, ip))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"internal enabled\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", \"--internal\", data.Identifier())\n\t\t\t\tnetw := nerdtest.InspectNetwork(helpers, data.Identifier())\n\t\t\t\tassert.Equal(t, len(netw.IPAM.Config), 1)\n\t\t\t\tdata.Labels().Set(\"subnet\", netw.IPAM.Config[0].Subnet)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(), testutil.CommonImage, \"ip\", \"route\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(stdout, data.Labels().Get(\"subnet\")))\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(stdout, \"default \"))\n\t\t\t\t\t\tif nerdtest.IsDocker() {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnativeNet := nerdtest.InspectNetworkNative(helpers, data.Identifier())\n\t\t\t\t\t\tvar cni struct {\n\t\t\t\t\t\t\tPlugins []struct {\n\t\t\t\t\t\t\t\tType   string `json:\"type\"`\n\t\t\t\t\t\t\t\tIsGW   bool   `json:\"isGateway\"`\n\t\t\t\t\t\t\t\tIPMasq bool   `json:\"ipMasq\"`\n\t\t\t\t\t\t\t} `json:\"plugins\"`\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_ = json.Unmarshal(nativeNet.CNI, &cni)\n\t\t\t\t\t\t// bridge plugin assertions and no portmap\n\t\t\t\t\t\tfoundBridge := false\n\t\t\t\t\t\tfor _, p := range cni.Plugins {\n\t\t\t\t\t\t\tassert.Assert(t, p.Type != \"portmap\")\n\t\t\t\t\t\t\tif p.Type == \"bridge\" {\n\t\t\t\t\t\t\t\tfoundBridge = true\n\t\t\t\t\t\t\t\tassert.Assert(t, !p.IsGW)\n\t\t\t\t\t\t\t\tassert.Assert(t, !p.IPMasq)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassert.Assert(t, foundBridge)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestNetworkCreateICC(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = require.All(\n\t\trequire.Linux,\n\t)\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"with enable_icc=false\",\n\t\t\tRequire:     nerdtest.CNIFirewallVersion(\"1.7.1\"),\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create a network with ICC disabled\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(), \"--driver\", \"bridge\",\n\t\t\t\t\t\"--opt\", \"com.docker.network.bridge.enable_icc=false\")\n\n\t\t\t\t// Run a container in that network\n\t\t\t\tdata.Labels().Set(\"container1\", helpers.Capture(\"run\", \"-d\", \"--net\", data.Identifier(),\n\t\t\t\t\t\"--name\", data.Identifier(\"c1\"), testutil.CommonImage, \"sleep\", \"infinity\"))\n\n\t\t\t\t// Wait for container to be running\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"c1\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", data.Identifier(\"c1\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// DEBUG: Check br_netfilter module status\n\t\t\t\thelpers.Custom(\"sh\", \"-ec\", \"lsmod | grep br_netfilter || echo 'br_netfilter not loaded'\").Run(&test.Expected{})\n\t\t\t\thelpers.Custom(\"sh\", \"-ec\", \"cat /proc/sys/net/bridge/bridge-nf-call-iptables 2>/dev/null || echo 'bridge-nf-call-iptables not available'\").Run(&test.Expected{})\n\t\t\t\thelpers.Custom(\"sh\", \"-ec\", \"ls /proc/sys/net/bridge/ 2>/dev/null || echo 'bridge sysctl not available'\").Run(&test.Expected{})\n\t\t\t\t// Try to ping the other container in the same network\n\t\t\t\t// This should fail when ICC is disabled\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(),\n\t\t\t\t\ttestutil.CommonImage, \"ping\", \"-c\", \"1\", \"-W\", \"1\", data.Identifier(\"c1\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, nil, nil), // Expect ping to fail with exit code 1\n\t\t},\n\t\t{\n\t\t\tDescription: \"with enable_icc=true\",\n\t\t\tRequire:     nerdtest.CNIFirewallVersion(\"1.7.1\"),\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create a network with ICC enabled (default)\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(), \"--driver\", \"bridge\",\n\t\t\t\t\t\"--opt\", \"com.docker.network.bridge.enable_icc=true\")\n\n\t\t\t\t// Run a container in that network\n\t\t\t\tdata.Labels().Set(\"container1\", helpers.Capture(\"run\", \"-d\", \"--net\", data.Identifier(),\n\t\t\t\t\t\"--name\", data.Identifier(\"c1\"), testutil.CommonImage, \"sleep\", \"infinity\"))\n\t\t\t\t// Wait for container to be running\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"c1\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", data.Identifier(\"c1\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Try to ping the other container in the same network\n\t\t\t\t// This should succeed when ICC is enabled\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(),\n\t\t\t\t\ttestutil.CommonImage, \"ping\", \"-c\", \"1\", \"-W\", \"1\", data.Identifier(\"c1\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil), // Expect ping to succeed with exit code 0\n\t\t},\n\t\t{\n\t\t\tDescription: \"with no enable_icc option set\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Create a network with ICC enabled (default)\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(), \"--driver\", \"bridge\")\n\n\t\t\t\t// Run a container in that network\n\t\t\t\tdata.Labels().Set(\"container1\", helpers.Capture(\"run\", \"-d\", \"--net\", data.Identifier(),\n\t\t\t\t\t\"--name\", data.Identifier(\"c1\"), testutil.CommonImage, \"sleep\", \"infinity\"))\n\t\t\t\t// Wait for container to be running\n\t\t\t\tnerdtest.EnsureContainerStarted(helpers, data.Identifier(\"c1\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"container\", \"rm\", \"-f\", data.Identifier(\"c1\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Try to ping the other container in the same network\n\t\t\t\t// This should succeed when no ICC is set\n\t\t\t\treturn helpers.Command(\"run\", \"--rm\", \"--net\", data.Identifier(),\n\t\t\t\t\ttestutil.CommonImage, \"ping\", \"-c\", \"1\", \"-W\", \"1\", data.Identifier(\"c1\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, nil), // Expect ping to succeed with exit code 0\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_create_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nconst DefaultNetworkDriver = \"bridge\"\n"
  },
  {
    "path": "cmd/nerdctl/network/network_create_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nconst DefaultNetworkDriver = \"nat\"\n"
  },
  {
    "path": "cmd/nerdctl/network/network_inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/network\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"inspect [flags] NETWORK [NETWORK, ...]\",\n\t\tShort:             \"Display detailed information on one or more networks\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              inspectAction,\n\t\tValidArgsFunction: networkInspectShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().String(\"mode\", \"dockercompat\", `Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output`)\n\tcmd.RegisterFlagCompletionFunc(\"mode\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"dockercompat\", \"native\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmode, err := cmd.Flags().GetString(\"mode\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toptions := types.NetworkInspectOptions{\n\t\tGOptions: globalOptions,\n\t\tMode:     mode,\n\t\tFormat:   format,\n\t\tNetworks: args,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn network.Inspect(ctx, client, options)\n}\n\nfunc networkInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show network names, including \"bridge\"\n\texclude := []string{\"host\", \"none\"}\n\treturn completion.NetworkNames(cmd, exclude)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_inspect_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestNetworkInspect(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\tconst (\n\t\ttestSubnet  = \"10.24.24.0/24\"\n\t\ttestGateway = \"10.24.24.1\"\n\t\ttestIPRange = \"10.24.24.0/25\"\n\t)\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"basenet\"))\n\t\tdata.Labels().Set(\"basenet\", data.Identifier(\"basenet\"))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier(\"basenet\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"non existent network\",\n\t\t\tCommand:     test.Command(\"network\", \"inspect\", \"nonexistent\"),\n\t\t\t// FIXME: where is this error even comin from?\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"no network found matching\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid name network\",\n\t\t\tCommand:     test.Command(\"network\", \"inspect\", \"∞\"),\n\t\t\t// FIXME: this is not even a valid identifier\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"no network found matching\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"none\",\n\t\t\tRequire:     nerdtest.NerdctlNeedsFixing(\"no issue opened\"),\n\t\t\tCommand:     test.Command(\"network\", \"inspect\", \"none\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\tassert.Equal(t, dc[0].Name, \"none\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"host\",\n\t\t\tRequire:     nerdtest.NerdctlNeedsFixing(\"no issue opened\"),\n\t\t\tCommand:     test.Command(\"network\", \"inspect\", \"host\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\tassert.Equal(t, dc[0].Name, \"host\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"bridge\",\n\t\t\tRequire:     require.Not(require.Windows),\n\t\t\tCommand:     test.Command(\"network\", \"inspect\", \"bridge\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\tassert.Equal(t, dc[0].Name, \"bridge\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"nat\",\n\t\t\tRequire:     require.Windows,\n\t\t\tCommand:     test.Command(\"network\", \"inspect\", \"nat\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\tassert.Equal(t, dc[0].Name, \"nat\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"custom\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", \"custom\")\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", \"custom\")\n\t\t\t},\n\t\t\tCommand: test.Command(\"network\", \"inspect\", \"custom\"),\n\t\t\tExpected: test.Expects(0, nil, func(stdout string, t tig.T) {\n\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\tassert.Equal(t, dc[0].Name, \"custom\")\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"match exact id\",\n\t\t\t// See notes below\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tid := strings.TrimSpace(helpers.Capture(\"network\", \"inspect\", data.Labels().Get(\"basenet\"), \"--format\", \"{{ .Id }}\"))\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", id)\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tassert.Equal(t, dc[0].Name, data.Labels().Get(\"basenet\"))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"match part of id\",\n\t\t\t// FIXME: for windows, network inspect testnetworkinspect-basenet-468cf999 --format {{ .Id }} MAY fail here\n\t\t\t// This is bizarre, as it is working in the match exact id test - and there does not seem to be a particular reason for that\n\t\t\tRequire: require.Not(require.Windows),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\tid := strings.TrimSpace(helpers.Capture(\"network\", \"inspect\", data.Labels().Get(\"basenet\"), \"--format\", \"{{ .Id }}\"))\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", id[0:25])\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tassert.Equal(t, dc[0].Name, data.Labels().Get(\"basenet\"))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"using another net short id\",\n\t\t\t// FIXME: for windows, network inspect testnetworkinspect-basenet-468cf999 --format {{ .Id }} MAY fail here\n\t\t\t// This is bizarre, as it is working in the match exact id test - and there does not seem to be a particular reason for that\n\t\t\tRequire: require.Not(require.Windows),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tid := strings.TrimSpace(helpers.Capture(\"network\", \"inspect\", data.Labels().Get(\"basenet\"), \"--format\", \"{{ .Id }}\"))\n\t\t\t\thelpers.Ensure(\"network\", \"create\", id[0:12])\n\t\t\t\tdata.Labels().Set(\"netname\", id[0:12])\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Labels().Get(\"netname\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", data.Labels().Get(\"netname\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tassert.Equal(t, dc[0].Name, data.Labels().Get(\"netname\"))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"basic\",\n\t\t\t// FIXME: IPAMConfig is not implemented on Windows yet\n\t\t\tRequire: require.Not(require.Windows),\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", \"--label\", \"tag=testNetwork\", \"--subnet\", testSubnet,\n\t\t\t\t\t\"--gateway\", testGateway, \"--ip-range\", testIPRange, data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar dc []dockercompat.Network\n\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tgot := dc[0]\n\n\t\t\t\t\t\tassert.Equal(t, got.Name, data.Identifier())\n\t\t\t\t\t\tassert.Equal(t, got.Labels[\"tag\"], \"testNetwork\")\n\t\t\t\t\t\tassert.Equal(t, len(got.IPAM.Config), 1)\n\t\t\t\t\t\tassert.Equal(t, got.IPAM.Config[0].Subnet, testSubnet)\n\t\t\t\t\t\tassert.Equal(t, got.IPAM.Config[0].Gateway, testGateway)\n\t\t\t\t\t\tassert.Equal(t, got.IPAM.Config[0].IPRange, testIPRange)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"with namespace\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", identifier)\n\t\t\t\thelpers.Anyhow(\"namespace\", \"remove\", identifier)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"create\", data.Identifier())\n\t\t\t},\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t// Note: some functions need to be tested without the automatic --namespace nerdctl-test argument, so we need\n\t\t\t\t\t\t// to retrieve the binary name.\n\t\t\t\t\t\t// Note that we know this works already, so no need to assert err.\n\t\t\t\t\t\tbin, _ := exec.LookPath(testutil.GetTarget())\n\t\t\t\t\t\tcmd := helpers.Custom(bin, \"--namespace\", data.Identifier())\n\n\t\t\t\t\t\tcom := cmd.Clone()\n\t\t\t\t\t\tcom.WithArgs(\"network\", \"inspect\", data.Identifier())\n\t\t\t\t\t\tcom.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\tErrors:   []error{errors.New(\"no network found\")},\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tcom = cmd.Clone()\n\t\t\t\t\t\tcom.WithArgs(\"network\", \"remove\", data.Identifier())\n\t\t\t\t\t\tcom.Run(&test.Expected{\n\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\tErrors:   []error{errors.New(\"no network found\")},\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tcom = cmd.Clone()\n\t\t\t\t\t\tcom.WithArgs(\"network\", \"ls\")\n\t\t\t\t\t\tcom.Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.DoesNotContain(data.Identifier()),\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tcom = cmd.Clone()\n\t\t\t\t\t\tcom.WithArgs(\"network\", \"prune\", \"-f\")\n\t\t\t\t\t\tcom.Run(&test.Expected{\n\t\t\t\t\t\t\tOutput: expect.DoesNotContain(data.Identifier()),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Verify that only active containers appear in the network inspect output\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"nginx-network-1\"))\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"nginx-network-2\"))\n\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/4322\n\t\t\t\t// Maybe network create on windows is asynchronous?\n\t\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t}\n\n\t\t\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(\"nginx-container-1\"), \"--network\", data.Identifier(\"nginx-network-1\"), testutil.NginxAlpineImage)\n\t\t\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(\"nginx-container-2\"), \"--network\", data.Identifier(\"nginx-network-1\"), testutil.NginxAlpineImage)\n\t\t\t\thelpers.Ensure(\"create\", \"--name\", data.Identifier(\"nginx-container-on-diff-network\"), \"--network\", data.Identifier(\"nginx-network-2\"), testutil.NginxAlpineImage)\n\t\t\t\thelpers.Ensure(\"start\", data.Identifier(\"nginx-container-1\"), data.Identifier(\"nginx-container-on-diff-network\"))\n\t\t\t\tdata.Labels().Set(\"nginx-container-1-id\", strings.Trim(helpers.Capture(\"inspect\", data.Identifier(\"nginx-container-1\"), \"--format\", \"{{.Id}}\"), \"\\n\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"nginx-container-1\"))\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"nginx-container-2\"))\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier(\"nginx-container-on-diff-network\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Identifier(\"nginx-network-1\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Identifier(\"nginx-network-2\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", data.Identifier(\"nginx-network-1\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar dc []dockercompat.Network\n\t\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\t\tassert.NilError(t, err, \"Unable to unmarshal output\\n\")\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tassert.Equal(t, dc[0].Name, data.Identifier(\"nginx-network-1\"))\n\t\t\t\t\t\t// Assert only the \"running\" containers on the same network are returned.\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc[0].Containers), \"Expected a single container as per configuration, but got multiple.\")\n\t\t\t\t\t\tassert.Equal(t, data.Identifier(\"nginx-container-1\"), dc[0].Containers[data.Labels().Get(\"nginx-container-1-id\")].Name)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Display containers belonging to multiple networks in the output of nerdctl network inspect\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"network-1\"))\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"network-2\"))\n\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/4322\n\t\t\t\t// Maybe network create on windows is asynchronous?\n\t\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t}\n\n\t\t\t\tcontainerID := helpers.Capture(\"run\", \"-d\", \"--name\", data.Identifier(), \"--network\", data.Identifier(\"network-1\"), \"--network\", data.Identifier(\"network-2\"), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\n\t\t\t\tdata.Labels().Set(\"containerID\", strings.Trim(containerID, \"\\n\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Identifier(\"network-1\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Identifier(\"network-2\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", data.Identifier(\"network-1\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.JSON([]dockercompat.Network{}, func(dc []dockercompat.Network, t tig.T) {\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tassert.Equal(t, dc[0].Name, data.Identifier(\"network-1\"))\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc[0].Containers), \"Expected a single container as per configuration, but got multiple.\")\n\t\t\t\t\t\tassert.Equal(t, data.Identifier(), dc[0].Containers[data.Labels().Get(\"containerID\")].Name)\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Display only containers attached to the specific network\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"some-network\"))\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier(\"some-network-as-well\"))\n\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/4322\n\t\t\t\t// Maybe network create on windows is asynchronous?\n\t\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t}\n\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--name\", data.Identifier(), \"--network\", data.Identifier(\"some-network-as-well\"), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Identifier(\"some-network\"))\n\t\t\t\thelpers.Anyhow(\"network\", \"remove\", data.Identifier(\"some-network-as-well\"))\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"inspect\", data.Identifier(\"some-network\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.JSON([]dockercompat.Network{}, func(dc []dockercompat.Network, t tig.T) {\n\t\t\t\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\t\tassert.Equal(t, dc[0].Name, data.Identifier(\"some-network\"))\n\t\t\t\t\t\tassert.Equal(t, 0, len(dc[0].Containers), \"Expected no containers as per configuration, but got multiple.\")\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/network\"\n)\n\nfunc listCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"ls\",\n\t\tAliases:       []string{\"list\"},\n\t\tShort:         \"List networks\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          listAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only display network IDs\")\n\tcmd.Flags().StringSliceP(\"filter\", \"f\", []string{}, \"Provide filter values (e.g. \\\"name=default\\\")\")\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\", \"table\", \"wide\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc listAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilters, err := cmd.Flags().GetStringSlice(\"filter\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn network.List(cmd.Context(), types.NetworkListOptions{\n\t\tGOptions: globalOptions,\n\t\tQuiet:    quiet,\n\t\tFormat:   format,\n\t\tFilters:  filters,\n\t\tStdout:   cmd.OutOrStdout(),\n\t})\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_list_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestNetworkLsFilter(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"identifier\", data.Identifier())\n\t\tdata.Labels().Set(\"label\", \"mylabel=label-1\")\n\t\tdata.Labels().Set(\"net1\", data.Identifier(\"1\"))\n\t\tdata.Labels().Set(\"net2\", data.Identifier(\"2\"))\n\t\tdata.Labels().Set(\"netID1\", helpers.Capture(\"network\", \"create\", \"--label=\"+data.Labels().Get(\"label\"), data.Labels().Get(\"net1\")))\n\t\tdata.Labels().Set(\"netID2\", helpers.Capture(\"network\", \"create\", data.Labels().Get(\"net2\")))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier(\"1\"))\n\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier(\"2\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"filter label\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"ls\", \"--quiet\", \"--filter\", \"label=\"+data.Labels().Get(\"label\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 1, \"expected at least one line\\n\")\n\t\t\t\t\t\tnetNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"netID1\")[:12]: {},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := netNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, \"expected to find name\\n\")\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"filter name\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"ls\", \"--quiet\", \"--filter\", \"name=\"+data.Labels().Get(\"net2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 1, \"expected at least one line\\n\")\n\t\t\t\t\t\tnetNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"netID2\")[:12]: {},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := netNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, \"expected to find name\\n\")\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"filter name regexp\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"ls\", \"--quiet\", \"--filter\", \"name=.*\"+data.Labels().Get(\"net2\")+\".*\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 1)\n\t\t\t\t\t\tnetNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"netID2\")[:12]: {},\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := netNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok)\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/network\"\n)\n\nvar NetworkDriversToKeep = []string{\"host\", \"none\", DefaultNetworkDriver}\n\nfunc pruneCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"prune [flags]\",\n\t\tShort:         \"Remove all unused networks\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          pruneAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\treturn cmd\n}\n\nfunc pruneAction(cmd *cobra.Command, _ []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !force {\n\t\tvar confirm string\n\t\tmsg := \"This will remove all custom networks not used by at least one container.\"\n\t\tmsg += \"\\nAre you sure you want to continue? [y/N] \"\n\n\t\tfmt.Fprintf(cmd.OutOrStdout(), \"WARNING! %s\", msg)\n\t\tfmt.Fscanf(cmd.InOrStdin(), \"%s\", &confirm)\n\n\t\tif strings.ToLower(confirm) != \"y\" {\n\t\t\treturn nil\n\t\t}\n\t}\n\toptions := types.NetworkPruneOptions{\n\t\tGOptions:             globalOptions,\n\t\tNetworkDriversToKeep: NetworkDriversToKeep,\n\t\tStdout:               cmd.OutOrStdout(),\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn network.Prune(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_prune_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestNetworkPrune(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Private\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Prune does not collect started container network\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\"network\", \"create\", identifier)\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--net\", identifier, \"--name\", identifier, testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"network\", \"prune\", \"-f\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.DoesNotContain(data.Identifier()),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Prune does collect stopped container network\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--net\", data.Identifier(), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t\thelpers.Ensure(\"stop\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"network\", \"prune\", \"-f\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Contains(data.Identifier()),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/network\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\nfunc removeCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"rm [flags] NETWORK [NETWORK, ...]\",\n\t\tAliases:           []string{\"remove\"},\n\t\tShort:             \"Remove one or more networks\",\n\t\tLong:              \"NOTE: network in use is deleted without caution\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              removeAction,\n\t\tValidArgsFunction: networkRmShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\treturn cmd\n}\n\nfunc removeAction(cmd *cobra.Command, args []string) error {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toptions := types.NetworkRemoveOptions{\n\t\tGOptions: globalOptions,\n\t\tNetworks: args,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn network.Remove(ctx, client, options)\n}\n\nfunc networkRmShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show network names, including \"bridge\"\n\texclude := []string{netutil.DefaultNetworkName, \"host\", \"none\"}\n\treturn completion.NetworkNames(cmd, exclude)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_remove_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/vishvananda/netlink\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestNetworkRemove(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.Rootful\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"Simple network remove\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\tidentifier := data.Identifier()\n\t\t\t\thelpers.Ensure(\"network\", \"create\", identifier)\n\t\t\t\tdata.Labels().Set(\"netID\", nerdtest.InspectNetwork(helpers, identifier).ID)\n\t\t\t\thelpers.Ensure(\"run\", \"--rm\", \"--net\", identifier, \"--name\", identifier, testutil.CommonImage)\n\t\t\t\t// Verity the network is here\n\t\t\t\t_, err := netlink.LinkByName(\"br-\" + data.Labels().Get(\"netID\")[:12])\n\t\t\t\tassert.NilError(t, err, \"failed to find network br-\"+data.Labels().Get(\"netID\")[:12], \"%v\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t_, err := netlink.LinkByName(\"br-\" + data.Labels().Get(\"netID\")[:12])\n\t\t\t\t\t\tassert.Error(t, err, \"Link not found\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Network remove when linked to container\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"run\", \"-d\", \"--net\", data.Identifier(), \"--name\", data.Identifier(), testutil.CommonImage, \"sleep\", nerdtest.Infinity)\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{errors.New(\"is in use\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"Network remove by id\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier())\n\t\t\t\tdata.Labels().Set(\"netID\", nerdtest.InspectNetwork(helpers, data.Identifier()).ID)\n\t\t\t\thelpers.Ensure(\"run\", \"--rm\", \"--net\", data.Identifier(), \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\t// Verity the network is here\n\t\t\t\t_, err := netlink.LinkByName(\"br-\" + data.Labels().Get(\"netID\")[:12])\n\t\t\t\tassert.NilError(t, err, \"failed to find network br-\"+data.Labels().Get(\"netID\")[:12], \"%v\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"rm\", data.Labels().Get(\"netID\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t_, err := netlink.LinkByName(\"br-\" + data.Labels().Get(\"netID\")[:12])\n\t\t\t\t\t\tassert.Error(t, err, \"Link not found\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Network remove by short id\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier())\n\t\t\t\tdata.Labels().Set(\"netID\", nerdtest.InspectNetwork(helpers, data.Identifier()).ID)\n\t\t\t\thelpers.Ensure(\"run\", \"--rm\", \"--net\", data.Identifier(), \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\t// Verity the network is here\n\t\t\t\t_, err := netlink.LinkByName(\"br-\" + data.Labels().Get(\"netID\")[:12])\n\t\t\t\tassert.NilError(t, err, \"failed to find network br-\"+data.Labels().Get(\"netID\")[:12], \"%v\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"network\", \"rm\", data.Labels().Get(\"netID\")[:12])\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\t_, err := netlink.LinkByName(\"br-\" + data.Labels().Get(\"netID\")[:12])\n\t\t\t\t\t\tassert.Error(t, err, \"Link not found\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/network/network_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/search/search.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage search\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/search\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:                   \"search [OPTIONS] TERM\",\n\t\tShort:                 \"Search registry for images\",\n\t\tArgs:                  cobra.ExactArgs(1),\n\t\tRunE:                  runSearch,\n\t\tDisableFlagsInUseLine: true,\n\t}\n\n\tflags := cmd.Flags()\n\n\tflags.Bool(\"no-trunc\", false, \"Don't truncate output\")\n\tflags.StringSliceP(\"filter\", \"f\", nil, \"Filter output based on conditions provided\")\n\tflags.Int(\"limit\", 0, \"Max number of search results\")\n\tflags.String(\"format\", \"\", \"Pretty-print search using a Go template\")\n\n\treturn cmd\n}\n\nfunc processSearchFlags(cmd *cobra.Command) (types.SearchOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.SearchOptions{}, err\n\t}\n\n\tnoTrunc, err := cmd.Flags().GetBool(\"no-trunc\")\n\tif err != nil {\n\t\treturn types.SearchOptions{}, err\n\t}\n\tlimit, err := cmd.Flags().GetInt(\"limit\")\n\tif err != nil {\n\t\treturn types.SearchOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.SearchOptions{}, err\n\t}\n\tfilter, err := cmd.Flags().GetStringSlice(\"filter\")\n\tif err != nil {\n\t\treturn types.SearchOptions{}, err\n\t}\n\n\treturn types.SearchOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tNoTrunc:  noTrunc,\n\t\tLimit:    limit,\n\t\tFilters:  filter,\n\t\tFormat:   format,\n\t}, nil\n}\n\nfunc runSearch(cmd *cobra.Command, args []string) error {\n\toptions, err := processSearchFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn search.Search(cmd.Context(), args[0], options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/search/search_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage search\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// All tests in this file are based on the output of `nerdctl search alpine`.\n//\n// Expected output format (default behavior with --limit 10):\n//\n// NAME                DESCRIPTION                                     STARS               OFFICIAL\n// alpine              A minimal Docker image based on Alpine Linux…   11437               [OK]\n// alpine/git          A  simple git container running in alpine li…   249\n// alpine/socat        Run socat command in alpine container           115\n// alpine/helm         Auto-trigger docker build for kubernetes hel…   69\n// alpine/curl                                                         11\n// alpine/k8s          Kubernetes toolbox for EKS (kubectl, helm, i…   64\n// alpine/bombardier   Auto-trigger docker build for bombardier whe…   28\n// alpine/httpie       Auto-trigger docker build for `httpie` when …   21\n// alpine/terragrunt   Auto-trigger docker build for terragrunt whe…   18\n// alpine/openssl      openssl                                         7\n\nfunc TestSearch(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"basic-search\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--limit\", \"5\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(\"NAME\"),\n\t\t\t\t\t\texpect.Contains(\"DESCRIPTION\"),\n\t\t\t\t\t\texpect.Contains(\"STARS\"),\n\t\t\t\t\t\texpect.Contains(\"OFFICIAL\"),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`NAME\\s+DESCRIPTION\\s+STARS\\s+OFFICIAL`)),\n\t\t\t\t\t\texpect.Contains(\"alpine\"),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine\\s+A minimal Docker image based on Alpine Linux`)),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine\\s+.*\\s+\\d+\\s+\\[OK\\]`)),\n\t\t\t\t\t\texpect.Contains(\"[OK]\"),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine/\\w+`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"search-library-image\",\n\t\t\tCommand:     test.Command(\"search\", \"library/alpine\", \"--limit\", \"5\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(\"NAME\"),\n\t\t\t\t\t\texpect.Contains(\"DESCRIPTION\"),\n\t\t\t\t\t\texpect.Contains(\"STARS\"),\n\t\t\t\t\t\texpect.Contains(\"OFFICIAL\"),\n\t\t\t\t\t\texpect.Contains(\"alpine\"),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine\\s+.*\\s+\\d+\\s+\\[OK\\]`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"search-with-no-trunc\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--limit\", \"3\", \"--no-trunc\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(\"NAME\"),\n\t\t\t\t\t\texpect.Contains(\"DESCRIPTION\"),\n\t\t\t\t\t\texpect.Contains(\"alpine\"),\n\t\t\t\t\t\t// With --no-trunc, the full description should be visible (not truncated with …)\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine\\s+A minimal Docker image based on Alpine Linux with a complete package index and only 5 MB in size!`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"search-with-format\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--limit\", \"2\", \"--format\", \"{{.Name}}: {{.StarCount}}\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine:\\s*\\d+`)),\n\t\t\t\t\t\texpect.DoesNotContain(\"NAME\"),\n\t\t\t\t\t\texpect.DoesNotContain(\"DESCRIPTION\"),\n\t\t\t\t\t\texpect.DoesNotContain(\"OFFICIAL\"),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"search-output-format\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--limit\", \"5\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`NAME\\s+DESCRIPTION\\s+STARS\\s+OFFICIAL`)),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`(?m)^alpine\\s+.*\\s+\\d+\\s+\\[OK\\]\\s*$`)),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`(?m)^alpine/\\w+\\s+.*\\s+\\d+\\s*$`)),\n\t\t\t\t\t\texpect.DoesNotMatch(regexp.MustCompile(`(?m)^\\s+\\d+\\s*$`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"search-description-formatting\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--limit\", \"10\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`Alpine Linux…`)),\n\t\t\t\t\t\texpect.DoesNotMatch(regexp.MustCompile(`(?m)^\\s+\\d+\\s+`)),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`(?m)^[a-z0-9/_-]+\\s+.*\\s+\\d+`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestSearchWithFilter(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"filter-is-official-true\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--filter\", \"is-official=true\", \"--limit\", \"5\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(\"NAME\"),\n\t\t\t\t\t\texpect.Contains(\"OFFICIAL\"),\n\t\t\t\t\t\texpect.Contains(\"alpine\"),\n\t\t\t\t\t\texpect.Contains(\"[OK]\"),\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine\\s+.*\\s+\\d+\\s+\\[OK\\]`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"filter-stars\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--filter\", \"stars=10000\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeSuccess,\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(\"NAME\"),\n\t\t\t\t\t\texpect.Contains(\"STARS\"),\n\t\t\t\t\t\texpect.Contains(\"alpine\"),\n\t\t\t\t\t\t// The official alpine image has > 10000 stars\n\t\t\t\t\t\texpect.Match(regexp.MustCompile(`alpine\\s+.*\\s+\\d{4,}\\s+\\[OK\\]`)),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n\nfunc TestSearchFilterErrors(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"invalid-filter-format\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--filter\", \"foo\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeGenericFail,\n\t\t\t\t\tErrors:   []error{errors.New(\"bad format of filter (expected name=value)\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-filter-key\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--filter\", \"foo=bar\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeGenericFail,\n\t\t\t\t\tErrors:   []error{errors.New(\"invalid filter 'foo'\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-stars-value\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--filter\", \"stars=abc\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeGenericFail,\n\t\t\t\t\tErrors:   []error{errors.New(\"invalid filter 'stars=abc'\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid-is-official-value\",\n\t\t\tCommand:     test.Command(\"search\", \"alpine\", \"--filter\", \"is-official=abc\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeGenericFail,\n\t\t\t\t\tErrors:   []error{errors.New(\"invalid filter 'is-official=abc'\")},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/search/search_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage search\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"system\",\n\t\tShort:         \"Manage containerd\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\t// versionCommand is not here\n\tcmd.AddCommand(\n\t\tEventsCommand(),\n\t\tInfoCommand(),\n\t\tpruneCommand(),\n\t)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_events.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/system\"\n)\n\nfunc EventsCommand() *cobra.Command {\n\tshortHelp := `Get real time events from the server`\n\tlongHelp := shortHelp + \"\\nNOTE: The output format is not compatible with Docker.\"\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"events\",\n\t\tArgs:          cobra.NoArgs,\n\t\tShort:         shortHelp,\n\t\tLong:          longHelp,\n\t\tRunE:          eventsAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringSliceP(\"filter\", \"f\", []string{}, \"Filter matches containers based on given conditions\")\n\treturn cmd\n}\n\nfunc eventsOptions(cmd *cobra.Command) (types.SystemEventsOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.SystemEventsOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.SystemEventsOptions{}, err\n\t}\n\tfilters, err := cmd.Flags().GetStringSlice(\"filter\")\n\tif err != nil {\n\t\treturn types.SystemEventsOptions{}, err\n\t}\n\treturn types.SystemEventsOptions{\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tGOptions: globalOptions,\n\t\tFormat:   format,\n\t\tFilters:  filters,\n\t}, nil\n}\n\nfunc eventsAction(cmd *cobra.Command, args []string) error {\n\toptions, err := eventsOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\treturn system.Events(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_events_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc testEventFilterExecutor(data test.Data, helpers test.Helpers) test.TestableCommand {\n\thelpers.Ensure(\"pull\", testutil.CommonImage)\n\tcmd := helpers.Command(\"events\", \"--filter\", data.Labels().Get(\"filter\"), \"--format\", \"json\")\n\t// 3 seconds is too short on slow rig (EL8)\n\tcmd.WithTimeout(10 * time.Second)\n\tcmd.Background()\n\thelpers.Ensure(\"run\", \"--rm\", testutil.CommonImage)\n\treturn cmd\n}\n\nfunc TestEventFilters(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"CapitalizedFilter\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand:     testEventFilterExecutor,\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeTimeout,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"filter\": \"event=START\",\n\t\t\t\t\"output\": \"\\\"Status\\\":\\\"start\\\"\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"StartEventFilter\",\n\t\t\tCommand:     testEventFilterExecutor,\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeTimeout,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"filter\": \"event=start\",\n\t\t\t\t\"output\": \"tatus\\\":\\\"start\\\"\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"UnsupportedEventFilter\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand:     testEventFilterExecutor,\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeTimeout,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"filter\": \"event=unknown\",\n\t\t\t\t\"output\": \"\\\"Status\\\":\\\"unknown\\\"\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"StatusFilter\",\n\t\t\tCommand:     testEventFilterExecutor,\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeTimeout,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"filter\": \"status=start\",\n\t\t\t\t\"output\": \"tatus\\\":\\\"start\\\"\",\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tDescription: \"UnsupportedStatusFilter\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand:     testEventFilterExecutor,\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: expect.ExitCodeTimeout,\n\t\t\t\t\tOutput:   expect.Contains(data.Labels().Get(\"output\")),\n\t\t\t\t}\n\t\t\t},\n\t\t\tData: test.WithLabels(map[string]string{\n\t\t\t\t\"filter\": \"status=unknown\",\n\t\t\t\t\"output\": \"\\\"Status\\\":\\\"unknown\\\"\",\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_info.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/system\"\n)\n\nfunc InfoCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"info\",\n\t\tArgs:          cobra.NoArgs,\n\t\tShort:         \"Display system-wide information\",\n\t\tRunE:          infoAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().String(\"mode\", \"dockercompat\", `Information mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output`)\n\tcmd.RegisterFlagCompletionFunc(\"mode\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"dockercompat\", \"native\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc infoOptions(cmd *cobra.Command) (types.SystemInfoOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.SystemInfoOptions{}, err\n\t}\n\n\tmode, err := cmd.Flags().GetString(\"mode\")\n\tif err != nil {\n\t\treturn types.SystemInfoOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.SystemInfoOptions{}, err\n\t}\n\treturn types.SystemInfoOptions{\n\t\tGOptions: globalOptions,\n\t\tMode:     mode,\n\t\tFormat:   format,\n\t\tStdout:   cmd.OutOrStdout(),\n\t\tStderr:   cmd.OutOrStderr(),\n\t}, nil\n}\n\nfunc infoAction(cmd *cobra.Command, args []string) error {\n\toptions, err := infoOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn system.Info(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_info_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc testInfoComparator(stdout string, t tig.T) {\n\tvar dinf dockercompat.Info\n\terr := json.Unmarshal([]byte(stdout), &dinf)\n\tassert.NilError(t, err, \"failed to unmarshal stdout\")\n\tunameM := infoutil.UnameM()\n\tassert.Assert(t, dinf.Architecture == unameM, fmt.Sprintf(\"expected info.Architecture to be %q, got %q\", unameM, dinf.Architecture))\n}\n\nfunc TestInfo(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Note: some functions need to be tested without the automatic --namespace nerdctl-test argument, so we need\n\t// to retrieve the binary name.\n\t// Note that we know this works already, so no need to assert err.\n\tbin, _ := exec.LookPath(testutil.GetTarget())\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"info\",\n\t\t\tCommand:     test.Command(\"info\", \"--format\", \"{{json .}}\"),\n\t\t\tExpected:    test.Expects(0, nil, testInfoComparator),\n\t\t},\n\t\t{\n\t\t\tDescription: \"info convenience form\",\n\t\t\tCommand:     test.Command(\"info\", \"--format\", \"json\"),\n\t\t\tExpected:    test.Expects(0, nil, testInfoComparator),\n\t\t},\n\t\t{\n\t\t\tDescription: \"info with namespace\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Custom(bin, \"info\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"Namespace:\tdefault\")),\n\t\t},\n\t\t{\n\t\t\tDescription: \"info with namespace env var\",\n\t\t\tEnv: map[string]string{\n\t\t\t\t\"CONTAINERD_NAMESPACE\": \"test\",\n\t\t\t},\n\t\t\tRequire: require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Custom(bin, \"info\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"Namespace:\ttest\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/builder\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/network\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/system\"\n)\n\nfunc pruneCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"prune [flags]\",\n\t\tShort:         \"Remove unused data\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          pruneAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Remove all unused images, not just dangling ones\")\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\tcmd.Flags().Bool(\"volumes\", false, \"Prune volumes\")\n\treturn cmd\n}\n\nfunc pruneOptions(cmd *cobra.Command) (types.SystemPruneOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.SystemPruneOptions{}, err\n\t}\n\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn types.SystemPruneOptions{}, err\n\t}\n\n\tvFlag, err := cmd.Flags().GetBool(\"volumes\")\n\tif err != nil {\n\t\treturn types.SystemPruneOptions{}, err\n\t}\n\n\tbuildkitHost, err := builder.GetBuildkitHost(cmd, globalOptions.Namespace)\n\tif err != nil {\n\t\tlog.L.WithError(err).Warn(\"BuildKit is not running. Build caches will not be pruned.\")\n\t\tbuildkitHost = \"\"\n\t}\n\n\treturn types.SystemPruneOptions{\n\t\tStdout:               cmd.OutOrStdout(),\n\t\tStderr:               cmd.ErrOrStderr(),\n\t\tGOptions:             globalOptions,\n\t\tAll:                  all,\n\t\tVolumes:              vFlag,\n\t\tBuildKitHost:         buildkitHost,\n\t\tNetworkDriversToKeep: network.NetworkDriversToKeep,\n\t}, nil\n}\n\nfunc grantSystemPrunePermission(cmd *cobra.Command, options types.SystemPruneOptions) (bool, error) {\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif !force {\n\t\tvar confirm string\n\t\tmsg := `This will remove:\n  - all stopped containers\n  - all networks not used by at least one container`\n\t\tif options.Volumes {\n\t\t\tmsg += `\n  - all volumes not used by at least one container`\n\t\t}\n\t\tif options.All {\n\t\t\tmsg += `\n  - all images without at least one container associated to them\n  - all build cache`\n\t\t} else {\n\t\t\tmsg += `\n  - all dangling images\n  - all dangling build cache`\n\t\t}\n\n\t\tmsg += \"\\nAre you sure you want to continue? [y/N] \"\n\t\tfmt.Fprintf(options.Stdout, \"WARNING! %s\", msg)\n\t\tfmt.Fscanf(cmd.InOrStdin(), \"%s\", &confirm)\n\n\t\tif strings.ToLower(confirm) != \"y\" {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\nfunc pruneAction(cmd *cobra.Command, _ []string) error {\n\toptions, err := pruneOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := grantSystemPrunePermission(cmd, options); err != nil {\n\t\treturn err\n\t} else if !ok {\n\t\treturn nil\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn system.Prune(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_prune_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestSystemPrune(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.NoParallel = true\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"volume prune all success\",\n\t\t\t// Private because of prune evidently\n\t\t\tRequire: nerdtest.Private,\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"network\", \"create\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier())\n\t\t\t\tanonIdentifier := helpers.Capture(\"volume\", \"create\")\n\t\t\t\thelpers.Ensure(\"run\", \"-v\", fmt.Sprintf(\"%s:/volume\", data.Identifier()),\n\t\t\t\t\t\"--net\", data.Identifier(), \"--name\", data.Identifier(), testutil.CommonImage)\n\n\t\t\t\tdata.Labels().Set(\"anonIdentifier\", anonIdentifier)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"network\", \"rm\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", data.Labels().Get(\"anonIdentifier\"))\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: test.Command(\"system\", \"prune\", \"-f\", \"--volumes\", \"--all\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 0,\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvolumes := helpers.Capture(\"volume\", \"ls\")\n\t\t\t\t\t\tnetworks := helpers.Capture(\"network\", \"ls\")\n\t\t\t\t\t\timages := helpers.Capture(\"images\")\n\t\t\t\t\t\tcontainers := helpers.Capture(\"ps\", \"-a\")\n\t\t\t\t\t\tassert.Assert(t, strings.Contains(volumes, data.Identifier()), volumes)\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(volumes, data.Labels().Get(\"anonIdentifier\")), volumes)\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(containers, data.Identifier()), containers)\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(networks, data.Identifier()), networks)\n\t\t\t\t\t\tassert.Assert(t, !strings.Contains(images, testutil.CommonImage), images)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"buildkit\",\n\t\t\t// FIXME: using a dedicated namespace does not work with rootful (because of buildkitd)\n\t\t\tNoParallel: true,\n\t\t\t// buildkitd is not available with docker\n\t\t\tRequire: require.All(nerdtest.Build, require.Not(nerdtest.Docker)),\n\t\t\t// FIXME: this test will happily say \"green\" even if the command actually fails to do its duty\n\t\t\t// if there is nothing in the build cache.\n\t\t\t// Ensure with setup here that we DO build something first\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"system\", \"prune\", \"-f\", \"--volumes\", \"--all\")\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn nerdtest.BuildCtlCommand(helpers, \"du\")\n\t\t\t},\n\t\t\tExpected: test.Expects(0, nil, expect.Contains(\"Total:\\t\\t0B\")),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/system/system_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "cmd/nerdctl/version.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"text/template\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc versionCommand() *cobra.Command {\n\tvar cmd = &cobra.Command{\n\t\tUse:           \"version\",\n\t\tArgs:          cobra.NoArgs,\n\t\tShort:         \"Show the nerdctl version information\",\n\t\tRunE:          versionAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc versionAction(cmd *cobra.Command, args []string) error {\n\tvar w io.Writer = os.Stdout\n\tvar tmpl *template.Template\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif format != \"\" {\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\taddress := globalOptions.Address\n\t// rootless `nerdctl version` runs in the host namespaces, so the address is different\n\tif rootlessutil.IsRootless() {\n\t\taddress, err = rootlessutil.RootlessContainredSockAddress()\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warning(\"failed to inspect the rootless containerd socket address\")\n\t\t\taddress = \"\"\n\t\t}\n\t}\n\n\tv, vErr := versionInfo(cmd, globalOptions.Namespace, address)\n\tif tmpl != nil {\n\t\tvar b bytes.Buffer\n\t\tif err := tmpl.Execute(&b, v); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := fmt.Fprintln(w, b.String()); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tfmt.Fprintln(w, \"Client:\")\n\t\tfmt.Fprintf(w, \" Version:\\t%s\\n\", v.Client.Version)\n\t\tfmt.Fprintf(w, \" OS/Arch:\\t%s/%s\\n\", v.Client.Os, v.Client.Arch)\n\t\tfmt.Fprintf(w, \" Git commit:\\t%s\\n\", v.Client.GitCommit)\n\t\tfor _, compo := range v.Client.Components {\n\t\t\tfmt.Fprintf(w, \" %s:\\n\", compo.Name)\n\t\t\tfmt.Fprintf(w, \"  Version:\\t%s\\n\", compo.Version)\n\t\t\tfor detailK, detailV := range compo.Details {\n\t\t\t\tfmt.Fprintf(w, \"  %s:\\t%s\\n\", detailK, detailV)\n\t\t\t}\n\t\t}\n\t\tif v.Server != nil {\n\t\t\tfmt.Fprintln(w)\n\t\t\tfmt.Fprintln(w, \"Server:\")\n\t\t\tfor _, compo := range v.Server.Components {\n\t\t\t\tfmt.Fprintf(w, \" %s:\\n\", compo.Name)\n\t\t\t\tfmt.Fprintf(w, \"  Version:\\t%s\\n\", compo.Version)\n\t\t\t\tfor detailK, detailV := range compo.Details {\n\t\t\t\t\tfmt.Fprintf(w, \"  %s:\\t%s\\n\", detailK, detailV)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn vErr\n}\n\n// versionInfo may return partial VersionInfo on error.\n// Address can be empty to skip inspecting the server.\nfunc versionInfo(cmd *cobra.Command, ns, address string) (dockercompat.VersionInfo, error) {\n\tv := dockercompat.VersionInfo{\n\t\tClient: infoutil.ClientVersion(),\n\t}\n\tif address == \"\" {\n\t\treturn v, nil\n\t}\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), ns, address)\n\tif err != nil {\n\t\treturn v, err\n\t}\n\tdefer cancel()\n\tv.Server, err = infoutil.ServerVersion(ctx, client)\n\treturn v, err\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n)\n\nfunc Command() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tAnnotations:   map[string]string{helpers.Category: helpers.Management},\n\t\tUse:           \"volume\",\n\t\tShort:         \"Manage volumes\",\n\t\tRunE:          helpers.UnknownSubcommandAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.AddCommand(\n\t\tlistCommand(),\n\t\tinspectCommand(),\n\t\tcreateCommand(),\n\t\tremoveCommand(),\n\t\tpruneCommand(),\n\t)\n\treturn cmd\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n)\n\nfunc createCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"create [flags] [VOLUME]\",\n\t\tShort:         \"Create a volume\",\n\t\tArgs:          cobra.MaximumNArgs(1),\n\t\tRunE:          createAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().StringArray(\"label\", nil, \"Set a label on the volume\")\n\treturn cmd\n}\n\nfunc createOptions(cmd *cobra.Command) (types.VolumeCreateOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.VolumeCreateOptions{}, err\n\t}\n\tlabels, err := cmd.Flags().GetStringArray(\"label\")\n\tif err != nil {\n\t\treturn types.VolumeCreateOptions{}, err\n\t}\n\tfor _, label := range labels {\n\t\tif label == \"\" {\n\t\t\treturn types.VolumeCreateOptions{}, fmt.Errorf(\"labels cannot be empty (%w)\", errdefs.ErrInvalidArgument)\n\t\t}\n\t}\n\n\treturn types.VolumeCreateOptions{\n\t\tGOptions: globalOptions,\n\t\tLabels:   labels,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc createAction(cmd *cobra.Command, args []string) error {\n\toptions, err := createOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvolumeName := \"\"\n\tif len(args) > 0 {\n\t\tvolumeName = args[0]\n\t}\n\t_, err = volume.Create(volumeName, options)\n\n\treturn err\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_create_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestVolumeCreate(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"arg missing should create anonymous volume\",\n\t\t\tCommand:     test.Command(\"volume\", \"create\"),\n\t\t\tExpected:    test.Expects(0, nil, expect.Match(regexp.MustCompile(\"^[a-f0-9]{64}\\n$\"))),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid identifier should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"create\", \"∞\"),\n\t\t\tExpected:    test.Expects(1, []error{errdefs.ErrInvalidArgument}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"too many args should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"create\", \"too\", \"many\"),\n\t\t\tExpected:    test.Expects(1, []error{errors.New(\"at most 1 arg\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"success\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"create\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Equals(data.Identifier() + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"success with labels\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"create\", \"--label\", \"foo1=baz1\", \"--label\", \"foo2=baz2\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Equals(data.Identifier() + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid labels should fail\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/3126\n\t\t\t\treturn helpers.Command(\"volume\", \"create\", \"--label\", \"a\", \"--label\", \"\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\t// NOTE: docker returns 125 on this\n\t\t\tExpected: test.Expects(expect.ExitCodeGenericFail, []error{errdefs.ErrInvalidArgument}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"creating already existing volume should succeed\",\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier())\n\t\t\t},\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"create\", data.Identifier())\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Equals(data.Identifier() + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n)\n\nfunc inspectCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"inspect [flags] VOLUME [VOLUME...]\",\n\t\tShort:             \"Display detailed information on one or more volumes\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              inspectAction,\n\t\tValidArgsFunction: volumeInspectShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().StringP(\"format\", \"f\", \"\", \"Format the output using the given Go template, e.g, '{{json .}}'\")\n\tcmd.Flags().BoolP(\"size\", \"s\", false, \"Display the disk usage of the volume\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\treturn cmd\n}\n\nfunc inspectOptions(cmd *cobra.Command) (types.VolumeInspectOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.VolumeInspectOptions{}, err\n\t}\n\tvolumeSize, err := cmd.Flags().GetBool(\"size\")\n\tif err != nil {\n\t\treturn types.VolumeInspectOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.VolumeInspectOptions{}, err\n\t}\n\treturn types.VolumeInspectOptions{\n\t\tGOptions: globalOptions,\n\t\tFormat:   format,\n\t\tSize:     volumeSize,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc inspectAction(cmd *cobra.Command, args []string) error {\n\toptions, err := inspectOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn volume.Inspect(cmd.Context(), args, options)\n}\n\nfunc volumeInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show volume names\n\treturn completion.VolumeNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_inspect_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc createFileWithSize(mountPoint string, size int64) error {\n\ttoken := make([]byte, size)\n\t_, _ = rand.Read(token)\n\terr := os.WriteFile(filepath.Join(mountPoint, \"test-file\"), token, 0644)\n\treturn err\n}\n\nfunc TestVolumeInspect(t *testing.T) {\n\tvar size int64 = 1028\n\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.BrokenTest(\"This test assumes that the host-side of a volume can be written into, \"+\n\t\t\"which is not always true. To be replaced by cp into the container.\",\n\t\t&test.Requirement{\n\t\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\t\tisDocker, _ := nerdtest.Docker.Check(data, helpers)\n\t\t\t\treturn !isDocker || os.Geteuid() == 0, \"docker cli needs to be run as root\"\n\t\t\t},\n\t\t})\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"first\"))\n\t\thelpers.Ensure(\"volume\", \"create\", \"--label\", \"foo=fooval\", \"--label\", \"bar=barval\", data.Identifier(\"second\"))\n\t\t// Obviously note here that if inspect code gets totally hosed, this entire suite will\n\t\t// probably fail right here on the Setup instead of actually testing something\n\t\tvol := nerdtest.InspectVolume(helpers, data.Identifier(\"first\"))\n\t\terr := createFileWithSize(vol.Mountpoint, size)\n\t\tassert.NilError(t, err, \"File creation failed\")\n\t\tdata.Labels().Set(\"vol1\", data.Identifier(\"first\"))\n\t\tdata.Labels().Set(\"vol2\", data.Identifier(\"second\"))\n\t}\n\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"first\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"second\"))\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"arg missing should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"inspect\"),\n\t\t\tExpected:    test.Expects(1, []error{errors.New(\"requires at least 1 arg\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid identifier should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"inspect\", \"∞\"),\n\t\t\tExpected:    test.Expects(1, []error{errdefs.ErrInvalidArgument}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"non existent volume should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"inspect\", \"doesnotexist\"),\n\t\t\tExpected:    test.Expects(1, []error{errdefs.ErrNotFound}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"success\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"inspect\", data.Labels().Get(\"vol1\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"vol1\")),\n\t\t\t\t\t\texpect.JSON([]native.Volume{}, func(dc []native.Volume, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, len(dc) == 1, fmt.Sprintf(\"one result, not %d\", len(dc)))\n\t\t\t\t\t\t\tassert.Assert(t, dc[0].Name == data.Labels().Get(\"vol1\"), fmt.Sprintf(\"expected name to be %q (was %q)\", data.Labels().Get(\"vol1\"), dc[0].Name))\n\t\t\t\t\t\t\tassert.Assert(t, dc[0].Labels == nil, fmt.Sprintf(\"expected labels to be nil and were %v\", dc[0].Labels))\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"inspect labels\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"inspect\", data.Labels().Get(\"vol2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"vol2\")),\n\t\t\t\t\t\texpect.JSON([]native.Volume{}, func(dc []native.Volume, t tig.T) {\n\t\t\t\t\t\t\tlabels := *dc[0].Labels\n\t\t\t\t\t\t\tassert.Assert(t, len(labels) == 2, fmt.Sprintf(\"two results, not %d\", len(labels)))\n\t\t\t\t\t\t\tassert.Assert(t, labels[\"foo\"] == \"fooval\", fmt.Sprintf(\"label foo should be fooval, not %s\", labels[\"foo\"]))\n\t\t\t\t\t\t\tassert.Assert(t, labels[\"bar\"] == \"barval\", fmt.Sprintf(\"label bar should be barval, not %s\", labels[\"bar\"]))\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"inspect size\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"inspect\", \"--size\", data.Labels().Get(\"vol1\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"vol1\")),\n\t\t\t\t\t\texpect.JSON([]native.Volume{}, func(dc []native.Volume, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, dc[0].Size == size, fmt.Sprintf(\"expected size to be %d (was %d)\", size, dc[0].Size))\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"multi success\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"inspect\", data.Labels().Get(\"vol1\"), data.Labels().Get(\"vol2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"vol1\"), data.Labels().Get(\"vol2\")),\n\t\t\t\t\t\texpect.JSON([]native.Volume{}, func(dc []native.Volume, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, len(dc) == 2, fmt.Sprintf(\"two results, not %d\", len(dc)))\n\t\t\t\t\t\t\tassert.Assert(t, dc[0].Name == data.Labels().Get(\"vol1\"), fmt.Sprintf(\"expected name to be %q (was %q)\", data.Labels().Get(\"vol1\"), dc[0].Name))\n\t\t\t\t\t\t\tassert.Assert(t, dc[1].Name == data.Labels().Get(\"vol2\"), fmt.Sprintf(\"expected name to be %q (was %q)\", data.Labels().Get(\"vol2\"), dc[1].Name))\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"part success multi\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"inspect\", \"invalid∞\", \"nonexistent\", data.Labels().Get(\"vol1\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors:   []error{errdefs.ErrNotFound, errdefs.ErrInvalidArgument},\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"vol1\")),\n\t\t\t\t\t\texpect.JSON([]native.Volume{}, func(dc []native.Volume, t tig.T) {\n\t\t\t\t\t\t\tassert.Assert(t, len(dc) == 1, fmt.Sprintf(\"one result, not %d\", len(dc)))\n\t\t\t\t\t\t\tassert.Assert(t, dc[0].Name == data.Labels().Get(\"vol1\"), fmt.Sprintf(\"expected name to be %q (was %q)\", data.Labels().Get(\"vol1\"), dc[0].Name))\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"multi failure\",\n\t\t\tCommand:     test.Command(\"volume\", \"inspect\", \"invalid∞\", \"nonexistent\"),\n\t\t\tExpected:    test.Expects(1, []error{errdefs.ErrNotFound, errdefs.ErrInvalidArgument}, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n)\n\nfunc listCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"ls\",\n\t\tAliases:       []string{\"list\"},\n\t\tShort:         \"List volumes\",\n\t\tRunE:          listAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\n\tcmd.Flags().BoolP(\"quiet\", \"q\", false, \"Only display volume names\")\n\t// Alias \"-f\" is reserved for \"--filter\"\n\tcmd.Flags().String(\"format\", \"\", \"Format the output using the given go template\")\n\tcmd.Flags().BoolP(\"size\", \"s\", false, \"Display the disk usage of volumes. Can be slow with volumes having loads of directories.\")\n\tcmd.RegisterFlagCompletionFunc(\"format\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\treturn []string{\"json\", \"table\", \"wide\"}, cobra.ShellCompDirectiveNoFileComp\n\t})\n\tcmd.Flags().StringSliceP(\"filter\", \"f\", []string{}, \"Filter matches volumes based on given conditions\")\n\treturn cmd\n}\n\nfunc listOptions(cmd *cobra.Command) (types.VolumeListOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.VolumeListOptions{}, err\n\t}\n\tquiet, err := cmd.Flags().GetBool(\"quiet\")\n\tif err != nil {\n\t\treturn types.VolumeListOptions{}, err\n\t}\n\tformat, err := cmd.Flags().GetString(\"format\")\n\tif err != nil {\n\t\treturn types.VolumeListOptions{}, err\n\t}\n\tsize, err := cmd.Flags().GetBool(\"size\")\n\tif err != nil {\n\t\treturn types.VolumeListOptions{}, err\n\t}\n\tfilters, err := cmd.Flags().GetStringSlice(\"filter\")\n\tif err != nil {\n\t\treturn types.VolumeListOptions{}, err\n\t}\n\treturn types.VolumeListOptions{\n\t\tGOptions: globalOptions,\n\t\tQuiet:    quiet,\n\t\tFormat:   format,\n\t\tSize:     size,\n\t\tFilters:  filters,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc listAction(cmd *cobra.Command, args []string) error {\n\toptions, err := listOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn volume.List(options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_list_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/tabutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestVolumeLsSize(t *testing.T) {\n\tnerdtest.Setup()\n\n\ttc := &test.Case{\n\t\tRequire: require.Not(nerdtest.Docker),\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"1\"))\n\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"2\"))\n\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"empty\"))\n\t\t\tvol1 := nerdtest.InspectVolume(helpers, data.Identifier(\"1\"))\n\t\t\tvol2 := nerdtest.InspectVolume(helpers, data.Identifier(\"2\"))\n\n\t\t\terr := createFileWithSize(vol1.Mountpoint, 102400)\n\t\t\tassert.NilError(t, err, \"File creation failed\")\n\t\t\terr = createFileWithSize(vol2.Mountpoint, 204800)\n\t\t\tassert.NilError(t, err, \"File creation failed\")\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"1\"))\n\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"2\"))\n\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"empty\"))\n\t\t},\n\t\tCommand: test.Command(\"volume\", \"ls\", \"--size\"),\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\tassert.Assert(t, len(lines) >= 4, \"expected at least 4 lines\")\n\t\t\t\t\tvolSizes := map[string]string{\n\t\t\t\t\t\tdata.Identifier(\"1\"):     \"100.0 KiB\",\n\t\t\t\t\t\tdata.Identifier(\"2\"):     \"200.0 KiB\",\n\t\t\t\t\t\tdata.Identifier(\"empty\"): \"0.0 B\",\n\t\t\t\t\t}\n\n\t\t\t\t\tvar numMatches = 0\n\t\t\t\t\tvar tab = tabutil.NewReader(\"VOLUME NAME\\tDIRECTORY\\tSIZE\")\n\t\t\t\t\tvar err = tab.ParseHeader(lines[0])\n\t\t\t\t\tassert.NilError(t, err, \"ParseHeader should not fail\\n\")\n\n\t\t\t\t\tfor _, line := range lines {\n\t\t\t\t\t\tname, _ := tab.ReadRow(line, \"VOLUME NAME\")\n\t\t\t\t\t\tsize, _ := tab.ReadRow(line, \"SIZE\")\n\t\t\t\t\t\texpectSize, ok := volSizes[name]\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassert.Assert(t, size == expectSize, fmt.Sprintf(\"expected size %s for volume %s, got %s\", expectSize, name, size))\n\t\t\t\t\t\tnumMatches++\n\t\t\t\t\t}\n\t\t\t\t\tassert.Assert(t, numMatches == len(volSizes), fmt.Sprintf(\"expected %d volumes, got: %d\", len(volSizes), numMatches))\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\ttc.Run(t)\n}\n\nfunc TestVolumeLsFilter(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.Require = nerdtest.BrokenTest(\"This test assumes that the host-side of a volume can be written into, \"+\n\t\t\"which is not always true. To be replaced by cp into the container.\",\n\t\t&test.Requirement{\n\t\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\t\tisDocker, _ := nerdtest.Docker.Check(data, helpers)\n\t\t\t\treturn !isDocker || os.Geteuid() == 0, \"docker cli needs to be run as root\"\n\t\t\t},\n\t\t})\n\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tvar vol1, vol2, vol3, vol4 = data.Identifier(\"1\"), data.Identifier(\"2\"), data.Identifier(\"3\"), data.Identifier(\"4\")\n\t\tvar label1, label2, label3, label4 = \"mylabel=label-1\", \"mylabel=label-2\", \"mylabel=label-3\", \"mylabel-group=label-4\"\n\n\t\thelpers.Ensure(\"volume\", \"create\", \"--label=\"+label1, \"--label=\"+label4, vol1)\n\t\thelpers.Ensure(\"volume\", \"create\", \"--label=\"+label2, \"--label=\"+label4, vol2)\n\t\thelpers.Ensure(\"volume\", \"create\", \"--label=\"+label3, vol3)\n\t\thelpers.Ensure(\"volume\", \"create\", vol4)\n\n\t\t// FIXME\n\t\t// This will not work with Docker rootful and Docker cli run as a user\n\t\t// We should replace it with cp inside the container\n\t\terr := createFileWithSize(nerdtest.InspectVolume(helpers, vol1).Mountpoint, 409600)\n\t\tassert.NilError(t, err, \"File creation failed\")\n\t\terr = createFileWithSize(nerdtest.InspectVolume(helpers, vol2).Mountpoint, 1024000)\n\t\tassert.NilError(t, err, \"File creation failed\")\n\t\terr = createFileWithSize(nerdtest.InspectVolume(helpers, vol3).Mountpoint, 409600)\n\t\tassert.NilError(t, err, \"File creation failed\")\n\t\terr = createFileWithSize(nerdtest.InspectVolume(helpers, vol4).Mountpoint, 1024000)\n\t\tassert.NilError(t, err, \"File creation failed\")\n\n\t\tdata.Labels().Set(\"vol1\", vol1)\n\t\tdata.Labels().Set(\"vol2\", vol2)\n\t\tdata.Labels().Set(\"vol3\", vol3)\n\t\tdata.Labels().Set(\"vol4\", vol4)\n\t\tdata.Labels().Set(\"mainlabel\", \"mylabel\")\n\t\tdata.Labels().Set(\"label1\", label1)\n\t\tdata.Labels().Set(\"label2\", label2)\n\t\tdata.Labels().Set(\"label3\", label3)\n\t\tdata.Labels().Set(\"label4\", label4)\n\n\t}\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"vol1\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"vol2\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"vol3\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"vol4\"))\n\t}\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"No filter\",\n\t\t\tCommand:     test.Command(\"volume\", \"ls\", \"--quiet\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 4, \"expected at least 4 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol3\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol4\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar numMatches = 0\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnumMatches++\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassert.Assert(t, len(volNames) == numMatches, fmt.Sprintf(\"expected %d volumes, got: %d\", len(volNames), numMatches))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving label=mainlabel\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"label=\"+data.Labels().Get(\"mainlabel\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 3, \"expected at least 3 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol3\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving label=mainlabel=label2\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"label=\"+data.Labels().Get(\"label2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 1, \"expected at least 1 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving label=mainlabel=\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"label=\"+data.Labels().Get(\"mainlabel\")+\"=\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.TrimSpace(stdout) == \"\", \"expected no result\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving label=mainlabel=label1 and label=mainlabel=label2\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"label=\"+data.Labels().Get(\"label1\"), \"--filter\", \"label=\"+data.Labels().Get(\"label2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tassert.Assert(t, strings.TrimSpace(stdout) == \"\", \"expected no result\")\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving label=mainlabel and label=grouplabel=label4\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"label=\"+data.Labels().Get(\"mainlabel\"), \"--filter\", \"label=\"+data.Labels().Get(\"label4\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 2, \"expected at least 2 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving name=volume1\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"name=\"+data.Labels().Get(\"vol1\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 1, \"expected at least 1 line\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving name=.*volume1.*\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"name=.*\"+data.Labels().Get(\"vol1\")+\".*\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 1, \"expected at least 1 line\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving name=volume1 and name=volume2\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--quiet\", \"--filter\", \"name=\"+data.Labels().Get(\"vol1\"), \"--filter\", \"name=\"+data.Labels().Get(\"vol2\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 2, \"expected at least 2 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, name := range lines {\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving size=1024000\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--size\", \"--filter\", \"size=1024000\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 3, \"expected at least 3 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol4\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar tab = tabutil.NewReader(\"VOLUME NAME\\tDIRECTORY\\tSIZE\")\n\t\t\t\t\t\tvar err = tab.ParseHeader(lines[0])\n\t\t\t\t\t\tassert.NilError(t, err, \"Tab reader failed\")\n\t\t\t\t\t\tfor _, line := range lines {\n\n\t\t\t\t\t\t\tname, _ := tab.ReadRow(line, \"VOLUME NAME\")\n\t\t\t\t\t\t\tif name == \"VOLUME NAME\" {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving size>=1024000 size<=2048000\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--size\", \"--filter\", \"size>=1024000\", \"--filter\", \"size<=2048000\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 3, \"expected at least 3 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol2\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol4\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar tab = tabutil.NewReader(\"VOLUME NAME\\tDIRECTORY\\tSIZE\")\n\t\t\t\t\t\tvar err = tab.ParseHeader(lines[0])\n\t\t\t\t\t\tassert.NilError(t, err, \"Tab reader failed\")\n\t\t\t\t\t\tfor _, line := range lines {\n\n\t\t\t\t\t\t\tname, _ := tab.ReadRow(line, \"VOLUME NAME\")\n\t\t\t\t\t\t\tif name == \"VOLUME NAME\" {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"Retrieving size>204800 size<1024000\",\n\t\t\tRequire:     require.Not(nerdtest.Docker),\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"ls\", \"--size\", \"--filter\", \"size>204800\", \"--filter\", \"size<1024000\")\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\tvar lines = strings.Split(strings.TrimSpace(stdout), \"\\n\")\n\t\t\t\t\t\tassert.Assert(t, len(lines) >= 3, \"expected at least 3 lines\")\n\t\t\t\t\t\tvolNames := map[string]struct{}{\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol1\"): {},\n\t\t\t\t\t\t\tdata.Labels().Get(\"vol3\"): {},\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvar tab = tabutil.NewReader(\"VOLUME NAME\\tDIRECTORY\\tSIZE\")\n\t\t\t\t\t\tvar err = tab.ParseHeader(lines[0])\n\t\t\t\t\t\tassert.NilError(t, err, \"Tab reader failed\")\n\t\t\t\t\t\tfor _, line := range lines {\n\n\t\t\t\t\t\t\tname, _ := tab.ReadRow(line, \"VOLUME NAME\")\n\t\t\t\t\t\t\tif name == \"VOLUME NAME\" {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_, ok := volNames[name]\n\t\t\t\t\t\t\tassert.Assert(t, ok, fmt.Sprintf(\"unexpected volume %s found\", name))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_namespace_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestVolumeNamespace(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\t// Docker does not support namespaces\n\ttestCase.Require = require.Not(nerdtest.Docker)\n\n\t// Create a volume in a different namespace\n\ttestCase.Setup = func(data test.Data, helpers test.Helpers) {\n\t\tdata.Labels().Set(\"root_namespace\", data.Identifier())\n\t\tdata.Labels().Set(\"root_volume\", data.Identifier())\n\t\thelpers.Ensure(\"--namespace\", data.Identifier(), \"volume\", \"create\", data.Identifier())\n\t}\n\n\t// Cleanup once done\n\ttestCase.Cleanup = func(data test.Data, helpers test.Helpers) {\n\t\tif data.Labels().Get(\"root_namespace\") != \"\" {\n\t\t\thelpers.Anyhow(\"--namespace\", data.Identifier(), \"volume\", \"remove\", data.Identifier())\n\t\t\thelpers.Anyhow(\"namespace\", \"remove\", data.Identifier())\n\t\t}\n\t}\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"inspect another namespace volume should fail\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"inspect\", data.Labels().Get(\"root_volume\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{\n\t\t\t\terrdefs.ErrNotFound,\n\t\t\t}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"removing another namespace volume should fail\",\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"remove\", data.Labels().Get(\"root_volume\"))\n\t\t\t},\n\t\t\tExpected: test.Expects(1, []error{\n\t\t\t\terrdefs.ErrNotFound,\n\t\t\t}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"prune should leave another namespace volume untouched\",\n\t\t\t// Make it private so that we do not interact with other tests in the main namespace\n\t\t\tRequire: nerdtest.Private,\n\t\t\tCommand: test.Command(\"volume\", \"prune\", \"-a\", \"-f\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"root_volume\")),\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\thelpers.Ensure(\"--namespace\", data.Labels().Get(\"root_namespace\"), \"volume\", \"inspect\", data.Labels().Get(\"root_volume\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"create with the same name should work, then delete it\",\n\t\t\tNoParallel:  true,\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"create\", data.Labels().Get(\"root_volume\"))\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", data.Labels().Get(\"root_volume\"))\n\t\t\t},\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\t\thelpers.Ensure(\"volume\", \"inspect\", data.Labels().Get(\"root_volume\"))\n\t\t\t\t\t\thelpers.Ensure(\"volume\", \"rm\", data.Labels().Get(\"root_volume\"))\n\t\t\t\t\t\thelpers.Ensure(\"--namespace\", data.Labels().Get(\"root_namespace\"), \"volume\", \"inspect\", data.Labels().Get(\"root_volume\"))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n)\n\nfunc pruneCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:           \"prune [flags]\",\n\t\tShort:         \"Remove all unused local volumes\",\n\t\tArgs:          cobra.NoArgs,\n\t\tRunE:          pruneAction,\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t}\n\tcmd.Flags().BoolP(\"all\", \"a\", false, \"Remove all unused volumes, not just anonymous ones\")\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"Do not prompt for confirmation\")\n\treturn cmd\n}\n\nfunc pruneOptions(cmd *cobra.Command) (types.VolumePruneOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.VolumePruneOptions{}, err\n\t}\n\n\tall, err := cmd.Flags().GetBool(\"all\")\n\tif err != nil {\n\t\treturn types.VolumePruneOptions{}, err\n\t}\n\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn types.VolumePruneOptions{}, err\n\t}\n\n\toptions := types.VolumePruneOptions{\n\t\tGOptions: globalOptions,\n\t\tAll:      all,\n\t\tForce:    force,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}\n\treturn options, nil\n}\n\nfunc pruneAction(cmd *cobra.Command, _ []string) error {\n\toptions, err := pruneOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !options.Force {\n\t\tvar confirm string\n\t\tmsg := \"This will remove all local volumes not used by at least one container.\"\n\t\tmsg += \"\\nAre you sure you want to continue? [y/N] \"\n\t\tfmt.Fprintf(options.Stdout, \"WARNING! %s\", msg)\n\t\tfmt.Fscanf(cmd.InOrStdin(), \"%s\", &confirm)\n\n\t\tif strings.ToLower(confirm) != \"y\" {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn volume.Prune(ctx, client, options)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_prune_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\nfunc TestVolumePrune(t *testing.T) {\n\tvar setup = func(data test.Data, helpers test.Helpers) {\n\t\tanonIDBusy := strings.TrimSpace(helpers.Capture(\"volume\", \"create\"))\n\t\tanonIDDangling := strings.TrimSpace(helpers.Capture(\"volume\", \"create\"))\n\n\t\tnamedBusy := data.Identifier(\"busy\")\n\t\tnamedDangling := data.Identifier(\"free\")\n\n\t\thelpers.Ensure(\"volume\", \"create\", namedBusy)\n\t\thelpers.Ensure(\"volume\", \"create\", namedDangling)\n\t\thelpers.Ensure(\"run\", \"--name\", data.Identifier(),\n\t\t\t\"-v\", namedBusy+\":/namedbusyvolume\",\n\t\t\t\"-v\", anonIDBusy+\":/anonbusyvolume\", testutil.CommonImage)\n\n\t\tdata.Labels().Set(\"anonIDBusy\", anonIDBusy)\n\t\tdata.Labels().Set(\"anonIDDangling\", anonIDDangling)\n\t\tdata.Labels().Set(\"namedBusy\", namedBusy)\n\t\tdata.Labels().Set(\"namedDangling\", namedDangling)\n\t}\n\n\tvar cleanup = func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"anonIDBusy\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"anonIDDangling\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"namedBusy\"))\n\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"namedDangling\"))\n\t}\n\n\ttestCase := nerdtest.Setup()\n\t// This set must be marked as private, since we cannot prune without interacting with other tests.\n\ttestCase.Require = nerdtest.Private\n\t// Furthermore, these two subtests cannot be run in parallel\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"prune anonymous only\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup:       setup,\n\t\t\tCleanup:     cleanup,\n\t\t\tCommand:     test.Command(\"volume\", \"prune\", \"-f\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"anonIDDangling\")),\n\t\t\t\t\t\texpect.DoesNotContain(\n\t\t\t\t\t\t\tdata.Labels().Get(\"anonIDBusy\"),\n\t\t\t\t\t\t\tdata.Labels().Get(\"namedBusy\"),\n\t\t\t\t\t\t\tdata.Labels().Get(\"namedDangling\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\thelpers.Ensure(\"volume\", \"inspect\", data.Labels().Get(\"anonIDBusy\"))\n\t\t\t\t\t\t\thelpers.Fail(\"volume\", \"inspect\", data.Labels().Get(\"anonIDDangling\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"volume\", \"inspect\", data.Labels().Get(\"namedBusy\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"volume\", \"inspect\", data.Labels().Get(\"namedDangling\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"prune all\",\n\t\t\tNoParallel:  true,\n\t\t\tSetup:       setup,\n\t\t\tCleanup:     cleanup,\n\t\t\tCommand:     test.Command(\"volume\", \"prune\", \"-f\", \"--all\"),\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.All(\n\t\t\t\t\t\texpect.DoesNotContain(data.Labels().Get(\"anonIDBusy\"), data.Labels().Get(\"namedBusy\")),\n\t\t\t\t\t\texpect.Contains(data.Labels().Get(\"anonIDDangling\"), data.Labels().Get(\"namedDangling\")),\n\t\t\t\t\t\tfunc(stdout string, t tig.T) {\n\t\t\t\t\t\t\thelpers.Ensure(\"volume\", \"inspect\", data.Labels().Get(\"anonIDBusy\"))\n\t\t\t\t\t\t\thelpers.Fail(\"volume\", \"inspect\", data.Labels().Get(\"anonIDDangling\"))\n\t\t\t\t\t\t\thelpers.Ensure(\"volume\", \"inspect\", data.Labels().Get(\"namedBusy\"))\n\t\t\t\t\t\t\thelpers.Fail(\"volume\", \"inspect\", data.Labels().Get(\"namedDangling\"))\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion\"\n\t\"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n)\n\nfunc removeCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"rm [flags] VOLUME [VOLUME...]\",\n\t\tAliases:           []string{\"remove\"},\n\t\tShort:             \"Remove one or more volumes\",\n\t\tLong:              \"NOTE: You cannot remove a volume that is in use by a container.\",\n\t\tArgs:              cobra.MinimumNArgs(1),\n\t\tRunE:              removeAction,\n\t\tValidArgsFunction: removeShellComplete,\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t}\n\tcmd.Flags().BoolP(\"force\", \"f\", false, \"(unimplemented yet)\")\n\treturn cmd\n}\n\nfunc removeOptions(cmd *cobra.Command) (types.VolumeRemoveOptions, error) {\n\tglobalOptions, err := helpers.ProcessRootCmdFlags(cmd)\n\tif err != nil {\n\t\treturn types.VolumeRemoveOptions{}, err\n\t}\n\tforce, err := cmd.Flags().GetBool(\"force\")\n\tif err != nil {\n\t\treturn types.VolumeRemoveOptions{}, err\n\t}\n\treturn types.VolumeRemoveOptions{\n\t\tGOptions: globalOptions,\n\t\tForce:    force,\n\t\tStdout:   cmd.OutOrStdout(),\n\t}, nil\n}\n\nfunc removeAction(cmd *cobra.Command, args []string) error {\n\toptions, err := removeOptions(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cancel()\n\n\treturn volume.Remove(ctx, client, args, options)\n}\n\nfunc removeShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// show volume names\n\treturn completion.VolumeNames(cmd)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_remove_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n)\n\n// TestVolumeRemove does test a large variety of volume remove situations, albeit none of them being\n// hard filesystem errors.\n// Behavior in such cases is largely unspecified, as there is no easy way to compare with Docker.\n// Anyhow, borked filesystem conditions is not something we should be expected to deal with in a smart way.\nfunc TestVolumeRemove(t *testing.T) {\n\ttestCase := nerdtest.Setup()\n\n\ttestCase.SubTests = []*test.Case{\n\t\t{\n\t\t\tDescription: \"arg missing should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"rm\"),\n\t\t\tExpected:    test.Expects(1, []error{errors.New(\"requires at least 1 arg\")}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"invalid identifier should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"rm\", \"∞\"),\n\t\t\tExpected:    test.Expects(1, []error{errdefs.ErrInvalidArgument}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"non existent volume should fail\",\n\t\t\tCommand:     test.Command(\"volume\", \"rm\", \"doesnotexist\"),\n\t\t\tExpected:    test.Expects(1, []error{errdefs.ErrNotFound}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"busy volume should fail\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"run\", \"-v\", fmt.Sprintf(\"%s:/volume\", data.Identifier()),\n\t\t\t\t\t\"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", data.Identifier())\n\t\t\t},\n\n\t\t\tExpected: test.Expects(1, []error{errdefs.ErrFailedPrecondition}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"busy anonymous volume should fail\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"run\", \"-v\", \"/volume\", \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\t// Inspect the container and find the anonymous volume id\n\t\t\t\tinspect := nerdtest.InspectContainer(helpers, data.Identifier())\n\t\t\t\tvar anonName string\n\t\t\t\tfor _, v := range inspect.Mounts {\n\t\t\t\t\tif v.Destination == \"/volume\" {\n\t\t\t\t\t\tanonName = v.Name\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.Assert(t, anonName != \"\", \"Failed to find anonymous volume id\", inspect)\n\t\t\t\tdata.Labels().Set(\"anonName\", anonName)\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Labels().Get(\"anonName\"))\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\t// Try to remove that anon volume\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", data.Labels().Get(\"anonName\"))\n\t\t\t},\n\n\t\t\tExpected: test.Expects(1, []error{errdefs.ErrFailedPrecondition}, nil),\n\t\t},\n\t\t{\n\t\t\tDescription: \"freed volume should succeed\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"run\", \"-v\", fmt.Sprintf(\"%s:/volume\", data.Identifier()), \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t\thelpers.Ensure(\"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", data.Identifier())\n\t\t\t},\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Equals(data.Identifier() + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"dangling volume should succeed\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier())\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", data.Identifier())\n\t\t\t},\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Equals(data.Identifier() + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"part success multi-remove\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier())\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"busy\"))\n\t\t\t\thelpers.Ensure(\"run\", \"-v\", fmt.Sprintf(\"%s:/volume\", data.Identifier(\"busy\")), \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"busy\"))\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", \"invalid∞\", \"nonexistent\", data.Identifier(\"busy\"), data.Identifier())\n\t\t\t},\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tExitCode: 1,\n\t\t\t\t\tErrors: []error{\n\t\t\t\t\t\terrdefs.ErrNotFound,\n\t\t\t\t\t\terrdefs.ErrFailedPrecondition,\n\t\t\t\t\t\terrdefs.ErrInvalidArgument,\n\t\t\t\t\t},\n\t\t\t\t\tOutput: expect.Equals(data.Identifier() + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"success multi-remove\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"1\"))\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"2\"))\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"1\"), data.Identifier(\"2\"))\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", data.Identifier(\"1\"), data.Identifier(\"2\"))\n\t\t\t},\n\n\t\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\t\treturn &test.Expected{\n\t\t\t\t\tOutput: expect.Equals(data.Identifier(\"1\") + \"\\n\" + data.Identifier(\"2\") + \"\\n\"),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tDescription: \"failing multi-remove\",\n\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Ensure(\"volume\", \"create\", data.Identifier(\"busy\"))\n\t\t\t\thelpers.Ensure(\"run\", \"-v\", fmt.Sprintf(\"%s:/volume\", data.Identifier(\"busy\")), \"--name\", data.Identifier(), testutil.CommonImage)\n\t\t\t},\n\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\thelpers.Anyhow(\"rm\", \"-f\", data.Identifier())\n\t\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"-f\", data.Identifier(\"busy\"))\n\t\t\t},\n\n\t\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\t\treturn helpers.Command(\"volume\", \"rm\", \"invalid∞\", \"nonexistent\", data.Identifier(\"busy\"))\n\t\t\t},\n\n\t\t\tExpected: test.Expects(1, []error{\n\t\t\t\terrdefs.ErrNotFound,\n\t\t\t\terrdefs.ErrFailedPrecondition,\n\t\t\t\terrdefs.ErrInvalidArgument,\n\t\t\t}, nil),\n\t\t},\n\t}\n\n\ttestCase.Run(t)\n}\n"
  },
  {
    "path": "cmd/nerdctl/volume/volume_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestutil.M(m)\n}\n"
  },
  {
    "path": "docs/build.md",
    "content": "# Setting up `nerdctl build` with BuildKit\n\n`nerdctl build` (and `nerdctl compose build`) relies on [BuildKit](https://github.com/moby/buildkit).\nTo use it, you need to set up BuildKit.\n\nBuildKit has 2 types of backends.\n\n- **containerd worker**: BuildKit relies on containerd to manage containers and images, etc. containerd needs to be up-and-running on the host.\n- **OCI worker**: BuildKit manages containers and images, etc. containerd isn't needed. This worker relies on runc for container execution.\n\nYou need to set up BuildKit with either of the above workers.\n\nNote that OCI worker cannot access base images (`FROM` images in Dockerfiles) managed by containerd.\nThus you cannot let `nerdctl build` use containerd-managed images as the base image.\nThey include images previously built using `nerdctl build`.\n\nFor example, the following build `bar` fails with OCI worker because it tries to use the previously built and containerd-managed image `foo`.\n\n```console\n$ mkdir -p /tmp/ctx && cat <<EOF > /tmp/ctx/Dockerfile\nFROM ghcr.io/stargz-containers/ubuntu:20.04-org\nRUN echo hello\nEOF\n$ nerdctl build -t foo /tmp/ctx\n$ cat <<EOF > /tmp/ctx/Dockerfile\nFROM foo\nRUN echo bar\nEOF\n$ nerdctl build -t bar /tmp/ctx\n```\n\nThis limitation can be avoided using containerd worker as mentioned later.\n\n## Setting up BuildKit with containerd worker\n\n### Rootless\n\n| :zap: Requirement | nerdctl >= 0.18, BuildKit >= 0.10 |\n|-------------------|-----------------------------------|\n\n```\n$ CONTAINERD_NAMESPACE=default containerd-rootless-setuptool.sh install-buildkit-containerd\n```\n\n`containerd-rootless-setuptool.sh` is aware of `CONTAINERD_NAMESPACE` and `CONTAINERD_SNAPSHOTTER` envvars.\nIt installs buildkitd to the specified containerd namespace.\nThis allows BuildKit using containerd-managed images in that namespace as the base image.\nNote that BuildKit can't use images in other namespaces as of now.\n\nIf `CONTAINERD_NAMESPACE` envvar is not specified, this script configures buildkitd to use \"buildkit\" namespace (not \"default\" namespace).\n\nYou can install an additional buildkitd process in a different namespace by executing this script with specifying the namespace with `CONTAINERD_NAMESPACE`.\n\nBuildKit will expose the socket at `$XDG_RUNTIME_DIR/buildkit-$CONTAINERD_NAMESPACE/buildkitd.sock` if `CONTAINERD_NAMESPACE` is specified.\nIf `CONTAINERD_NAMESPACE` is not specified, that location will be `$XDG_RUNTIME_DIR/buildkit/buildkitd.sock`.\n\n### Rootful\n\n```\n$ sudo systemctl enable --now buildkit\n```\n\nThen add the following configuration to `/etc/buildkit/buildkitd.toml` to enable containerd worker.\n\n```toml\n[worker.oci]\n  enabled = false\n\n[worker.containerd]\n  enabled = true\n  # namespace should be \"k8s.io\" for Kubernetes (including Rancher Desktop)\n  namespace = \"default\"\n```\n\n## Setting up BuildKit with OCI worker\n\n### Rootless\n\n```\n$ containerd-rootless-setuptool.sh install-buildkit\n```\n\nAs mentioned in the above, BuildKit with this configuration cannot use images managed by containerd.\nThey include images previously built with `nerdctl build`.\n\nBuildKit will expose the socket at `$XDG_RUNTIME_DIR/buildkit/buildkitd.sock`.\n\n### rootful\n\n```\n$ sudo systemctl enable --now buildkit\n```\n\n## Which BuildKit socket will nerdctl use?\n\nYou can specify BuildKit address for `nerdctl build` using `--buildkit-host` flag or `BUILDKIT_HOST` envvar.\nWhen BuildKit address isn't specified, nerdctl tries some default BuildKit addresses the following order and uses the first available one.\n\n- `<runtime directory>/buildkit-<current namespace>/buildkitd.sock`\n- `<runtime directory>/buildkit-default/buildkitd.sock`\n- `<runtime directory>/buildkit/buildkitd.sock`\n\nFor example, if you run rootless nerdctl with `test` containerd namespace, it tries to use `$XDG_RUNTIME_DIR/buildkit-test/buildkitd.sock` by default then try to fall back to `$XDG_RUNTIME_DIR/buildkit-default/buildkitd.sock` and `$XDG_RUNTIME_DIR/buildkit/buildkitd.sock`\n"
  },
  {
    "path": "docs/builder-debug.md",
    "content": "# Interactive debugging of Dockerfile (Experimental)\n\nnerdctl supports interactive debugging of Dockerfile as `nerdctl builder debug`.\n\n```\n$ nerdctl builder debug /path/to/context\n```\n\nThis feature leverages [buildg](https://github.com/ktock/buildg), interactive debugger of Dockerfile.\nFor command reference, please refer to the [Command reference doc in buildg repo](https://github.com/ktock/buildg#command-reference).\n\n:warning: This command currently doesn't use the host's `buildkitd` daemon but uses the patched version of BuildKit provided by buildg. This should be fixed to use the host's `buildkitd` in the future.\n\n## Example\n\nExample Dockerfile:\n\n```Dockerfile\nFROM busybox AS build1\nRUN echo a > /a\nRUN echo b > /b\nRUN echo c > /c\n```\n\nExample debugging:\n\n```console\n$ nerdctl builder debug --image=ubuntu:22.04 /tmp/ctx/\nWARN[2022-05-17T10:15:48Z] using host network as the default#1 [internal] load .dockerignore\n#1 transferring context: 2B done\n#1 DONE 0.1s\n\n#2 [internal] load build definition from Dockerfile\n#2 transferring dockerfile: 108B done\n#2 DONE 0.1s\n\n#3 [internal] load metadata for docker.io/library/busybox:latest\nINFO[2022-05-17T10:15:51Z] debug session started. type \"help\" for command reference.\nFilename: \"Dockerfile\"\n =>   1| FROM busybox AS build1\n      2| RUN echo a > /a\n      3| RUN echo b > /b\n      4| RUN echo c > /c\n(buildg) break 3\n(buildg) breakpoints\n[0]: line: Dockerfile:3\n[on-fail]: breaks on fail\n(buildg) continue\n#3 DONE 3.1s\n\n#4 [1/4] FROM docker.io/library/busybox@sha256:d2b53584f580310186df7a2055ce3ff83cc0df6caacf1e3489bff8cf5d0af5d8\n#4 resolve docker.io/library/busybox@sha256:d2b53584f580310186df7a2055ce3ff83cc0df6caacf1e3489bff8cf5d0af5d8 0.0s done\n#4 sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 0B / 772.81kB 0.2s\n#4 sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 0B / 772.81kB 5.3s\n#4 sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 0B / 772.81kB 10.4s\n#4 sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 772.81kB / 772.81kB 11.4s done\n#4 extracting sha256:50e8d59317eb665383b2ef4d9434aeaa394dcd6f54b96bb7810fdde583e9c2d1 0.1s done\n#4 DONE 20.2s\n\n#5 [2/4] RUN echo a > /a\n#5 DONE 0.1s\nBreakpoint[0]: reached line: Dockerfile:3\nFilename: \"Dockerfile\"\n      1| FROM busybox AS build1\n      2| RUN echo a > /a\n*=>   3| RUN echo b > /b\n      4| RUN echo c > /c\n(buildg) exec --image sh\n# ls /debugroot/\na  b  bin  dev\tetc  home  proc  root  tmp  usr  var\n# cat /debugroot/a /debugroot/b\na\nb\n#\n(buildg) quit\n```\n"
  },
  {
    "path": "docs/cni.md",
    "content": "# Using CNI with nerdctl\n\nnerdctl uses CNI plugins for its container network, you can set network by\neither `--network` or `--net` option.\n\n## Basic networks\n\nnerdctl support some basic types of CNI plugins without any configuration\nneeded(you should have CNI plugin be installed), for Linux systems the basic\nCNI plugin types are `bridge`, `portmap`, `firewall`, `tuning`, for Windows\nsystem, the supported CNI plugin types are `nat` only.\n\nThe default network `bridge` for Linux and `nat` for Windows if you\ndon't set any network options.\n\nConfiguration of the default network `bridge` of Linux:\n\n```json\n{\n  \"cniVersion\": \"1.0.0\",\n  \"name\": \"bridge\",\n  \"plugins\": [\n    {\n      \"type\": \"bridge\",\n      \"bridge\": \"nerdctl0\",\n      \"isGateway\": true,\n      \"ipMasq\": true,\n      \"hairpinMode\": true,\n      \"ipam\": {\n        \"type\": \"host-local\",\n        \"routes\": [{ \"dst\": \"0.0.0.0/0\" }],\n        \"ranges\": [\n          [\n            {\n              \"subnet\": \"10.4.0.0/24\",\n              \"gateway\": \"10.4.0.1\"\n            }\n          ]\n        ]\n      }\n    },\n    {\n      \"type\": \"portmap\",\n      \"capabilities\": {\n        \"portMappings\": true\n      }\n    },\n    {\n      \"type\": \"firewall\",\n      \"ingressPolicy\": \"same-bridge\"\n    },\n    {\n      \"type\": \"tuning\"\n    }\n  ]\n}\n```\n\n## Bridge isolation\n\nnerdctl >= 0.18 sets the `ingressPolicy` to `same-bridge` when `firewall` plugin >= 1.1.0 is installed.\nThis `ingressPolicy` replaces the CNI `isolation` plugin used in nerdctl <= 0.17.\n\nWhen `firewall` plugin >= 1.1.0 is not found, nerdctl does not enable the bridge isolation.\nThis means a container in `--net=foo` can connect to a container in `--net=bar`.\n\n## macvlan/IPvlan networks\n\nnerdctl also support macvlan and IPvlan network driver.\n\nTo create a `macvlan` network which bridges with a given physical network interface, use `--driver macvlan` with\n`nerdctl network create` command.\n\n```\n# nerdctl network create mac0 --driver macvlan \\\n  --subnet=192.168.5.0/24\n  --gateway=192.168.5.2\n  -o parent=eth0\n```\n\nYou can specify the `parent`, which is the interface the traffic will physically go through on the host,\ndefaults to default route interface.\n\nAnd the `subnet` should be under the same network as the network interface,\nan easier way is to use DHCP to assign the IP:\n\n```\n# nerdctl network create mac0 --driver macvlan --ipam-driver=dhcp\n```\n\nUsing `--driver ipvlan` can create `ipvlan` network, the default mode for IPvlan is `l2`.\n\n## DHCP host-name and other DHCP options\n\nNerdctl automatically sets the DHCP host-name option to the hostname value of the container.\n\nFurthermore, on network creation, nerdctl supports the ability to set other DHCP options through `--ipam-options`.\n\nCurrently, the following options are supported by the DHCP plugin:\n```\ndhcp-client-identifier\nsubnet-mask\nrouters\nuser-class\nvendor-class-identifier\n```\n\nFor example:\n```\n# nerdctl network create --driver macvlan \\\n    --ipam-driver dhcp \\\n    --ipam-opt 'vendor-class-identifier={\"type\": \"provide\", \"value\": \"Hey! Its me!\"}' \\\n    my-dhcp-net\n```\n\n## Custom networks\n\nYou can also customize your CNI network by providing configuration files.\n\nWhen rootful, the expected root location is `/etc/cni/net.d`.\nFor rootless, the expected root location is `~/.config/cni/net.d/`\n\nConfiguration files (like `10-mynet.conf`) can be placed either in the root location,\nor under a subfolder.\nIf in the root location, this network will be available to all nerdctl namespaces.\nIf placed in a subfolder, it will be available only to the identically named namespace.\n\nFor example, you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)\nfor `bridge` network:\n\n```json\n{\n  \"cniVersion\": \"1.0.0\",\n  \"name\": \"mynet\",\n  \"type\": \"bridge\",\n  \"bridge\": \"cni0\",\n  \"isGateway\": true,\n  \"ipMasq\": true,\n  \"ipam\": {\n    \"type\": \"host-local\",\n    \"subnet\": \"172.19.0.0/24\",\n    \"routes\": [\n      { \"dst\": \"0.0.0.0/0\" }\n    ]\n  }\n}\n```\n\nThis will configure a new CNI network with the name `mynet`, and you can use\nthis network to create a container in any namespace:\n\n```console\n# nerdctl run -it --net mynet --rm alpine ip addr show\n1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000\n    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n    inet 127.0.0.1/8 scope host lo\n       valid_lft forever preferred_lft forever\n    inet6 ::1/128 scope host\n       valid_lft forever preferred_lft forever\n3: eth0@if6120: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP\n    link/ether 5e:5b:3f:0c:36:56 brd ff:ff:ff:ff:ff:ff\n    inet 172.19.0.51/24 brd 172.19.0.255 scope global eth0\n       valid_lft forever preferred_lft forever\n    inet6 fe80::5c5b:3fff:fe0c:3656/64 scope link tentative\n       valid_lft forever preferred_lft forever\n```\n"
  },
  {
    "path": "docs/command-reference.md",
    "content": "# Command reference\n\n:whale:     = Docker compatible\n\n:nerd_face: = nerdctl specific\n\n> [!NOTE]\n> - Unlisted `docker` CLI flags are unimplemented yet in `nerdctl` CLI.\n>   It does not necessarily mean that the corresponding features are missing in containerd.\n> - Some commands and flags are only available on Linux.\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Container management](#container-management)\n  - [:whale: nerdctl run](#whale-nerdctl-run)\n  - [:whale: nerdctl exec](#whale-nerdctl-exec)\n  - [:whale: nerdctl create](#whale-nerdctl-create)\n  - [:whale: nerdctl cp](#whale-nerdctl-cp)\n  - [:whale: nerdctl ps](#whale-nerdctl-ps)\n  - [:whale: nerdctl inspect](#whale-nerdctl-inspect)\n  - [:whale: nerdctl logs](#whale-nerdctl-logs)\n  - [:whale: nerdctl port](#whale-nerdctl-port)\n  - [:whale: nerdctl rm](#whale-nerdctl-rm)\n  - [:whale: nerdctl stop](#whale-nerdctl-stop)\n  - [:whale: nerdctl start](#whale-nerdctl-start)\n  - [:whale: nerdctl restart](#whale-nerdctl-restart)\n  - [:whale: nerdctl update](#whale-nerdctl-update)\n  - [:whale: nerdctl wait](#whale-nerdctl-wait)\n  - [:whale: nerdctl kill](#whale-nerdctl-kill)\n  - [:whale: nerdctl pause](#whale-nerdctl-pause)\n  - [:whale: nerdctl unpause](#whale-nerdctl-unpause)\n  - [:whale: nerdctl rename](#whale-nerdctl-rename)\n  - [:whale: nerdctl attach](#whale-nerdctl-attach)\n  - [:whale: nerdctl container prune](#whale-nerdctl-container-prune)\n  - [:whale: nerdctl diff](#whale-nerdctl-diff)\n  - [:whale: nerdctl export](#whale-nerdctl-export)\n- [Build](#build)\n  - [:whale: nerdctl build](#whale-nerdctl-build)\n  - [:whale: nerdctl commit](#whale-nerdctl-commit)\n- [Image management](#image-management)\n  - [:whale: nerdctl images](#whale-nerdctl-images)\n  - [:whale: nerdctl pull](#whale-nerdctl-pull)\n  - [:whale: nerdctl push](#whale-nerdctl-push)\n  - [:whale: nerdctl load](#whale-nerdctl-load)\n  - [:whale: nerdctl save](#whale-nerdctl-save)\n  - [:whale: nerdctl import](#whale-nerdctl-import)\n  - [:whale: nerdctl tag](#whale-nerdctl-tag)\n  - [:whale: nerdctl rmi](#whale-nerdctl-rmi)\n  - [:whale: nerdctl image inspect](#whale-nerdctl-image-inspect)\n  - [:whale: nerdctl image history](#whale-nerdctl-image-history)\n  - [:whale: nerdctl image prune](#whale-nerdctl-image-prune)\n  - [:nerd_face: nerdctl image convert](#nerd_face-nerdctl-image-convert)\n  - [:nerd_face: nerdctl image encrypt](#nerd_face-nerdctl-image-encrypt)\n  - [:nerd_face: nerdctl image decrypt](#nerd_face-nerdctl-image-decrypt)\n- [Checkpoint management](#checkpoint-management)\n  - [:whale: nerdctl checkpoint create](#whale-nerdctl-checkpoint-create)\n  - [:whale: nerdctl checkpoint list](#whale-nerdctl-checkpoint-list)\n  - [:whale: nerdctl checkpoint remove](#whale-nerdctl-checkpoint-remove)\n- [Manifest management](#manifest-management)\n  - [:whale: nerdctl manifest annotate](#whale-nerdctl-manifest-annotate)\n  - [:whale: nerdctl manifest create](#whale-nerdctl-manifest-create)\n  - [:whale: nerdctl manifest inspect](#whale-nerdctl-manifest-inspect)\n  - [:whale: nerdctl manifest push](#whale-nerdctl-manifest-push)\n  - [:whale: nerdctl manifest rm](#whale-nerdctl-manifest-rm)\n- [Registry](#registry)\n  - [:whale: nerdctl login](#whale-nerdctl-login)\n  - [:whale: nerdctl logout](#whale-nerdctl-logout)\n  - [:whale: nerdctl search](#whale-nerdctl-search)\n- [Network management](#network-management)\n  - [:whale: nerdctl network create](#whale-nerdctl-network-create)\n  - [:whale: nerdctl network ls](#whale-nerdctl-network-ls)\n  - [:whale: nerdctl network inspect](#whale-nerdctl-network-inspect)\n  - [:whale: nerdctl network rm](#whale-nerdctl-network-rm)\n  - [:whale: nerdctl network prune](#whale-nerdctl-network-prune)\n- [Volume management](#volume-management)\n  - [:whale: nerdctl volume create](#whale-nerdctl-volume-create)\n  - [:whale: nerdctl volume ls](#whale-nerdctl-volume-ls)\n  - [:whale: nerdctl volume inspect](#whale-nerdctl-volume-inspect)\n  - [:whale: nerdctl volume rm](#whale-nerdctl-volume-rm)\n  - [:whale: nerdctl volume prune](#whale-nerdctl-volume-prune)\n- [Namespace management](#namespace-management)\n  - [:nerd_face: nerdctl namespace create](#nerd_face-nerdctl-namespace-create)\n  - [:nerd_face: nerdctl namespace inspect](#nerd_face-nerdctl-namespace-inspect)\n  - [:nerd_face: nerdctl namespace ls](#nerd_face-nerdctl-namespace-ls)\n  - [:nerd_face: nerdctl namespace remove](#nerd_face-nerdctl-namespace-remove)\n  - [:nerd_face: nerdctl namespace update](#nerd_face-nerdctl-namespace-update)\n- [AppArmor profile management](#apparmor-profile-management)\n  - [:nerd_face: nerdctl apparmor inspect](#nerd_face-nerdctl-apparmor-inspect)\n  - [:nerd_face: nerdctl apparmor load](#nerd_face-nerdctl-apparmor-load)\n  - [:nerd_face: nerdctl apparmor ls](#nerd_face-nerdctl-apparmor-ls)\n  - [:nerd_face: nerdctl apparmor unload](#nerd_face-nerdctl-apparmor-unload)\n- [Builder management](#builder-management)\n  - [:whale: nerdctl builder prune](#whale-nerdctl-builder-prune)\n  - [:nerd_face: nerdctl builder debug](#nerd_face-nerdctl-builder-debug)\n- [System](#system)\n  - [:whale: nerdctl events](#whale-nerdctl-events)\n  - [:whale: nerdctl info](#whale-nerdctl-info)\n  - [:whale: nerdctl version](#whale-nerdctl-version)\n  - [:whale: nerdctl system prune](#whale-nerdctl-system-prune)\n- [Stats](#stats)\n  - [:whale: nerdctl stats](#whale-nerdctl-stats)\n  - [:whale: nerdctl top](#whale-nerdctl-top)\n- [Shell completion](#shell-completion)\n  - [:nerd_face: nerdctl completion bash](#nerd_face-nerdctl-completion-bash)\n  - [:nerd_face: nerdctl completion zsh](#nerd_face-nerdctl-completion-zsh)\n  - [:nerd_face: nerdctl completion fish](#nerd_face-nerdctl-completion-fish)\n  - [:nerd_face: nerdctl completion powershell](#nerd_face-nerdctl-completion-powershell)\n- [Compose](#compose)\n  - [:whale: nerdctl compose](#whale-nerdctl-compose)\n  - [:whale: nerdctl compose up](#whale-nerdctl-compose-up)\n  - [:whale: nerdctl compose logs](#whale-nerdctl-compose-logs)\n  - [:whale: nerdctl compose build](#whale-nerdctl-compose-build)\n  - [:whale: nerdctl compose create](#whale-nerdctl-compose-create)\n  - [:whale: nerdctl compose exec](#whale-nerdctl-compose-exec)\n  - [:whale: nerdctl compose down](#whale-nerdctl-compose-down)\n  - [:whale: nerdctl compose images](#whale-nerdctl-compose-images)\n  - [:whale: nerdctl compose start](#whale-nerdctl-compose-start)\n  - [:whale: nerdctl compose stop](#whale-nerdctl-compose-stop)\n  - [:whale: nerdctl compose port](#whale-nerdctl-compose-port)\n  - [:whale: nerdctl compose ps](#whale-nerdctl-compose-ps)\n  - [:whale: nerdctl compose pull](#whale-nerdctl-compose-pull)\n  - [:whale: nerdctl compose push](#whale-nerdctl-compose-push)\n  - [:whale: nerdctl compose pause](#whale-nerdctl-compose-pause)\n  - [:whale: nerdctl compose unpause](#whale-nerdctl-compose-unpause)\n  - [:whale: nerdctl compose config](#whale-nerdctl-compose-config)\n  - [:whale: nerdctl compose cp](#whale-nerdctl-compose-cp)\n  - [:whale: nerdctl compose kill](#whale-nerdctl-compose-kill)\n  - [:whale: nerdctl compose restart](#whale-nerdctl-compose-restart)\n  - [:whale: nerdctl compose rm](#whale-nerdctl-compose-rm)\n  - [:whale: nerdctl compose run](#whale-nerdctl-compose-run)\n  - [:whale: nerdctl compose top](#whale-nerdctl-compose-top)\n  - [:whale: nerdctl compose version](#whale-nerdctl-compose-version)\n- [IPFS management](#ipfs-management)\n  - [:nerd_face: nerdctl ipfs registry serve](#nerd_face-nerdctl-ipfs-registry-serve)\n- [Global flags](#global-flags)\n- [Unimplemented Docker commands](#unimplemented-docker-commands)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Container management\n\n### :whale: nerdctl run\n\nRun a command in a new container.\n\nUsage: `nerdctl run [OPTIONS] IMAGE [COMMAND] [ARG...]`\n\n:nerd_face: `ipfs://` prefix can be used for `IMAGE` to pull it from IPFS. See [`ipfs.md`](./ipfs.md) for details.\n:nerd_face: `oci-archive://` prefix can be used for `IMAGE` to specify a local file system path to an OCI formatted tarball.\n\nBasic flags:\n\n- :whale: `-a, --attach`: Attach STDIN, STDOUT, or STDERR\n- :whale: `-i, --interactive`: Keep STDIN open even if not attached\"\n- :whale: `-t, --tty`: Allocate a pseudo-TTY\n  - :warning: WIP: currently `-t` conflicts with `-d`\n- :whale: `-sig-proxy`: Proxy received signals to the process (default true)\n- :whale: `-d, --detach`: Run container in background and print container ID\n- :whale: `--restart=(no|always|on-failure|unless-stopped)`: Restart policy to apply when a container exits\n  - Default: \"no\"\n  - always: Always restart the container if it stops.\n  - on-failure[:max-retries]: Restart only if the container exits with a non-zero exit status. Optionally, limit the number of times attempts to restart the container using the :max-retries option.\n  - unless-stopped: Always restart the container unless it is stopped.\n- :whale: `--rm`: Automatically remove the container when it exits\n- :whale: `--pull=(always|missing|never)`: Pull image before running\n  - Default: \"missing\"\n- :whale: `-q, --quiet`: Suppress the pull output\n- :whale: `--pid=(host|container:<container>)`: PID namespace to use\n- :whale: `--uts=(host)` : UTS namespace to use\n- :whale: `--stop-signal`: Signal to stop a container (default \"SIGTERM\")\n- :whale: `--stop-timeout`: Timeout (in seconds) to stop a container\n- :whale: `--detach-keys`: Override the default detach keys\n\nPlatform flags:\n\n- :whale: `--platform=(amd64|arm64|...)`: Set platform\n\nInit process flags:\n\n- :whale: `--init`: Run an init inside the container that forwards signals and reaps processes.\n- :nerd_face: `--init-binary=<binary-name>`: The custom init binary to use. We suggest you use the [tini](https://github.com/krallin/tini) binary which is used in Docker project to get the same behavior.\n  Please make sure the binary exists in your `PATH`.\n  - Default: `tini`\n\nIsolation flags:\n\n- :whale: :nerd_face: `--isolation=(default|process|host|hyperv)`: Used on Windows to change process isolation level. `default` will use the runtime options configured in `default_runtime` in the [containerd configuration](https://github.com/containerd/containerd/blob/master/docs/cri/config.md#cri-plugin-config-guide) which is `process` in containerd by default. `process` runs process isolated containers.  `host` runs [Host Process containers](https://kubernetes.io/docs/tasks/configure-pod-container/create-hostprocess-pod/).  Host process containers inherit permissions from containerd process unless `--user` is specified then will start with user specified and the user specified must be present on the host.  `host` requires Containerd 1.7+. `hyperv` runs Hyper-V hypervisor partition-based isolated containers. Not implemented for Linux.\n\nNetwork flags:\n\n- :whale: `--net, --network=(bridge|host|none|container:<container>|ns:<path>|<CNI>)`: Connect a container to a network.\n  - Default: \"bridge\"\n  - `container:<name|id>`: reuse another container's network stack, container has to be precreated.\n  - :nerd_face: `ns:<path>`: run inside an existing network namespace\n  - :nerd_face: Unlike Docker, this flag can be specified multiple times (`--net foo --net bar`)\n- :whale: `-p, --publish`: Publish a container's port(s) to the host\n- :whale: `--dns`: Set custom DNS servers\n- :whale: `--dns-search`: Set custom DNS search domains\n- :whale: `--dns-opt, --dns-option`: Set DNS options\n- :whale: `-h, --hostname`: Container host name\n- :whale: `--domainname`: Container domain name\n- :whale: `--add-host`: Add a custom host-to-IP mapping (host:ip). `ip` could be a special string `host-gateway`,\n- which will be resolved to the `host-gateway-ip` in nerdctl.toml or global flag.\n- :whale: `--ip`: Specific static IP address(es) to use. Note that unlike docker, nerdctl allows specifying it with the default bridge network.\n- :whale: `--ip6`: Specific static IP6 address(es) to use. Should be used with user networks\n- :whale: `--mac-address`: Specific MAC address to use. Be aware that it does not\n  check if manually specified MAC addresses are unique. Supports network\n  type `bridge` and `macvlan`\n\nResource flags:\n\n- :whale: `--cpus`: Number of CPUs\n- :whale: `--cpu-quota`: Limit the CPU CFS (Completely Fair Scheduler) quota\n- :whale: `--cpu-period`: Limit the CPU CFS (Completely Fair Scheduler) period\n- :whale: `--cpu-shares`: CPU shares (relative weight)\n- :whale: `--cpuset-cpus`: CPUs in which to allow execution (0-3, 0,1)\n- :whale: `--cpuset-mems`: Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems\n- :whale: `--cpu-rt-period`: Limit CPU real-time period in microseconds. Only supported with cgroup v1.\n- :whale: `--cpu-rt-runtime`: Limit CPU real-time runtime in microseconds. Only supported with cgroup v1.\n- :whale: `--memory`: Memory limit\n- :whale: `--memory-reservation`: Memory soft limit\n- :whale: `--memory-swap`: Swap limit equal to memory plus swap: '-1' to enable unlimited swap\n- :whale: `--memory-swappiness`: Tune container memory swappiness (0 to 100) (default -1)\n- :whale: `--kernel-memory`: Kernel memory limit (deprecated)\n- :whale: `--oom-kill-disable`: Disable OOM Killer\n- :whale: `--oom-score-adj`: Tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)\n- :whale: `--pids-limit`: Tune container pids limit\n- :nerd_face: `--cgroup-conf`: Configure cgroup v2 (key=value)\n- :whale: `--blkio-weight`: Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)\n- :whale: `--blkio-weight-device`: Block IO weight (relative device weight)\n- :whale: `--device-read-bps`: Limit read rate (bytes per second) from a device\n- :whale: `--device-read-iops`: Limit read rate (IO per second) from a device\n- :whale: `--device-write-bps`: Limit write rate (bytes per second) to a device\n- :whale: `--device-write-iops`: Limit write rate (IO per second) to a device\n- :whale: `--cgroupns=(host|private)`: Cgroup namespace to use\n  - Default: \"private\" on cgroup v2 hosts, \"host\" on cgroup v1 hosts\n- :whale: `--cgroup-parent`: Optional parent cgroup for the container\n- :whale: `--device`: Add a host device to the container\n\nIntel RDT flags:\n\n- :nerd_face: `--rdt-class=CLASS`: Name of the RDT class (or CLOS) to associate the container wit\n\nUser flags:\n\n- :whale: `-u, --user`: Username or UID (format: <name|uid>[:<group|gid>])\n- :nerd_face: `--umask`: Set the umask inside the container. Defaults to 0022.\n  Corresponds to Podman CLI.\n- :whale: `--group-add`: Add additional groups to join\n- :whale: `--userns`: Set it to `host` to disable user namespacing set in nerdctl.toml or in cli.\n\n\nSecurity flags:\n\n- :whale: `--security-opt seccomp=<PROFILE_JSON_FILE>`: specify custom seccomp profile\n- :whale: `--security-opt apparmor=<PROFILE>`: specify custom AppArmor profile\n- :whale: `--security-opt no-new-privileges`: disallow privilege escalation, e.g., setuid and file capabilities\n- :whale: `--security-opt systempaths=unconfined`: Turn off confinement for system paths (masked paths, read-only paths) for the container\n- :whale: `--security-opt writable-cgroups`: making the cgroups writeable\n- :nerd_face: `--security-opt privileged-without-host-devices`: Don't pass host devices to privileged containers\n- :whale: `--cap-add=<CAP>`: Add Linux capabilities\n- :whale: `--cap-drop=<CAP>`: Drop Linux capabilities\n- :whale: `--privileged`: Give extended privileges to this container\n- :nerd_face: `--systemd=(true|false|always)`: Enable systemd compatibility (default: false).\n  - Default: \"false\"\n  - true: Enable systemd compatibility is enabled if the entrypoint executable matches one of the following paths:\n    - `/sbin/init`\n    - `/usr/sbin/init`\n    - `/usr/local/sbin/init`\n  - always: Always enable systemd compatibility\n\nCorresponds to Podman CLI.\n\nRuntime flags:\n\n- :whale: `--runtime`: Runtime to use for this container, e.g. \\\"crun\\\", or \\\"io.containerd.runsc.v1\\\".\n- :whale: `--sysctl`: Sysctl options, e.g \\\"net.ipv4.ip_forward=1\\\"\n\nVolume flags:\n\n- :whale: `-v, --volume <SRC>:<DST>[:<OPT>]`: Bind mount a volume, e.g., `-v /mnt:/mnt:rro,rprivate`\n  - :whale:     option `rw` : Read/Write (when writable)\n  - :whale:     option `ro` : Non-recursive read-only\n  - :nerd_face: option `rro`: Recursive read-only. Should be used in conjunction with `rprivate`. e.g., `-v /mnt:/mnt:rro,rprivate` makes children such as `/mnt/usb` to be read-only, too.\n    Requires kernel >= 5.12, and crun >= 1.4 or runc >= 1.1 (PR [#3272](https://github.com/opencontainers/runc/pull/3272)). With older runc, `rro` just works as `ro`.\n  - :whale:     option `shared`, `slave`, `private`: Non-recursive \"shared\" / \"slave\" / \"private\" propagation\n  - :whale:     option `rshared`, `rslave`, `rprivate`: Recursive \"shared\" / \"slave\" / \"private\" propagation\n  - :nerd_face: option `bind`: Not-recursively bind-mounted\n  - :nerd_face: option `rbind`: Recursively bind-mounted\n  - unimplemented options: `:z` and `:Z` (SELinux relabeling)\n- :whale: `--tmpfs`: Mount a tmpfs directory, e.g. `--tmpfs /tmp:size=64m,exec`.\n- :whale: `--mount`: Attach a filesystem mount to the container.\n  Consists of multiple key-value pairs, separated by commas and each\n  consisting of a `<key>=<value>` tuple.\n  e.g., `-- mount type=bind,source=/src,target=/app,bind-propagation=shared`.\n  - :whale: `type`: Current supported mount types are `bind`, `volume`, `tmpfs`.\n    The default type will be set to `volume` if not specified.\n    i.e., `--mount src=vol-1,dst=/app,readonly` equals `--mount type=volume,src=vol-1,dst=/app,readonly`\n    - unimplemented type: `image`\n  - Common Options:\n    - :whale: `src`, `source`: Mount source spec for bind and volume. Mandatory for bind.\n    - :whale: `dst`, `destination`, `target`: Mount destination spec.\n    - :whale: `readonly`, `ro`, `rw`, `rro`: Filesystem permissions.\n  - Options specific to `bind`:\n    - :whale: `bind-propagation`: `shared`, `slave`, `private`, `rshared`, `rslave`, or `rprivate`(default).\n    - :whale: `bind-nonrecursive`: `true` or `false`(default). If set to true, submounts are not recursively bind-mounted. This option is useful for readonly bind mount.\n    - unimplemented options: `consistency`\n  - Options specific to `tmpfs`:\n    - :whale: `tmpfs-size`: Size of the tmpfs mount in bytes. Unlimited by default.\n    - :whale: `tmpfs-mode`: File mode of the tmpfs in **octal**.\n      Defaults to `1777` or world-writable.\n  - Options specific to `volume`:\n    - unimplemented options: `volume-nocopy`, `volume-label`, `volume-driver`, `volume-opt`\n- :whale: `--volumes-from`: Mount volumes from the specified container(s), e.g. \"--volumes-from my-container\".\n\nRootfs flags:\n\n- :whale: `--read-only`: Mount the container's root filesystem as read only\n- :nerd_face: `--rootfs`: The first argument is not an image but the rootfs to the exploded container.\n  Corresponds to Podman CLI.\n\nEnv flags:\n\n- :whale: `--entrypoint`: Overwrite the default ENTRYPOINT of the image\n- :whale: `-w, --workdir`: Working directory inside the container\n- :whale: `-e, --env`: Set environment variables\n- :whale: `--env-file`: Set environment variables from file\n\nMetadata flags:\n\n- :whale: `--name`: Assign a name to the container\n- :whale: `-l, --label`: Set meta data on a container (Not passed through the OCI runtime since nerdctl v2.0, with an exception for `nerdctl/bypass4netns`)\n- :whale: `--label-file`: Read in a line delimited file of labels\n- :whale: `--annotation`: Add an annotation to the container (passed through to the OCI runtime)\n- :whale: `--cidfile`: Write the container ID to the file\n- :nerd_face: `--pidfile`: file path to write the task's pid. The CLI syntax conforms to Podman convention.\n\nHealth check flags:\n\n- :whale: `--health-cmd`: Command to run to check container health\n- :whale: `--health-interval`: Time between running the check (e.g., 30s, 1m)\n- :whale: `--health-timeout`: Time to wait before considering the check failed (e.g., 5s)\n- :whale: `--health-retries`: Number of failures before container is considered unhealthy\n- :whale: `--health-start-period`: Start period for the container to initialize before starting health-retries countdown\n- :whale: `--health-start-interval`: Interval between checks during the start period\n- :whale: `--no-healthcheck`: Disable any health checks defined by image or CLI\n\nLogging flags:\n\n- :whale: `--log-driver=(json-file|journald|fluentd|syslog|none)`: Logging driver for the container (default `json-file`).\n  - :whale: `--log-driver=json-file`: The logs are formatted as JSON. The default logging driver for nerdctl.\n    - The `json-file` logging driver supports the following logging options:\n      - :whale: `--log-opt=max-size=<MAX-SIZE>`: The maximum size of the log before it is rolled. A positive integer plus a modifier representing the unit of measure (k, m, or g). Defaults to unlimited.\n      - :whale: `--log-opt=max-file=<MAX-FILE>`: The maximum number of log files that can be present. If rolling the logs creates excess files, the oldest file is removed. Only effective when `max-size` is also set. A positive integer. Defaults to 1.\n      - :nerd_face: `--log-opt=log-path=<LOG-PATH>`: The log path where the logs are written. The path will be created if it does not exist. If the log file exists, the old file will be renamed to `<LOG-PATH>.1`.\n        - Default: `<data-root>/<containerd-socket-hash>/<namespace>/<container-id>/<container-id>-json.log`\n        - Example: `/var/lib/nerdctl/1935db59/containers/default/<container-id>/<container-id>-json.log`\n      - :whale: `--log-opt labels=production_status,geo`: A comma-separated list of logging-related labels this daemon accepts.\n      - :whale: `--log-opt env=os,customer`: A comma-separated list of logging-related environment variables this daemon accepts.\n  - :whale: `--log-driver=journald`: Writes log messages to `journald`. The `journald` daemon must be running on the host machine.\n    - :whale: `--log-opt=tag=<TEMPLATE>`: Specify template to set `SYSLOG_IDENTIFIER` value in journald logs.\n    - :whale: `--log-opt labels=production_status,geo`: A comma-separated list of logging-related labels this daemon accepts.\n    - :whale: `--log-opt env=os,customer`: A comma-separated list of logging-related environment variables this daemon accepts.\n  - :whale: `--log-driver=fluentd`: Writes log messages to `fluentd`. The `fluentd` daemon must be running on the host machine.\n    - The `fluentd` logging driver supports the following logging options:\n      - :whale: `--log-opt=fluentd-address=<ADDRESS>`: The address of the `fluentd` daemon, tcp(default) and unix sockets are supported..\n      - :whale: `--log-opt=fluentd-async=<true|false>`: Enable async mode for fluentd. The default value is false.\n      - :whale: `--log-opt=fluentd-buffer-limit=<LIMIT>`: The buffer limit for fluentd. If the buffer is full, the call to record logs will fail. The default is 8192. (<https://github.com/fluent/fluent-logger-golang/tree/master#bufferlimit>)\n      - :whale: `--log-opt=fluentd-retry-wait=<1s|1ms>`: The time to wait before retrying to send logs to fluentd. The default value is 1s.\n      - :whale: `--log-opt=fluentd-max-retries=<1>`: The maximum number of retries to send logs to fluentd. The default value is MaxInt32.\n      - :whale: `--log-opt=fluentd-sub-second-precision=<true|false>`: Enable sub-second precision for fluentd. The default value is false.\n      - :nerd_face: `--log-opt=fluentd-async-reconnect-interval=<1s|1ms>`: The time to wait before retrying to reconnect to fluentd. The default value is 0s.\n      - :nerd_face: `--log-opt=fluentd-request-ack=<true|false>`: Enable request ack for fluentd. The default value is false.\n  - :whale: `--log-driver=syslog`: Writes log messages to `syslog`. The\n      `syslog` daemon must be running on either the host machine or remote.\n    - The `syslog` logging driver supports the following logging options:\n      - :whale: `--log-opt=syslog-address=<ADDRESS>`: The address of an\n          external `syslog` server. The URI specifier may be\n          `tcp|udp|tcp+tls]://host:port`, `unix://path`, or `unixgram://path`.\n          If the transport is `tcp`, `udp`, or `tcp+tls`, the default port is\n          `514`.\n      - :whale: `--log-opt=syslog-facility=<FACILITY>`: The `syslog` facility to\n          use. Can be the number or name for any valid syslog facility. See the\n          [syslog documentation](https://www.rfc-editor.org/rfc/rfc5424#section-6.2.1).\n      - :whale: `--log-opt=syslog-tls-ca-cert=<VALUE>`: The absolute path to\n          the trust certificates signed by the CA. **Ignored if the address\n          protocol is not `tcp+tls`**.\n      - :whale: `--log-opt=syslog-tls-cert=<VALUE>`: The absolute path to\n          the TLS certificate file. **Ignored if the address protocol is not\n          `tcp+tls`**.\n      - :whale: `--log-opt=syslog-tls-key=<VALUE>`:The absolute path to\n          the TLS key file. **Ignored if the address protocol is not `tcp+tls`**.\n      - :whale: `--log-opt=syslog-tls-skip-verify=<VALUE>`: If set to `true`,\n          TLS verification is skipped when connecting to the daemon.\n          **Ignored if the address protocol is not `tcp+tls`**.\n      - :whale: `--log-opt=syslog-format=<VALUE>`: The `syslog` message format\n          to use. If not specified the local UNIX syslog format is used,\n          without a specified hostname. Specify `rfc3164` for the RFC-3164\n          compatible format, `rfc5424` for RFC-5424 compatible format, or\n          `rfc5424micro` for RFC-5424 compatible format with microsecond\n          timestamp resolution.\n      - :whale: `--log-opt=tag=<VALUE>`: A string that is appended to the\n          `APP-NAME` in the `syslog` message. By default, nerdctl uses the first\n          12 characters of the container ID to tag log messages.\n  - :whale:  `--log-driver=none`: Disables logging for the container, preventing log output from being collected.\n  - :nerd_face: Accepts a LogURI which is a containerd shim logger. A scheme must be specified for the URI. Example: `nerdctl run -d --log-driver binary:///usr/bin/ctr-journald-shim docker.io/library/hello-world:latest`. An implementation of shim logger can be found at (<https://github.com/containerd/containerd/tree/dbef1d56d7ebc05bc4553d72c419ed5ce025b05d/runtime/v2#logging>)\n\nShared memory flags:\n\n- :whale: `--ipc=(host|private|shareable|container:<container>)`: IPC namespace to use and mount `/dev/shm`. Default: \"private\". Only implemented on Linux.\n- :whale: `--shm-size`: Size of `/dev/shm`\n\nGPU flags:\n\n- :whale: `--gpus`: GPU devices to add to the container ('all' to pass all GPUs). Please see also [`./gpu.md`](./gpu.md) for details.\n\nUlimit flags:\n\n- :whale: `--ulimit`: Set ulimit\n\n--ulimit can be used to restrict the following types of resources.\n\n| type       |  describe| value range                                                                                                                                                                                                      |\n|----|----|----|\n| core       | limits the core file size (KB)| A 64-bit integer (INT64), with no units. It can be 0, negative, where -1 represents UNLIMITED (i.e., no limit is applied), and any other negative values will be forcibly converted to a large positive integer.|\n| cpu        | max CPU time (MIN)| same as above|\n| data       |max data size (KB) | same as above|\n| fsize      | maximum filesize (KB)| same as above|\n| locks      | max number of file locks the user can hold | same as above|\n| memlock    | max locked-in-memory address space (KB) | same as above|\n| msgqueue   | max memory used by POSIX message queues (bytes)| same as above|\n| nice       | nice priority | same as above |\n| nproc      | max number of processes | same as above|\n| rss        | max resident set size (KB)| same as above|\n| rtprio     | max realtime priority| same as above|\n| rttime     | realtime timeout | same as above|\n| sigpending | max number of pending signals| same as above|\n| stack      | max stack size (KB) | same as above|\n| nofile    | max number of open file descriptors| A 64-bit integer (int64), with no units. It cannot be negative; negative values will be forcibly converted to a large number, and an \"Operation not permitted\" error will occur during setting|\n\nVerify flags:\n\n- :nerd_face: `--verify`: Verify the image (none|cosign|notation). See [`./cosign.md`](./cosign.md) and [`./notation.md`](./notation.md) for details.\n- :nerd_face: `--cosign-key`: Path to the public key file, KMS, URI or Kubernetes Secret for `--verify=cosign`\n- :nerd_face: `--cosign-certificate-identity`: The identity expected in a valid Fulcio certificate for --verify=cosign. Valid values include email address, DNS names, IP addresses, and URIs. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\n- :nerd_face: `--cosign-certificate-identity-regexp`: A regular expression alternative to --cosign-certificate-identity for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\n- :nerd_face: `--cosign-certificate-oidc-issuer`: The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign,, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\n- :nerd_face: `--cosign-certificate-oidc-issuer-regexp`: A regular expression alternative to --certificate-oidc-issuer for --verify=cosign,. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\n\nIPFS flags:\n\n- :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default uses `$IPFS_PATH` env variable if defined or local directory `~/.ipfs`)\n\nUnimplemented `docker run` flags:\n    `--device-cgroup-rule`, `--disable-content-trust`, `--expose`,\n    `--link*`, `--publish-all`, `--storage-opt`, `--volume-driver`\n\n### :whale: nerdctl exec\n\nRun a command in a running container.\n\nUsage: `nerdctl exec [OPTIONS] CONTAINER COMMAND [ARG...]`\n\nFlags:\n\n- :whale: `-i, --interactive`: Keep STDIN open even if not attached\n- :whale: `-t, --tty`: Allocate a pseudo-TTY\n  - :warning: WIP: currently `-t` conflicts with `-d`\n- :whale: `-d, --detach`: Detached mode: run command in the background\n- :whale: `-w, --workdir`: Working directory inside the container\n- :whale: `-e, --env`: Set environment variables\n- :whale: `--env-file`: Set environment variables from file\n- :whale: `--privileged`: Give extended privileges to the command\n- :whale: `-u, --user`: Username or UID (format: <name|uid>[:<group|gid>])\n\nUnimplemented `docker exec` flags: `--detach-keys`\n\n### :whale: nerdctl create\n\nCreate a new container.\n\nUsage: `nerdctl create [OPTIONS] IMAGE [COMMAND] [ARG...]`\n\n:nerd_face: `ipfs://` prefix can be used for `IMAGE` to pull it from IPFS. See [`ipfs.md`](./ipfs.md) for details.\n:nerd_face: `oci-archive://` prefix can be used for `IMAGE` to specify a local file system path to an OCI formatted tarball.\n\nThe `nerdctl create` command similar to `nerdctl run -d` except the container is never started. You can then use the `nerdctl start <container_id>` command to start the container at any point.\n\n### :whale: nerdctl cp\n\nCopy files/folders between a running container and the local filesystem\n\nUsage:\n\n- `nerdctl cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-`\n- `nerdctl cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH`\n\nUsing `-` as the `SRC_PATH` streams the contents of `STDIN` as a tar archive. The command extracts the content of the tar to the `DEST_PATH` in container's filesystem. In this case, `DEST_PATH` must specify a directory. Using `-` as the `DEST_PATH` streams the contents of the resource as a tar archive to `STDOUT`.\n\n:warning: `nerdctl cp` is designed only for use with trusted, cooperating containers.\nUsing `nerdctl cp` with untrusted or malicious containers is unsupported and may not provide protection against unexpected behavior.\n\nFlags:\n\n- :whale: `-L, --follow-link` Always follow symbol link in SRC_PATH.\n\nUnimplemented `docker cp` flags: `--archive`\n\n### :whale: nerdctl ps\n\nList containers.\n\nUsage: `nerdctl ps [OPTIONS]`\n\nFlags:\n\n- :whale: `-a, --all`: Show all containers (default shows just running)\n- :whale: `--no-trunc`: Don't truncate output\n- :whale: `-q, --quiet`: Only display container IDs\n- :whale: `-s, --size`: Display total file sizes\n- :whale: `--format`: Format the output using the given Go template\n  - :whale: `--format=table` (default): Table\n  - :whale: `--format='{{json .}}'`: JSON\n  - :nerd_face: `--format=wide`: Wide table\n  - :nerd_face: `--format=json`: Alias of `--format='{{json .}}'`\n- :whale: `-n, --last`: Show n last created containers (includes all states)\n- :whale: `-l, --latest`: Show the latest created container (includes all states)\n- :whale: `-f, --filter`: Filter containers based on given conditions. When specifying the condition 'status', it filters all containers\n  - :whale: `--filter id=<value>`: Container's ID. Both full ID and\n    truncated ID are supported\n  - :whale: `--filter name=<value>`: Container's name\n  - :whale: `--filter label=<key>=<value>`: Arbitrary string either a key or a\n    key-value pair\n  - :whale: `--filter exited=<value>`: Container's exit code. Only work with\n    `--all`\n  - :whale: `--filter status=<value>`: One of `created, running, paused,\n    stopped, exited, pausing, unknown`. Note that `restarting, removing, dead` are\n    not supported and will be ignored. When specifying this condition, it filters all containers.\n  - :whale: `--filter before/since=<ID/name>`: Filter containers created before\n    or after a given ID or name\n  - :whale: `--filter volume=<value>`: Filter by a given mounted volume or bind\n    mount\n  - :whale: `--filter network=<value>`: Filter by a given network\n\nFollowing arguments for `--filter` are not supported yet:\n\n1. `--filter ancestor=<value>`\n2. `--filter publish/expose=<port/startport-endport>[/<proto>]`\n3. `--filter health=<value>`\n4. `--filter isolation=<value>`\n5. `--filter is-task=<value>`\n\n### :whale: nerdctl inspect\n\nDisplay detailed information on one or more containers.\n\nUsage: `nerdctl inspect [OPTIONS] NAME|ID [NAME|ID...]`\n\nFlags:\n\n- :nerd_face: `--mode=(dockercompat|native)`: Inspection mode. \"native\" produces more information.\n- :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :whale: `--type`: Return JSON for specified type\n- :whale: `--size`: Display total file sizes if the type is container\n\n### :whale: nerdctl logs\n\nFetch the logs of a container.\n\n:warning: Currently, only containers created with `nerdctl run -d` are supported.\n\nUsage: `nerdctl logs [OPTIONS] CONTAINER`\n\nFlags:\n\n- :whale: `--details`: Show extra details provided to logs\n- :whale: `-f, --follow`: Follow log output\n- :whale: `--since`: Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\n- :whale: `--until`: Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\n- :whale: `-t, --timestamps`: Show timestamps\n- :whale: `-n, --tail`: Number of lines to show from the end of the logs (default \"all\")\n\n### :whale: nerdctl port\n\nList port mappings or a specific mapping for the container.\n\nUsage: `nerdctl port CONTAINER [PRIVATE_PORT[/PROTO]]`\n\n### :whale: nerdctl rm\n\nRemove one or more containers.\n\nUsage: `nerdctl rm [OPTIONS] CONTAINER [CONTAINER...]`\n\nFlags:\n\n- :whale: `-f, --force`: Force the removal of a running|paused|unknown container (uses SIGKILL)\n- :whale: `-v, --volumes`: Remove anonymous volumes associated with the container\n\nUnimplemented `docker rm` flags: `--link`\n\n### :whale: nerdctl stop\n\nStop one or more running containers.\n\nUsage: `nerdctl stop [OPTIONS] CONTAINER [CONTAINER...]`\n\nFlags:\n\n- :whale: `-t, --time=SECONDS`: Seconds to wait for stop before killing it (default \"10\")\n  - Tips: If the init process in container is exited after receiving SIGTERM or exited before the time you specified, the container will be exited immediately\n- :whale: `-s, --signal=SIGNAL`: Signal to send to the container (e.g. SIGINT).\n\n### :whale: nerdctl start\n\nStart one or more running containers.\n\nUsage: `nerdctl start [OPTIONS] CONTAINER [CONTAINER...]`\n\nFlags:\n\n- :whale: `-a, --attach`: Attach STDOUT/STDERR and forward signals\n- :whale: `--detach-keys`: Override the default detach keys\n- :whale: `--checkpoint`: checkpoint name\n- :whale: `--detach-keys`: checkpoint directory\n\n### :whale: nerdctl restart\n\nRestart one or more running containers.\n\nUsage: `nerdctl restart [OPTIONS] CONTAINER [CONTAINER...]`\n\nFlags:\n\n- :whale: `-t, --time=SECONDS`: Seconds to wait for stop before killing it (default \"10\")\n  - Tips: If the init process in container is exited after receiving SIGTERM or exited before the time you specified, the container will be exited immediately\n- :whale: `-s, --signal=SIGNAL`: Signal to send to the container (e.g. SIGINT).\n\n### :whale: nerdctl update\n\nUpdate configuration of one or more containers.\n\nUsage: `nerdctl update [OPTIONS] CONTAINER [CONTAINER...]`\n\n- :whale: `--cpus`: Number of CPUs\n- :whale: `--cpu-quota`: Limit the CPU CFS (Completely Fair Scheduler) quota\n- :whale: `--cpu-period`: Limit the CPU CFS (Completely Fair Scheduler) period\n- :whale: `--cpu-shares`: CPU shares (relative weight)\n- :whale: `--cpuset-cpus`: CPUs in which to allow execution (0-3, 0,1)\n- :whale: `--cpuset-mems`: Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems\n- :whale: `--memory`: Memory limit\n- :whale: `--memory-reservation`: Memory soft limit\n- :whale: `--memory-swap`: Swap limit equal to memory plus swap: '-1' to enable unlimited swap\n- :whale: `--kernel-memory`: Kernel memory limit (deprecated)\n- :whale: `--pids-limit`: Tune container pids limit\n- :whale: `--blkio-weight`: Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)\n- :whale: `--restart=(no|always|on-failure|unless-stopped)`: Restart policy to apply when a container exits\n\n### :whale: nerdctl wait\n\nBlock until one or more containers stop, then print their exit codes.\n\nUsage: `nerdctl wait CONTAINER [CONTAINER...]`\n\n### :whale: nerdctl kill\n\nKill one or more running containers.\n\nUsage: `nerdctl kill [OPTIONS] CONTAINER [CONTAINER...]`\n\nFlags:\n\n- :whale: `-s, --signal`: Signal to send to the container (default: \"KILL\")\n\n### :whale: nerdctl pause\n\nPause all processes within one or more containers.\n\nUsage: `nerdctl pause CONTAINER [CONTAINER...]`\n\n### :whale: nerdctl unpause\n\nUnpause all processes within one or more containers.\n\nUsage: `nerdctl unpause CONTAINER [CONTAINER...]`\n\n### :whale: nerdctl rename\n\nRename a container.\n\nUsage: `nerdctl rename CONTAINER NEW_NAME`\n\n### :whale: nerdctl attach\n\nAttach stdin, stdout, and stderr to a running container. For example:\n\n1. `nerdctl run -it --name test busybox` to start a container with a pty\n2. `ctrl-p ctrl-q` to detach from the container\n3. `nerdctl attach test` to attach to the container\n\nCaveats:\n\n- Currently only one attach session is allowed. When the second session tries to attach, currently no error will be returned from nerdctl.\n  However, since behind the scenes, there's only one FIFO for stdin, stdout, and stderr respectively,\n  if there are multiple sessions, all the sessions will be reading from and writing to the same 3 FIFOs, which will result in mixed input and partial output.\n- Until dual logging (issue #1946) is implemented,\n  a container that is spun up by either `nerdctl run -d` or `nerdctl start` (without `--attach`) cannot be attached to.\n\nUsage: `nerdctl attach CONTAINER`\n\nFlags:\n\n- :whale: `--detach-keys`: Override the default detach keys\n- :whale: `--no-stdin`: Do not attach STDIN\n\nUnimplemented `docker attach` flags: `--sig-proxy`\n\n### :whale: nerdctl container prune\n\nRemove all stopped containers.\n\nUsage: `nerdctl container prune [OPTIONS]`\n\nFlags:\n\n- :whale: `-f, --force`: Do not prompt for confirmation.\n\nUnimplemented `docker container prune` flags: `--filter`\n\n### :whale: nerdctl diff\n\nInspect changes to files or directories on a container's filesystem\n\nUsage: `nerdctl diff CONTAINER`\n\n### :whale: nerdctl export\n\nExport a containers filesystem as a tar archive.\n\nUsage: `nerdctl export CONTAINER`\n\n## Build\n\n### :whale: nerdctl build\n\nBuild an image from a Dockerfile.\n\n:information_source: Needs buildkitd to be running. See also [the document about setting up `nerdctl build` with BuildKit](./build.md).\n\nUsage: `nerdctl build [OPTIONS] PATH`\n\nFlags:\n\n- :nerd_face: `--buildkit-host=<BUILDKIT_HOST>`: BuildKit address\n- :whale: `-t, --tag`: Name and optionally a tag in the 'name:tag' format\n- :whale: `-f, --file`: Name of the Dockerfile\n- :whale: `--target`: Set the target build stage to build\n- :whale: `--build-arg`: Set build-time variables\n- :whale: `--no-cache`: Do not use cache when building the image\n- :whale: `--output=OUTPUT`: Output destination (format: type=local,dest=path)\n  - :whale: `type=local,dest=path/to/output-dir`: Local directory\n  - :whale: `type=oci[,dest=path/to/output.tar]`: Docker/OCI dual-format tar ball (compatible with `docker buildx build`)\n  - :whale: `type=docker[,dest=path/to/output.tar]`: Docker format tar ball (compatible with `docker buildx build`)\n  - :whale: `type=tar[,dest=path/to/output.tar]`: Raw tar ball\n  - :whale: `type=image,name=example.com/image,push=true`: Push to a registry (see [`buildctl build`](https://github.com/moby/buildkit/tree/v0.9.0#imageregistry) documentation)\n- :whale: `--progress=(auto|plain|tty)`: Set type of progress output (auto, plain, tty). Use plain to show container output\n- :whale: `--provenance`: Shorthand for \\\"--attest=type=provenance\\\", see [`buildx_build.md`](https://github.com/docker/buildx/blob/v0.12.1/docs/reference/buildx_build.md#provenance) documentation\n- :whale: `--pull=(true|false)`: On true, always attempt to pull latest image version from remote. Default uses buildkit's default.\n- :whale: `--secret`: Secret file to expose to the build: id=mysecret,src=/local/secret\n- :whale: `--allow`: Allow extra privileged entitlement, e.g. network.host, security.insecure  (It’s required to configure the buildkitd to enable the feature, see [`buildkitd.toml`](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) documentation)\n- :whale: `--attest`: Attestation parameters (format: \"type=sbom,generator=image\"), see [`buildx_build.md`](https://github.com/docker/buildx/blob/v0.12.1/docs/reference/buildx_build.md#attest) documentation\n- :whale: `--ssh`: SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)\n- :whale: `-q, --quiet`: Suppress the build output and print image ID on success\n- :whale: `--sbom`: Shorthand for \\\"--attest=type=sbom\\\", see [`buildx_build.md`](https://github.com/docker/buildx/blob/v0.12.1/docs/reference/buildx_build.md#sbom) documentation\n- :whale: `--cache-from=CACHE`: External cache sources (eg. user/app:cache, type=local,src=path/to/dir) (compatible with `docker buildx build`)\n- :whale: `--cache-to=CACHE`: Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir) (compatible with `docker buildx build`)\n- :whale: `--platform=(amd64|arm64|...)`: Set target platform for build (compatible with `docker buildx build`)\n- :whale: `--iidfile=FILE`: Write the image ID to the file\n- :nerd_face: `--ipfs`: Build image with pulling base images from IPFS. See [`ipfs.md`](./ipfs.md) for details.\n- :whale: `--label`: Set metadata for an image\n- :whale: `--network=(default|host|none)`: Set the networking mode for the RUN instructions during build.(compatible with `buildctl build`)\n- :whale: `--build-context`: Set additional contexts for build (e.g. dir2=/path/to/dir2, myorg/myapp=docker-image://path/to/myorg/myapp)\n- :whale: `--add-host`: Add a custom host-to-IP mapping (format: `host:ip`)\n- :nerd_face: `--source-policy-file`: BuildKit source policy JSON file for reproducible builds. See [BuildKit build-repro docs](https://github.com/moby/buildkit/blob/master/docs/build-repro.md).\n  For compatibility with Docker Buildx, the `EXPERIMENTAL_BUILDKIT_SOURCE_POLICY` environment variable is also supported. Example no-op policy: `{\"rules\":[]}`\n\nUnimplemented `docker build` flags: `--squash`\n\n### :whale: nerdctl commit\n\nCreate a new image from a container's changes\n\nUsage: `nerdctl commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]`\n\nFlags:\n\n- :whale: `-a, --author`: Author (e.g., \"nerdctl contributor <nerdctl-dev@example.com>\")\n- :whale: `-m, --message`: Commit message\n- :whale: `-c, --change`: Apply Dockerfile instruction to the created image (supported directives: [CMD, ENTRYPOINT])\n- :whale: `-p, --pause`: Pause container during commit (default: true)\n- :nerd_face: `--compression`: Commit compression algorithm (supported values: zstd or gzip) (default: gzip) (zstd is generally better for compression ratio but might not be as widely supported)\n- :nerd_face: `--format`: Format of the committed image (supported values: docker or oci) (default: docker) (docker uses Docker Schema2 media types for compatibility, oci uses OCI image format media types)\n- :nerd_face: `--estargz`: Convert the committed layer to eStargz for lazy pulling\n- :nerd_face: `--estargz-compression-level`: eStargz compression level (1-9) (default: 9)\n- :nerd_face: `--estargz-chunk-size`: eStargz chunk size\n- :nerd_face: `--estargz-min-chunk-size`: The minimal number of bytes of data must be written in one gzip stream\n- :nerd_face: `--zstdchunked`: Convert the committed layer to zstd:chunked for lazy pulling\nsupport zstdchunked convert\n- :nerd_face: `--zstdchunked-compression-level`: zstd:chunked compression level (default: 3)\n- :nerd_face: `--zstdchunked-chunk-size`: zstd:chunked chunk size\n\n## Image management\n\n### :whale: nerdctl images\n\nList images\n\n:warning: The image ID is usually different from Docker image ID.\n\nUsage: `nerdctl images [OPTIONS] [REPOSITORY[:TAG]]`\n\nFlags:\n\n- :whale: `-a, --all`: Show all images (unimplemented)\n- :whale: `-q, --quiet`: Only show numeric IDs\n- :whale: `--no-trunc`: Don't truncate output\n- :whale: `--format`: Format the output using the given Go template\n  - :whale: `--format=table` (default): Table\n  - :whale: `--format='{{json .}}'`: JSON\n  - :nerd_face: `--format=wide`: Wide table\n  - :nerd_face: `--format=json`: Alias of `--format='{{json .}}'`\n- :whale: `--digests`: Show digests (compatible with Docker, unlike ID)\n- :whale: `-f, --filter`: Filter the images.\n  - :whale: `--filter=before=<image:tag>`: Images created before given image (exclusive)\n  - :whale: `--filter=since=<image:tag>`: Images created after given image (exclusive)\n  - :whale: `--filter=label<key>=<value>`: Matches images based on the presence of a label alone or a label and a value\n  - :whale: `--filter=dangling=true`: Filter images by dangling\n  - :nerd_face: `--filter=reference=<image:tag>`: Filter images by reference (Matches both docker compatible wildcard pattern and regexp match)\n- :nerd_face: `--names`: Show image names\n\n### :whale: nerdctl pull\n\nPull an image from a registry.\n\nUsage: `nerdctl pull [OPTIONS] NAME[:TAG|@DIGEST]`\n\n:nerd_face: `ipfs://` prefix can be used for `NAME` to pull it from IPFS. See [`ipfs.md`](./ipfs.md) for details.\n\nFlags:\n\n- :whale: `--platform=(amd64|arm64|...)`: Pull content for a specific platform\n  - :nerd_face: Unlike Docker, this flag can be specified multiple times (`--platform=amd64 --platform=arm64`)\n- :nerd_face: `--all-platforms`: Pull content for all platforms\n- :nerd_face: `--unpack`: Unpack the image for the current single platform (auto/true/false)\n- :whale: `-q, --quiet`: Suppress verbose output\n- :nerd_face: `--verify`: Verify the image (none|cosign|notation). See [`./cosign.md`](./cosign.md) and [`./notation.md`](./notation.md) for details.\n- :nerd_face: `--cosign-key`: Path to the public key file, KMS, URI or Kubernetes Secret for `--verify=cosign`\n- :nerd_face: `--cosign-certificate-identity`: The identity expected in a valid Fulcio certificate for --verify=cosign. Valid values include email address, DNS names, IP addresses, and URIs. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\n- :nerd_face: `--cosign-certificate-identity-regexp`: A regular expression alternative to --cosign-certificate-identity for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\n- :nerd_face: `--cosign-certificate-oidc-issuer`: The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign,, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\n- :nerd_face: `--cosign-certificate-oidc-issuer-regexp`: A regular expression alternative to --certificate-oidc-issuer for --verify=cosign,. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\n- :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default uses `$IPFS_PATH` env variable if defined or local directory `~/.ipfs`)\n- :nerd_face: `--soci-index-digest`: Specify a particular index digest for SOCI. If left empty, SOCI will automatically use the index determined by the selection policy.\n\nUnimplemented `docker pull` flags: `--all-tags`, `--disable-content-trust` (default true)\n\n### :whale: nerdctl push\n\nPush an image to a registry.\n\nUsage: `nerdctl push [OPTIONS] NAME[:TAG]`\n\n:nerd_face: `ipfs://` prefix can be used for `NAME` to push it to IPFS. See [`ipfs.md`](./ipfs.md) for details.\n\nFlags:\n\n- :nerd_face: `--platform=(amd64|arm64|...)`: Push content for a specific platform\n- :nerd_face: `--all-platforms`: Push content for all platforms\n- :nerd_face: `--sign`: Sign the image (none|cosign|notation). See [`./cosign.md`](./cosign.md) and [`./notation.md`](./notation.md) for details.\n- :nerd_face: `--cosign-key`: Path to the private key file, KMS, URI or Kubernetes Secret for `--sign=cosign`\n- :nerd_face: `--notation-key-name`: Signing key name for a key previously added to notation's key list for `--sign=notation`\n- :nerd_face: `--allow-nondistributable-artifacts`: Allow pushing images with non-distributable blobs\n- :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default uses `$IPFS_PATH` env variable if defined or local directory `~/.ipfs`)\n- :whale: `-q, --quiet`: Suppress verbose output\n- :nerd_face: `--soci-span-size`: Span size in bytes that soci index uses to segment layer data. Default is 4 MiB.\n- :nerd_face: `--soci-min-layer-size`: Minimum layer size in bytes to build zTOC for. Smaller layers won't have zTOC and not lazy pulled. Default is 10 MiB.\n\nUnimplemented `docker push` flags: `--all-tags`, `--disable-content-trust` (default true)\n\n### :whale: nerdctl load\n\nLoad an image from a tar archive or STDIN.\n\n:nerd_face: Supports both Docker Image Spec v1.2 and OCI Image Spec v1.0.\n\nUsage: `nerdctl load [OPTIONS]`\n\nFlags:\n\n- :whale: `-i, --input`: Read from tar archive file, instead of STDIN\n- :whale: `-q, --quiet`: Suppress the load output\n- :nerd_face: `--platform=(amd64|arm64|...)`: Import content for a specific platform\n- :nerd_face: `--all-platforms`: Import content for all platforms\n\n### :whale: nerdctl save\n\nSave one or more images to a tar archive (streamed to STDOUT by default)\n\n:nerd_face: The archive implements both Docker Image Spec v1.2 and OCI Image Spec v1.0.\n\nUsage: `nerdctl save [OPTIONS] IMAGE [IMAGE...]`\n\nFlags:\n\n- :whale: `-o, --output`: Write to a file, instead of STDOUT\n- :nerd_face: `--platform=(amd64|arm64|...)`: Export content for a specific platform\n- :nerd_face: `--all-platforms`: Export content for all platforms\n\n### :whale: nerdctl import\n\nImport the contents from a tarball to create a filesystem image.\n\nUsage: `nerdctl import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]`\n\nFlags:\n\n- :whale: `-m, --message`: Set commit message for imported image\n- :nerd_face: `--platform=(linux/amd64|linux/arm64|...)`: Set platform for the imported image\n\nUnimplemented `docker import` flags: `--change`\n\n### :whale: nerdctl tag\n\nCreate a tag TARGET\\_IMAGE that refers to SOURCE\\_IMAGE.\n\nUsage: `nerdctl tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`\n\n### :whale: nerdctl rmi\n\nRemove one or more images\n\nUsage: `nerdctl rmi [OPTIONS] IMAGE [IMAGE...]`\n\nFlags:\n\n- :nerd_face: `--async`: Asynchronous mode\n- :whale: `-f, --force`: Force removal of the image\n\nUnimplemented `docker rmi` flags: `--no-prune`\n\n### :whale: nerdctl image inspect\n\nDisplay detailed information on one or more images.\n\nUsage: `nerdctl image inspect [OPTIONS] NAME|ID [NAME|ID...]`\n\nFlags:\n\n- :nerd_face: `--mode=(dockercompat|native)`: Inspection mode. \"native\" produces more information.\n- :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :nerd_face: `--platform=(amd64|arm64|...)`: Inspect a specific platform\n\n### :whale: nerdctl image history\n\nShow the history of an image.\n\nUsage: `nerdctl history [OPTIONS] IMAGE`\n\nFlags:\n\n- :whale: `--no-trunc`: Don't truncate output\n- :whale: `-q, --quiet`: Only display snapshots IDs\n- :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :whale: `-H, --human`: Print sizes and dates in human readable format (default true)\n\n### :whale: nerdctl image prune\n\nRemove unused images.\n\nUsage: `nerdctl image prune [OPTIONS]`\n\nFlags:\n\n- :whale: `-a, --all`: Remove all unused images, not just dangling ones\n- :whale: `--filter`: Filter the images.\n  - :whale: `--filter=until=<timestamp>`: Images created before given date formatted timestamps or Go duration strings. Currently does not support Unix timestamps.\n  - :whale: `--filter=label<key>=<value>`: Matches images based on the presence of a label alone or a label and a value\n- :whale: `-f, --force`: Do not prompt for confirmation\n\n### :nerd_face: nerdctl image convert\n\nConvert an image format.\n\ne.g., `nerdctl image convert --estargz --oci example.com/foo:orig example.com/foo:esgz`\n\nUsage: `nerdctl image convert [OPTIONS] SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`\n\nFlags:\n\n- `--estargz`                          : convert legacy tar(.gz) layers to eStargz for lazy pulling. Should be used in conjunction with '--oci'\n- `--estargz-record-in=<FILE>`         : read `ctr-remote optimize --record-out=<FILE>` record file. :warning: This flag is experimental and subject to change.\n- `--estargz-compression-level=<LEVEL>`: eStargz compression level (default: 9)\n- `--estargz-chunk-size=<SIZE>`        : eStargz chunk size\n- `--estargz-min-chunk-size=<SIZE>` : The minimal number of bytes of data must be written in one gzip stream (requires stargz-snapshotter >= v0.13.0). Useful for creating a smaller eStargz image (refer to [`./stargz.md`](./stargz.md) for details).\n- `--estargz-external-toc` : Separate TOC JSON into another image (called \\\"TOC image\\\"). The name of TOC image is the original + \\\"-esgztoc\\\" suffix. Both eStargz and the TOC image should be pushed to the same registry. (requires stargz-snapshotter >= v0.13.0) Useful for creating a smaller eStargz image (refer to [`./stargz.md`](./stargz.md) for details). :warning: This flag is experimental and subject to change.\n- `--estargz-keep-diff-id`: Convert to esgz without changing diffID (cannot be used in conjunction with '--estargz-record-in'. must be specified with '--estargz-external-toc')\n- `--zstd`                             : Use zstd compression instead of gzip. Should be used in conjunction with '--oci'\n- `--zstd-compression-level=<LEVEL>`   : zstd compression level (default: 3)\n- `--zstdchunked`                      : Use zstd compression instead of gzip (a.k.a zstd:chunked). Should be used in conjunction with '--oci'\n- `--zstdchunked-record-in=<FILE>` : read `ctr-remote optimize --record-out=<FILE>` record file. :warning: This flag is experimental and subject to change.\n- `--zstdchunked-compression-level=<LEVEL>`: zstd:chunked compression level (default: 3)\n- `--zstdchunked-chunk-size=<SIZE>`: zstd:chunked chunk size\n- `--uncompress`                       : convert tar.gz layers to uncompressed tar layers\n- `--oci`                              : convert Docker media types to OCI media types\n- `--platform=<PLATFORM>`              : convert content for a specific platform\n- `--all-platforms`                    : convert content for all platforms (default: false)\n- `--soci`                             : convert content to SOCI image manifest v2\n*[**Note**: soci convert uses the default platform if nothing is specified. --platform flag can be used to specify a platform]*\n- `--soci-span-size` : Span size in bytes that soci index uses to segment layer data. Default is 4 MiB.\n- `--soci-min-layer-size`: Minimum layer size in bytes to build zTOC for. Smaller layers won't have zTOC and not lazy pulled. Default is 10 MiB.\n\n\n### :nerd_face: nerdctl image encrypt\n\nEncrypt image layers. See [`./ocicrypt.md`](./ocicrypt.md).\n\nUsage: `nerdctl image encrypt [OPTIONS] SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`\n\nExample:\n\n```bash\nopenssl genrsa -out mykey.pem\nopenssl rsa -in mykey.pem -pubout -out mypubkey.pem\nnerdctl image encrypt --recipient=jwe:mypubkey.pem --platform=linux/amd64,linux/arm64 foo example.com/foo:encrypted\nnerdctl push example.com/foo:encrypted\n```\n\n:warning: CAUTION: This command only encrypts image layers, but does NOT encrypt [container configuration such as `Env` and `Cmd`](https://github.com/opencontainers/image-spec/blob/v1.0.1/config.md#example).\nTo see non-encrypted information, run `nerdctl image inspect --mode=native --platform=PLATFORM example.com/foo:encrypted` .\n\nFlags:\n\n- `--recipient=<RECIPIENT>`      : Recipient of the image is the person who can decrypt (e.g., `jwe:mypubkey.pem`)\n- `--dec-recipient=<RECIPIENT>`  : Recipient of the image; used only for PKCS7 and must be an x509 certificate\n- `--key=<KEY>[:<PWDDESC>]`      : A secret key's filename and an optional password separated by colon, PWDDESC=<password>|pass:<password>|fd=<file descriptor>|filename\n- `--gpg-homedir=<DIR>`          : The GPG homedir to use; by default gpg uses ~/.gnupg\n- `--gpg-version=<VERSION>`      : The GPG version (\"v1\" or \"v2\"), default will make an educated guess\n- `--platform=<PLATFORM>`        : Convert content for a specific platform\n- `--all-platforms`              : Convert content for all platforms (default: false)\n\n### :nerd_face: nerdctl image decrypt\n\nDecrypt image layers. See [`./ocicrypt.md`](./ocicrypt.md).\n\nUsage: `nerdctl image decrypt [OPTIONS] SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`\n\nExample:\n\n```bash\nnerdctl pull --unpack=false example.com/foo:encrypted\nnerdctl image decrypt --key=mykey.pem example.com/foo:encrypted foo:decrypted\n```\n\nFlags:\n\n- `--dec-recipient=<RECIPIENT>`  : Recipient of the image; used only for PKCS7 and must be an x509 certificate\n- `--key=<KEY>[:<PWDDESC>]`      : A secret key's filename and an optional password separated by colon, PWDDESC=<password>|pass:<password>|fd=<file descriptor>|filename\n- `--gpg-homedir=<DIR>`          : The GPG homedir to use; by default gpg uses ~/.gnupg\n- `--gpg-version=<VERSION>`      : The GPG version (\"v1\" or \"v2\"), default will make an educated guess\n- `--platform=<PLATFORM>`        : Convert content for a specific platform\n- `--all-platforms`              : Convert content for all platforms (default: false)\n\n## Checkpoint management\n\n### :whale: nerdctl checkpoint create\n\nCreate a checkpoint from a running container.\n\nUsage: `nerdctl checkpoint create [OPTIONS] CONTAINER CHECKPOINT`\n\nFlags:\n- :whale: `--leave-running`: Leave the container running after checkpoint\n- :whale: `checkpoint-dir`: Use a custom checkpoint storage directory\n\n### :whale: nerdctl checkpoint list\n\nList checkpoints for a container\n\nUsage: `nerdctl checkpoint list/ls [OPTIONS] CONTAINER`\n\nFlags:\n- :whale: `checkpoint-dir`: Use a custom checkpoint storage directory\n\n### :whale: nerdctl checkpoint remove\n\nRemove a checkpoint for a container\n\nUsage: `nerdctl checkpoint remove/rm [OPTIONS] CONTAINER CHECKPOINT`\n\nFlags:\n- :whale: `checkpoint-dir`: Use a custom checkpoint storage directory\n\n## Manifest management\n\n### :whale: nerdctl manifest annotate\n\nAdd additional information to a local image manifest.\n\nUsage: `nerdctl manifest annotate [OPTIONS] INDEX/MANIFESTLIST MANIFEST`\n\nFlags:\n\n- :whale: `--os`: Set operating system (e.g., \"linux\", \"windows\", \"freebsd\")\n- :whale: `--arch`: Set architecture (e.g., \"amd64\", \"arm64\", \"arm\")\n- :whale: `--os-version`: Set operating system version (e.g., \"10.0.19041\")\n- :whale: `--variant`: Set architecture variant (e.g., \"v7\", \"v8\")\n- :whale: `--os-features`: Set operating system features (e.g., \"win32k\")\n\nExamples:\n\n```bash\nnerdctl manifest annotate myapp:latest alpine@sha256:eafc1edb577d2e9b458664a15f23ea1c370214193226069eb22921169fc7e43f \\\n  --os linux --arch arm --variant v7 --os-features feature1,feature2\n```\n\n### :whale: nerdctl manifest create\n\nCreate a local index/manifest list.\n\nUsage: `nerdctl manifest create [OPTIONS] INDEX/MANIFESTLIST MANIFEST [MANIFEST...]`\n\nFlags:\n\n- `--amend`: Amend the existing index/manifest list\n- `--insecure`: Allow communication with an insecure registry\n\nExample:\n\n```bash\nnerdctl manifest create myapp:latest alpine@sha256:eafc1edb577d2e9b458664a15f23ea1c370214193226069eb22921169fc7e43f\n```\n\n### :whale: nerdctl manifest inspect\n\nDisplay the contents of a manifest list or manifest.\n\nUsage: `nerdctl manifest inspect [OPTIONS] MANIFEST`\n\n#### Input formats\n\nYou can specify the manifest to inspect using one of the following formats:\n- **Image name with tag**: `alpine:3.22.1`\n- **Image name with digest**: `alpine@sha256:eafc1edb577d2e9b458664a15f23ea1c370214193226069eb22921169fc7e43f`\n\nFlags:\n\n- `--verbose` : Verbose output, show additional info including layers and platform\n- `--insecure`: Allow communication with an insecure registry\nExample:\n\n```bash\nnerdctl manifest inspect alpine:3.22.1\nnerdctl manifest inspect alpine@sha256:eafc1edb577d2e9b458664a15f23ea1c370214193226069eb22921169fc7e43f\n```\n\n### :whale: nerdctl manifest push\n\nPush a manifest list to a registry.\n\nUsage: `nerdctl manifest push [OPTIONS] INDEX/MANIFESTLIST`\n\nFlags:\n\n- `--insecure`: Allow communication with an insecure registry\n- `--purge`: Remove the manifest list after pushing\n\nExamples:\n\n```bash\n# Push a manifest list to a registry\nnerdctl manifest push myapp:latest\n```\n\n### :whale: nerdctl manifest rm\n\nRemove one or more index/manifest lists.\n\nUsage: `nerdctl manifest rm INDEX/MANIFESTLIST [INDEX/MANIFESTLIST...]`\n\nExample:\n\n```bash\nnerdctl manifest rm alpine:3.22.1 alpine:3.22.2\n```\n\n## Registry\n\n### :whale: nerdctl login\n\nLog in to a container registry.\n\nUsage: `nerdctl login [OPTIONS] [SERVER]`\n\nFlags:\n\n- :whale: `-u, --username`:   Username\n- :whale: `-p, --password`:   Password\n- :whale: `--password-stdin`: Take the password from stdin\n\n### :whale: nerdctl logout\n\nLog out from a container registry\n\nUsage: `nerdctl logout [SERVER]`\n\n### :whale: nerdctl search\n\nSearch Docker Hub or a registry for images\n\nUsage: `nerdctl search [OPTIONS] TERM`\n\nFlags:\n\n- :whale: `--limit`: Max number of search results (default: 0)\n- :whale: `--no-trunc`: Don't truncate output (default: false)\n- :whale: `--filter, -f`: Filter output based on conditions provided\n- :whale: `--format`: Format the output using the given Go template\n\n## Network management\n\n### :whale: nerdctl network create\n\nCreate a network\n\n:information_source: To isolate CNI bridge, CNI plugins v1.1.0 or later needs to be installed.\n\nUsage: `nerdctl network create [OPTIONS] NETWORK`\n\nFlags:\n\n- :whale: `-d, --driver=(bridge|nat|macvlan|ipvlan)`: Driver to manage the Network\n  - :whale: `--driver=bridge`: Default driver for unix\n  - :whale: `--driver=macvlan`: Macvlan network driver for unix\n  - :whale: `--driver=ipvlan`: IPvlan network driver for unix\n  - :whale: `--driver=nat`: Default driver for windows\n- :whale: `-o, --opt`: Set driver specific options\n  - :whale: `--opt=com.docker.network.driver.mtu=<MTU>`: Set the containers network MTU\n  - :nerd_face: `--opt=mtu=<MTU>`: Alias of `--opt=com.docker.network.driver.mtu=<MTU>`\n  - :whale: `--opt=com.docker.network.bridge.enable_icc=<true/false>`: Enable or Disable inter-container connectivity\n  - :nerd_face: `--opt=icc=<true/false>`: Alias of `--opt=com.docker.network.bridge.enable_icc`\n  - :whale: `--opt=macvlan_mode=(bridge)>`: Set macvlan network mode (default: bridge)\n  - :whale: `--opt=ipvlan_mode=(l2|l3)`: Set IPvlan network mode (default: l2)\n  - :nerd_face: `--opt=mode=(bridge|l2|l3)`: Alias of `--opt=macvlan_mode=(bridge)` and `--opt=ipvlan_mode=(l2|l3)`\n  - :whale: `--opt=parent=<INTERFACE>`: Set valid parent interface on host\n- :whale: `--ipam-driver=(default|host-local|dhcp)`: IP Address Management Driver\n  - :whale: `--ipam-driver=default`: Default IPAM driver\n  - :nerd_face: `--ipam-driver=host-local`: Host-local IPAM driver for unix\n  - :nerd_face: `--ipam-driver=dhcp`: DHCP IPAM driver for unix, requires root\n- :whale: `--ipam-opt`: Set IPAM driver specific options\n- :whale: `--subnet`: Subnet in CIDR format that represents a network segment, e.g. \"10.5.0.0/16\"\n- :whale: `--gateway`: Gateway for the master subnet\n- :whale: `--ip-range`: Allocate container ip from a sub-range\n- :whale: `--label`: Set metadata on a network\n- :whale: `--ipv6`: Enable IPv6. Should be used with a valid subnet.\n- :whale: `--internal`: Restrict external access to the network.\n\nUnimplemented `docker network create` flags: `--attachable`, `--aux-address`, `--config-from`, `--config-only`, `--ingress`, `--scope`\n\n### :whale: nerdctl network ls\n\nList networks\n\nUsage: `nerdctl network ls [OPTIONS]`\n\nFlags:\n\n- :whale: `-q, --quiet`: Only display network IDs\n- :whale: `--format`: Format the output using the given Go template\n  - :whale: `--format=table` (default): Table\n  - :whale: `--format='{{json .}}'`: JSON\n  - :nerd_face: `--format=wide`: Alias of `--format=table`\n  - :nerd_face: `--format=json`: Alias of `--format='{{json .}}'`\n\nUnimplemented `docker network ls` flags: `--no-trunc`\n\n### :whale: nerdctl network inspect\n\nDisplay detailed information on one or more networks\n\nUsage: `nerdctl network inspect [OPTIONS] NETWORK [NETWORK...]`\n\nFlags:\n\n- :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :nerd_face: `--mode=(dockercompat|native)`: Inspection mode. \"native\" produces more information.\n\nUnimplemented `docker network inspect` flags: `--verbose`\n\n### :whale: nerdctl network rm\n\nRemove one or more networks by name or identifier\n\n:warning network removal will fail if there are containers attached to it.\n\nUsage: `nerdctl network rm NETWORK [NETWORK...]`\n\n### :whale: nerdctl network prune\n\nRemove all unused networks\n\nUsage: `nerdctl network prune [OPTIONS]`\n\nFlags:\n\n- :whale: `-f, --force`: Do not prompt for confirmation\n\nUnimplemented `docker network prune` flags: `--filter`\n\n## Volume management\n\n### :whale: nerdctl volume create\n\nCreate a volume\n\nUsage: `nerdctl volume create [OPTIONS] [VOLUME]`\n\nFlags:\n\n- :whale: `--label`: Set metadata for a volume\n\nUnimplemented `docker volume create` flags: `--driver`, `--opt`\n\n### :whale: nerdctl volume ls\n\nList volumes\n\nUsage: `nerdctl volume ls [OPTIONS]`\n\nFlags:\n\n- :whale: `-q, --quiet`: Only display volume names\n- :whale: `--format`: Format the output using the given Go template\n  - :whale: `--format=table` (default): Table\n  - :whale: `--format='{{json .}}'`: JSON\n  - :nerd_face: `--format=wide`: Alias of `--format=table`\n  - :nerd_face: `--format=json`: Alias of `--format='{{json .}}'`\n- :nerd_face: `--size`: Display the disk usage of volumes.\n- :whale: `-f, --filter`: Filter volumes based on given conditions.\n  - :whale: `--filter label=<key>=<value>`: Matches volumes by label on both\n      `key` and `value`. If `value` is left empty, matches all volumes with `key`\n      regardless of its value\n  - :whale: `--filter name=<value>`: Matches all volumes with a name containing\n      the `value` string\n  - :nerd_face: `--filter \"size=<value>\"`: Matches all volumes with a size\n      meets the `value`. `size` operand can be `>=, <=, >, <, =` and `value` must be\n      an integer. Quotes should be used otherwise some shells may treat operand as\n      redirections\n\nFollowing arguments for `--filter` are not supported yet:\n\n1. `--filter=dangling=true`: Filter volumes by dangling\n2. `--filter=driver=local`: Filter volumes by driver\n\n### :whale: nerdctl volume inspect\n\nDisplay detailed information on one or more volumes\n\nUsage: `nerdctl volume inspect [OPTIONS] VOLUME [VOLUME...]`\n\nFlags:\n\n- :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :nerd_face: `--size`: Displays disk usage of volume\n\n### :whale: nerdctl volume rm\n\nRemove one or more volumes\n\nUsage: `nerdctl volume rm [OPTIONS] VOLUME [VOLUME...]`\n\n- :whale: `-f, --force`: Force the removal of one or more volumes\n\n### :whale: nerdctl volume prune\n\nRemove all unused local volumes\n\nUsage: `nerdctl volume prune [OPTIONS]`\n\nFlags:\n\n- :whale: `-f, --force`: Do not prompt for confirmation\n\nUnimplemented `docker volume prune` flags: `--filter`\n\n## Namespace management\n\n### :nerd_face: nerdctl namespace create\n\nCreate a new namespace.\n\nUsage: `nerdctl namespace create NAMESPACE`\nFlags:\n\n- `--label`: Set labels for a namespace\n\n### :nerd_face: nerdctl namespace inspect\n\nInspect a namespace.\n\nUsage: `nerdctl namespace inspect NAMESPACE`\n\n### :nerd_face: nerdctl namespace ls\n\nList containerd namespaces such as \"default\", \"moby\", or \"k8s.io\".\n\nUsage: `nerdctl namespace ls [OPTIONS]`\n\nFlags:\n\n- `-q, --quiet`: Only display namespace names\n- `-f, --format`: Format the output using the given Go template, e.g, `{{json .}}`\n\n### :nerd_face: nerdctl namespace remove\n\nRemove one or more namespaces.\n\nUsage: `nerdctl namespace remove [OPTIONS] NAMESPACE [NAMESPACE...]`\n\nFlags:\n\n- `-c, --cgroup`: delete the namespace's cgroup\n\n### :nerd_face: nerdctl namespace update\n\nUpdate labels for a namespace.\n\nUsage: `nerdctl namespace update NAMESPACE`\n\nFlags:\n\n- `--label`: Set labels for a namespace\n\n## AppArmor profile management\n\n### :nerd_face: nerdctl apparmor inspect\n\nDisplay the default AppArmor profile \"nerdctl-default\". Other profiles cannot be displayed with this command.\n\nUsage: `nerdctl apparmor inspect`\n\n### :nerd_face: nerdctl apparmor load\n\nLoad the default AppArmor profile \"nerdctl-default\". Requires root.\n\nUsage: `nerdctl apparmor load`\n\n### :nerd_face: nerdctl apparmor ls\n\nList the loaded AppArmor profile\n\nUsage: `nerdctl apparmor ls [OPTIONS]`\n\nFlags:\n\n- `-q, --quiet`: Only display volume names\n- `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n\n### :nerd_face: nerdctl apparmor unload\n\nUnload an AppArmor profile. The target profile name defaults to \"nerdctl-default\". Requires root.\n\nUsage: `nerdctl apparmor unload [PROFILE]`\n\n## Builder management\n\n### :whale: nerdctl builder prune\n\nClean up BuildKit build cache.\n\n:warning: The output format is not compatible with Docker.\n\nUsage: `nerdctl builder prune`\n\nFlags:\n\n- :nerd_face: `--buildkit-host=<BUILDKIT_HOST>`: BuildKit address\n- :whale: `--all`: Remove all unused build cache, not just dangling ones\n- :whale: `--force`: Do not prompt for confirmation\n\nUnimplemented `docker builder prune` flags: `--filter`, `--keep-storage`\n\n### :nerd_face: nerdctl builder debug\n\nInteractive debugging of Dockerfile using [buildg](https://github.com/ktock/buildg).\nPlease refer to [`./builder-debug.md`](./builder-debug.md) for details.\nThis is an [experimental](./experimental.md) feature.\n\n:warning: This command currently doesn't use the host's `buildkitd` daemon but uses the patched version of BuildKit provided by buildg. This should be fixed in the future.\n\n:warning: This command is currently incompatible with `docker buildx debug`.\n\nUsage: `nerdctl builder debug PATH`\n\nFlags:\n\n- :nerd_face: `-f`, `--file`: Name of the Dockerfile\n- :nerd_face: `--image`: Image to use for debugging stage\n- :nerd_face: `--target`: Set the target build stage to build\n- :nerd_face: `--build-arg`: Set build-time variables\n\n## System\n\n### :whale: nerdctl events\n\nGet real time events from the server.\n\n:warning: The output format is not compatible with Docker.\n\nUsage: `nerdctl events [OPTIONS]`\n\nFlags:\n\n- :whale: `--format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :whale: `-f, --filter`: Filter containers based on given conditions\n  - :whale: `--filter event=<value>`: Event's status. Start is the only supported status.\n\nUnimplemented `docker events` flags: `--since`, `--until`\n\n### :whale: nerdctl info\n\nDisplay system-wide information\n\nUsage: `nerdctl info [OPTIONS]`\n\nFlags:\n\n- :whale: `-f, --format`: Format the output using the given Go template, e.g, `{{json .}}`\n- :nerd_face: `--mode=(dockercompat|native)`: Information mode. \"native\" produces more information.\n\n### :whale: nerdctl version\n\nShow the nerdctl version information\n\nUsage: `nerdctl version [OPTIONS]`\n\nFlags:\n\n- :whale: `-f, --format`: Format the output using the given Go template, e.g, `{{json .}}`\n\n### :whale: nerdctl system prune\n\nRemove unused data\n\nUsage: `nerdctl system prune [OPTIONS]`\n\nFlags:\n\n- :whale: `-a, --all`: Remove all unused images, not just dangling ones\n- :whale: `-f, --force`: Do not prompt for confirmation\n- :whale: `--volumes`: Prune volumes\n\nUnimplemented `docker system prune` flags: `--filter`\n\n## Stats\n\n### :whale: nerdctl stats\n\nDisplay a live stream of container(s) resource usage statistics.\n\nUsage: `nerdctl stats [OPTIONS]`\n\nFlags:\n\n- :whale: `-a, --all`: Show all containers (default shows just running)\n- :whale: `--format=FORMAT`: Pretty-print images using a Go template, e.g., `{{json .}}`\n- :whale: `--no-stream`: Disable streaming stats and only pull the first result\n- :whale: `--no-trunc`: Do not truncate output\n\n### :whale: nerdctl top\n\nDisplay the running processes of a container.\n\nUsage: `nerdctl top CONTAINER [ps OPTIONS]`\n\n## Shell completion\n\n### :nerd_face: nerdctl completion bash\n\nGenerate the autocompletion script for bash.\n\nUsage: add the following line to `~/.bash_profile`:\n\n```bash\nsource <(nerdctl completion bash)\n```\n\nOr run `nerdctl completion bash > /etc/bash_completion.d/nerdctl` as the root.\n\n### :nerd_face: nerdctl completion zsh\n\nGenerate the autocompletion script for zsh.\n\nUsage: see `nerdctl completion zsh --help`\n\n### :nerd_face: nerdctl completion fish\n\nGenerate the autocompletion script for fish.\n\nUsage: see `nerdctl completion fish --help`\n\n### :nerd_face: nerdctl completion powershell\n\nGenerate the autocompletion script for powershell.\n\nUsage: see `nerdctl completion powershell --help`\n\n## Compose\n\n### :whale: nerdctl compose\n\nCompose\n\nUsage: `nerdctl compose [OPTIONS] [COMMAND]`\n\nFlags:\n\n- :whale: `-f, --file`: Specify an alternate compose file\n- :whale: `-p, --project-name`: Specify an alternate project name\n- :whale: `--project-directory`: Specify an alternate working directory\n- :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default uses `$IPFS_PATH` env variable if defined or local directory `~/.ipfs`)\n- :whale: `--profile: Specify a profile to enable\n- :whale: `--env-file` : Specify an alternate environment file\n\n### :whale: nerdctl compose up\n\nCreate and start containers\n\nUsage: `nerdctl compose up [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `--abort-on-container-exit`: Stops all containers if any container was stopped. Incompatible with `-d`.\n- :whale: `-d, --detach`: Detached mode: Run containers in the background. Incompatible with `--abort-on-container-exit`.\n- :whale: `--no-build`: Don't build an image, even if it's missing.\n- :whale: `--no-color`: Produce monochrome output\n- :whale: `--no-log-prefix`: Don't print prefix in logs\n- :whale: `--build`: Build images before starting containers.\n- :nerd_face: `--ipfs`: Build images with pulling base images from IPFS. See [`ipfs.md`](./ipfs.md) for details.\n- :whale: `--quiet-pull`: Pull without printing progress information\n- :whale: `--scale`: Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.\n- :whale: `--remove-orphans`: Remove containers for services not defined in the Compose file\n- :whale: `--force-recreate`: force Compose to stop and recreate all containers\n- :whale: `--no-recreate`: force Compose to reuse existing containers\n- :whale: `--pull`: Pull image before running (\"always\"|\"missing\"|\"never\")\n\nUnimplemented `docker-compose up` (V1) flags: `--no-deps`, `--always-recreate-deps`,\n`--no-start`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--exit-code-from`\n\nUnimplemented `docker compose up` (V2) flags: `--environment`\n\n### :whale: nerdctl compose logs\n\nShow logs of running containers\n\nUsage: `nerdctl compose logs [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `--no-color`: Produce monochrome output\n- :whale: `--no-log-prefix`: Don't print prefix in logs\n- :whale: `-f, --follow`: Follow log output.\n- :whale: `--timestamps`: Show timestamps\n- :whale: `--tail`: Number of lines to show from the end of the logs\n\nUnimplemented `docker compose logs` (V2) flags:  `--since`, `--until`\n\n### :whale: nerdctl compose build\n\nBuild or rebuild services.\n\nUsage: `nerdctl compose build [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `--build-arg`: Set build-time variables for services\n- :whale: `--no-cache`: Do not use cache when building the image\n- :whale: `--progress`: Set type of progress output (auto, plain, tty). Use plain to show container output\n- :nerd_face: `--ipfs`: Build images with pulling base images from IPFS. See [`ipfs.md`](./ipfs.md) for details.\n\nUnimplemented `docker-compose build` (V1) flags:  `--compress`, `--force-rm`, `--memory`, `--no-rm`, `--parallel`, `--pull`, `--quiet`\n\n### :whale: nerdctl compose create\n\nCreates containers for one or more services.\n\nUsage: `nerdctl compose create [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `--build`: Build images before starting containers\n- :whale: `--force-recreate`: Recreate containers even if their configuration and image haven't changed\n- :whale: `--no-build`: Don't build an image even if it's missing, conflict with `--build`\n- :whale: `--no-recreate`: Don't recreate containers if they exist, conflict with `--force-recreate`\n- :whale: `--pull`: Pull images before running. (support always|missing|never) (default \"missing\")\n\n### :whale: nerdctl compose exec\n\nExecute a command on a running container of the service.\n\nUsage: `nerdctl compose exec [OPTIONS] SERVICE COMMAND [ARGS...]`\n\nFlags:\n\n- :whale: `-d, --detach`: Detached mode: Run the command in background\n- :whale: `-e, --env`: Set environment variables\n- :whale: `--index`: Set index of the container if the service has multiple instances. (default 1)\n- :whale: `-i, --interactive`: Keep STDIN open even if not attached (default true)\n- :whale: `--privileged`: Give extended privileges to the command\n- :whale: `-t, --tty`: Allocate a pseudo-TTY\n- :whale: `-T, --no-TTY`: Disable pseudo-TTY allocation. By default nerdctl compose exec allocates a TTY.\n- :whale: `-u, --user`: Username or UID (format: `<name|uid>[:<group|gid>]`)\n- :whale: `-w, --workdir`: Working directory inside the container\n\n### :whale: nerdctl compose down\n\nRemove containers and associated resources\n\nUsage: `nerdctl compose down [OPTIONS]`\n\nFlags:\n\n- :whale: `-v, --volumes`: Remove named volumes declared in the volumes section of the Compose file and anonymous volumes attached to containers\n- :whale: `--remove-orphans`: Remove containers of services not defined in the Compose file.\n\nUnimplemented `docker-compose down` (V1) flags: `--rmi`, `--timeout`\n\n### :whale: nerdctl compose images\n\nList images used by created containers in services\n\nUsage: `nerdctl compose images [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `-q, --quiet`: Only show numeric image IDs\n- :whale: `--format`: Format the output. Supported values: [json]\n\n### :whale: nerdctl compose start\n\nStart existing containers for service(s)\n\nUsage: `nerdctl compose start [SERVICE...]`\n\n### :whale: nerdctl compose stop\n\nStop containers in services without removing them.\n\nUsage: `nerdctl compose stop [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `-t, --timeout`: Seconds to wait for stop before killing it (default 10)\n\n### :whale: nerdctl compose port\n\nPrint the public port for a port binding of a service container\n\nUsage: `nerdctl compose port [OPTIONS] SERVICE PRIVATE_PORT`\n\nFlags:\n\n- :whale: `--index`: Index of the container if the service has multiple instances. (default 1)\n- :whale: `--protocol`: Protocol of the port (tcp|udp) (default \"tcp\")\n\n### :whale: nerdctl compose ps\n\nList containers of services\n\nUsage: `nerdctl compose ps [OPTIONS] [SERVICE...]`\n\n- :whale: `-a, --all`: Show all containers (default shows just running)\n- :whale: `-q, --quiet`: Only display container IDs\n- :whale: `--format`: Format the output\n  - :whale: `--format=table` (default): Table\n  - :whale: `--format=json'`: JSON\n- :whale: `-f, --filter`: Filter containers based on given conditions\n  - :whale: `--filter status=<value>`: One of `created, running, paused,\n    restarting, exited, pausing, unknown`. Note that `removing, dead` are\n    not supported and will be ignored\n- :whale: `--services`: Print the service names, one per line\n- :whale: `--status`: Filter containers by status. Values: [paused | restarting | running | created | exited | pausing | unknown]\n\n### :whale: nerdctl compose pull\n\nPull service images\n\nUsage: `nerdctl compose pull [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `-q, --quiet`: Pull without printing progress information\n\nUnimplemented `docker-compose pull` (V1) flags: `--ignore-pull-failures`, `--parallel`, `--no-parallel`, `include-deps`\n\n### :whale: nerdctl compose push\n\nPush service images\n\nUsage: `nerdctl compose push [OPTIONS] [SERVICE...]`\n\nUnimplemented `docker-compose pull` (V1) flags: `--ignore-push-failures`\n\n### :whale: nerdctl compose pause\n\nPause all processes within containers of service(s). They can be unpaused with `nerdctl compose unpause`\n\nUsage: `nerdctl compose pause [SERVICE...]`\n\n### :whale: nerdctl compose unpause\n\nUnpause all processes within containers of service(s)\n\nUsage: `nerdctl compose unpause [SERVICE...]`\n\n### :whale: nerdctl compose config\n\nValidate and view the Compose file\n\nUsage: `nerdctl compose config`\n\nFlags:\n\n- :whale: `-q, --quiet`: Pull without printing progress information\n- :whale: `--services`: Print the service names, one per line.\n- :whale: `--volumes`: Print the volume names, one per line.\n- :whale: `--hash=\"*\"`: Print the service config hash, one per line.\n\nUnimplemented `docker-compose config` (V1) flags: `--resolve-image-digests`, `--no-interpolate`\n\nUnimplemented `docker compose config` (V2) flags: `--resolve-image-digests`, `--no-interpolate`, `--format`, `--output`, `--profiles`\n\n### :whale: nerdctl compose cp\n\nCopy files/folders between a service container and the local filesystem\n\nUsage:\n```\nnerdctl compose cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-\nnerdctl compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH [flags]\n```\n\nFlags:\n- :whale: `--dry-run`: Execute command in dry run mode\n- :whale: `-L, --follow-link`: Always follow symbol link in SRC_PATH\n- :whale: `--index int`: index of the container if service has multiple replicas\n\nUnimplemented `docker compose cp` flags: `--archive`\n\n### :whale: nerdctl compose kill\n\nForce stop service containers\n\nUsage: `nerdctl compose kill [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `-s, --signal`: SIGNAL to send to the container (default: \"SIGKILL\")\n\n### :whale: nerdctl compose restart\n\nRestart containers of given (or all) services\n\nUsage: `nerdctl compose restart [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `-t, --timeout`: Seconds to wait before restarting it (default 10)\n\n### :whale: nerdctl compose rm\n\nRemove stopped service containers\n\nUsage: `nerdctl compose rm [OPTIONS] [SERVICE...]`\n\nFlags:\n\n- :whale: `-f, --force`: Don't prompt for confirmation (different with `-f` in `nerdctl rm` which means force deletion).\n- :whale: `-s, --stop`: Stop containers before removing.\n- :whale: `-v, --volumes`: Remove anonymous volumes associated with the container.\n\n### :whale: nerdctl compose run\n\nRun a one-off command on a service\n\nUsage: `nerdctl compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]`\n\nFlags:\n\n- :whale: `--build`: Build images before starting containers.\n- :whale: `-d, —detach`: Detached mode: Run containers in the background.\n- :whale: `--entrypoint`: Overwrite the default ENTRYPOINT of the image.\n- :whale: `-e, —env`: Set environment variables.\n- :whale: `-i, —interactive`: Keep STDIN open even if not attached (default true).\n- :whale: `-l, —label`: Set metadata on container.\n- :whale: `--name`: Assign a name to the container.\n- :whale: `--no-build`: Don't build an image, even if it's missing.\n- :whale: `--no-color`: Produce monochrome output.\n- :whale: `--no-deps`: Don't start dependencies.\n- :whale: `--no-log-prefix`: Don't print prefix in logs.\n- :whale: `--publish`: Publish a container's port(s) to the host.\n- :whale: `--quiet-pull`: Pull without printing progress information.\n- :whale: `--remove-orphans`: Remove containers for services not defined in the Compose file.\n- :whale: `--rm`: Automatically remove the container when it exits.\n- :whale: `--service-ports`: Run command with the service's ports enabled and mapped to the host.\n- :whale: `-u, —user`: Username or UID (format: <name|uid>[:<group|gid>]).\n- :whale: `-v, —volume`: Bind mount a volume.\n- :whale: `-w, —workdir`: Working directory inside the container.\n\nUnimplemented `docker-compose run` (V1) flags: `--use-aliases`, `--no-TTY`\n\nUnimplemented `docker compose run` (V2) flags: `--use-aliases`, `--no-TTY`, `--tty`\n\n### :whale: nerdctl compose top\n\nDisplay the running processes of service containers\n\nUsage: `nerdctl compose top [SERVICES...]`\n\n### :whale: nerdctl compose version\n\nShow the Compose version information (which is the nerdctl version)\n\nUsage: `nerdctl compose version`\n\nFlags:\n\n- :whale: `-f, --format`: Format the output. Values: [pretty | json] (default \"pretty\")\n- :whale: `--short`: Shows only Compose's version number\n\n## IPFS management\n\nP2P image distribution (IPFS) is completely optional. Your host is NOT connected to any P2P network, unless you opt in to [install and run IPFS daemon](https://docs.ipfs.io/install/).\n\n### :nerd_face: nerdctl ipfs registry serve\n\nServe read-only registry backed by IPFS on localhost.\nThis is needed to run `nerdctl build` with pulling base images from IPFS.\nOther commands (e.g. `nerdctl push ipfs://<image-name>` and `nerdctl pull ipfs://<CID>`) don't require this.\n\nYou need to install `ipfs` command on the host.\nSee [`ipfs.md`](./ipfs.md) for details.\n\nUsage: `nerdctl ipfs registry serve [OPTIONS]`\n\nFlags:\n\n- :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default is pulled from `$IPFS_PATH/api` file. If `$IPFS_PATH` env var is not present, it defaults to `~/.ipfs`).\n- :nerd_face: `--listen-registry`: Address to listen (default `localhost:5050`).\n- :nerd_face: `--read-retry-num`: Times to retry query on IPFS (default 0 (no retry))\n- :nerd_face: `--read-timeout`: Timeout duration of a read request to IPFS (default 0 (no timeout))\n\n## Global flags\n\n- :nerd_face: `--address`:  containerd address, optionally with \"unix://\" prefix\n- :nerd_face: `-a`, `--host`, `-H`: deprecated aliases of `--address`\n- :nerd_face: `--namespace`: containerd namespace\n- :nerd_face: `-n`: deprecated alias of `--namespace`\n- :nerd_face: `--snapshotter`: containerd snapshotter\n- :nerd_face: `--storage-driver`: deprecated alias of `--snapshotter`\n- :nerd_face: `--cni-path`: CNI binary path (default: `/opt/cni/bin`) [`$CNI_PATH`]\n- :nerd_face: `--cni-netconfpath`: CNI netconf path (default: `/etc/cni/net.d`) [`$NETCONFPATH`]\n- :nerd_face: `--data-root`: nerdctl data root, e.g. \"/var/lib/nerdctl\"\n- :nerd_face: `--cgroup-manager=(cgroupfs|systemd|none)`: cgroup manager\n  - Default: \"systemd\" on cgroup v2 (rootful & rootless), \"cgroupfs\" on v1 rootful, \"none\" on v1 rootless\n- :nerd_face: `--insecure-registry`: skips verifying HTTPS certs, and allows falling back to plain HTTP\n- :nerd_face: `--host-gateway-ip`: IP address that the special 'host-gateway' string in --add-host resolves to. It has no effect without setting --add-host\n  - Default: the IP address of the host\n- :nerd_face: `--userns-remap=<username>:<groupname>`: Support idmapping of containers. This options is only supported on rootful linux for container create and run if a user name and optionally group name is passed, it does idmapping based on the uidmap and gidmap ranges specified in /etc/subuid and /etc/subgid respectively. Note: `--userns-remap` is not supported for building containers. Nerdctl Build doesn't support userns-remap feature. (format: <name|uid>[:<group|gid>])\n\nThe global flags can be also specified in `/etc/nerdctl/nerdctl.toml` (rootful) and `~/.config/nerdctl/nerdctl.toml` (rootless).\nSee [`./config.md`](./config.md).\n\n## Unimplemented Docker commands\n\nImage:\n\n- `docker trust *` (Instead, nerdctl supports `nerdctl pull --verify=cosign|notation` and `nerdctl push --sign=cosign|notation`. See [`./cosign.md`](./cosign.md) and [`./notation.md`](./notation.md).)\n\nNetwork management:\n\n- `docker network connect`\n- `docker network disconnect`\n\nCompose:\n\n- `docker-compose events|scale`\n\nBuilder:\n\n- `docker buildx debug` (buildx debugger)\n\nOthers:\n\n- `docker system df`\n- `docker context`\n- Swarm commands are unimplemented and will not be implemented: `docker swarm|node|service|config|secret|stack *`\n- Plugin commands are unimplemented and will not be implemented: `docker plugin *`\n"
  },
  {
    "path": "docs/compose.md",
    "content": "# nerdctl compose\n\n| :zap: Requirement | nerdctl >= 0.8 |\n|-------------------|----------------|\n\n## Usage\n\nThe `nerdctl compose` CLI is designed to be compatible with `docker-compose`.\n\n```console\n$ nerdctl compose up -d\n$ nerdctl compose down\n```\n\nSee the Command Reference in [`../README.md`](../README.md).\n\n## Spec conformance\n\n`nerdctl compose` implements [The Compose Specification](https://github.com/compose-spec/compose-spec),\nwhich was derived from [Docker Compose file version 3 specification](https://docs.docker.com/compose/compose-file/compose-file-v3/).\n\n### Unimplemented YAML fields\n- Fields that correspond to unimplemented `docker run` flags, e.g., `services.<SERVICE>.links` (corresponds to `docker run --link`)\n- Fields that correspond to unimplemented `docker build` flags, e.g., `services.<SERVICE>.build.extra_hosts` (corresponds to `docker build --add-host`)\n- `services.<SERVICE>.credential_spec`\n- `services.<SERVICE>.deploy.update_config`\n- `services.<SERVICE>.deploy.rollback_config`\n- `services.<SERVICE>.deploy.resources.reservations`\n- `services.<SERVICE>.deploy.placement`\n- `services.<SERVICE>.deploy.endpoint_mode`\n- `services.<SERVICE>.healthcheck`\n- `services.<SERVICE>.stop_grace_period`\n- `services.<SERVICE>.stop_signal`\n- `configs.<CONFIG>.external`\n- `secrets.<SECRET>.external`\n\n### Incompatibility\n#### `services.<SERVICE>.build.context`\n- The value must be a local directory path, not a URL.\n\n#### `services.<SERVICE>.secrets`, `services.<SERVICE>.configs`\n- `uid`, `gid`: Cannot be specified. The default value is not propagated from `USER` instruction of Dockerfile.\n  The file owner corresponds to the original file on the host.\n- `mode`: Cannot be specified. The file is mounted as read-only, with permission bits that correspond to the original file on the host.\n"
  },
  {
    "path": "docs/config.md",
    "content": "# Configuring nerdctl with `nerdctl.toml`\n\n| :zap: Requirement | nerdctl >= 0.16 |\n|-------------------|-----------------|\n\nThis document describes the configuration file of nerdctl (`nerdctl.toml`).\nThis file is unrelated to the configuration file of containerd (`config.toml`) .\n\n## File path\n- Rootful mode:  `/etc/nerdctl/nerdctl.toml`\n- Rootless mode: `~/.config/nerdctl/nerdctl.toml`\n\nThe path can be overridden with `$NERDCTL_TOML`.\n\n## Example\n\n```toml\n# This is an example of /etc/nerdctl/nerdctl.toml .\n# Unrelated to the daemon's /etc/containerd/config.toml .\n\ndebug          = false\ndebug_full     = false\naddress        = \"unix:///run/k3s/containerd/containerd.sock\"\nnamespace      = \"k8s.io\"\nsnapshotter    = \"stargz\"\ncgroup_manager = \"cgroupfs\"\nhosts_dir      = [\"/etc/containerd/certs.d\", \"/etc/docker/certs.d\"]\nexperimental   = true\nuserns_remap   = \"\"\ndns            = [\"8.8.8.8\", \"1.1.1.1\"]\ndns_opts       = [\"ndots:1\", \"timeout:2\"]\ndns_search     = [\"example.com\", \"example.org\"]\n```\n\n## Properties\n\n| TOML property       | CLI flag                           | Env var                   | Description                                                                                                                                                      | Availability |\n|---------------------|------------------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|\n| `debug`             | `--debug`                          |                           | Debug mode                                                                                                                                                       | Since 0.16.0     |\n| `debug_full`        | `--debug-full`                     |                           | Debug mode (with full output)                                                                                                                                    | Since 0.16.0     |\n| `address`           | `--address`,`--host`,`-a`,`-H`     | `$CONTAINERD_ADDRESS`     | containerd address                                                                                                                                               | Since 0.16.0     |\n| `namespace`         | `--namespace`,`-n`                 | `$CONTAINERD_NAMESPACE`   | containerd namespace                                                                                                                                             | Since 0.16.0     |\n| `snapshotter`       | `--snapshotter`,`--storage-driver` | `$CONTAINERD_SNAPSHOTTER` | containerd snapshotter                                                                                                                                           | Since 0.16.0     |\n| `cni_path`          | `--cni-path`                       | `$CNI_PATH`               | CNI binary directory                                                                                                                                             | Since 0.16.0     |\n| `cni_netconfpath`   | `--cni-netconfpath`                | `$NETCONFPATH`            | CNI config directory                                                                                                                                             | Since 0.16.0     |\n| `data_root`         | `--data-root`                      |                           | Persistent state directory                                                                                                                                       | Since 0.16.0     |\n| `cgroup_manager`    | `--cgroup-manager`                 |                           | cgroup manager                                                                                                                                                   | Since 0.16.0     |\n| `insecure_registry` | `--insecure-registry`              |                           | Allow insecure registry                                                                                                                                          | Since 0.16.0     |\n| `hosts_dir`         | `--hosts-dir`                      |                           | `certs.d` directory                                                                                                                                              | Since 0.16.0     |\n| `experimental`      | `--experimental`                   | `NERDCTL_EXPERIMENTAL`    | Enable  [experimental features](experimental.md)                                                                                                                 | Since 0.22.3     |\n| `host_gateway_ip`   | `--host-gateway-ip`                | `NERDCTL_HOST_GATEWAY_IP` | IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host. It has no effect without setting --add-host | Since 1.3.0      |\n| `bridge_ip`         | `--bridge-ip`                      | `NERDCTL_BRIDGE_IP`       | IP address for the default nerdctl bridge network, e.g., 10.1.100.1/24                                                                                           | Since 2.0.1      |\n| `kube_hide_dupe`    | `--kube-hide-dupe`                 |                           | Deduplicate images for Kubernetes with namespace k8s.io, no more redundant <none> ones are displayed    | Since 2.0.3      |\n| `cdi_spec_dirs`     | `--cdi-spec-dirs`                   |                          | The folders to use when searching for CDI ([container-device-interface](https://github.com/cncf-tags/container-device-interface)) specifications.    | Since 2.1.0 |\n| `userns_remap`      | `--userns-remap`                   |                           | Support idmapping of containers. This options is only supported on rootful linux. If `host` is passed, no idmapping is done. if a user name is passed, it does idmapping based on the uidmap and gidmap ranges specified in /etc/subuid and /etc/subgid respectively. |   Since 2.1.0 |\n| `dns`               |                                    |                           | Set global DNS servers for containers                                                                                                                  | Since 2.1.3 |\n| `dns_opts`          |                                    |                           | Set global DNS options for containers                                                                                                                         | Since 2.1.3 |\n| `dns_search`        |                                    |                           | Set global DNS search domains for containers                                                                                                           | Since 2.1.3 |\n\nThe properties are parsed in the following precedence:\n1. CLI flag\n2. Env var\n3. TOML property\n4. Built-in default value (Run `nerdctl --help` to see the default values)\n\n\n## See also\n- [`registry.md`](registry.md)\n- [`faq.md`](faq.md)\n- https://github.com/containerd/containerd/blob/main/docs/ops.md#base-configuration (`/etc/containerd/config.toml`)\n"
  },
  {
    "path": "docs/cosign.md",
    "content": "# Container Image Sign and Verify with cosign tool\n\n| :zap: Requirement | nerdctl >= 0.15 |\n|-------------------|-----------------|\n\n[cosign](https://github.com/sigstore/cosign) is tool that allows you to sign and verify container images with the\npublic/private key pairs or without them by providing\na [Keyless support](https://github.com/sigstore/cosign/blob/main/KEYLESS.md).\n\nKeyless uses ephemeral keys and certificates, which are signed automatically by\nthe [fulcio](https://github.com/sigstore/fulcio) root CA. Signatures are stored in\nthe [rekor](https://github.com/sigstore/rekor) transparency log, which automatically provides an attestation as to when\nthe signature was created.\n\nCosign would use prompt to confirm the statement below during `sign`. Nerdctl added `--yes` to Cosign command, which says yes and prevents this prompt.\nUsing Nerdctl push with signing by Cosign means that users agree the statement.\n\n\n```\nNote that there may be personally identifiable information associated with this signed artifact.\nThis may include the email address associated with the account with which you authenticate.\nThis information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later.\n\nBy typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs.\n```\n\nYou can enable container signing and verifying features with `push` and `pull` commands of `nerdctl` by using `cosign`\nunder the hood with make use of flags `--sign` while pushing the container image, and `--verify` while pulling the\ncontainer image.\n\n> * Ensure cosign executable in your `$PATH`.\n> * You can install cosign by following this page: https://docs.sigstore.dev/cosign/installation\n\nPrepare your environment:\n\n```shell\n# Create a sample Dockerfile\n$ cat <<EOF | tee Dockerfile.dummy\nFROM alpine:latest\nCMD [ \"echo\", \"Hello World\" ]\nEOF\n```\n\n> Please do not forget, we won't be validating the base images, which is `alpine:latest` in this case, of the container image that was built on,\n> we'll only verify the container image itself once we sign it.\n\n```shell\n\n# Build the image\n$ nerdctl build -t devopps/hello-world -f Dockerfile.dummy .\n\n# Generate a key-pair: cosign.key and cosign.pub\n$ cosign generate-key-pair\n\n# Export your COSIGN_PASSWORD to prevent CLI prompting\n$ export COSIGN_PASSWORD=$COSIGN_PASSWORD\n```\n\nSign the container image while pushing:\n\n```\n# Sign the image with Keyless mode\n$ nerdctl push --sign=cosign devopps/hello-world\n\n# Sign the image and store the signature in the registry\n$ nerdctl push --sign=cosign --cosign-key cosign.key devopps/hello-world\n```\n\nVerify the container image while pulling:\n\n> REMINDER: Image won't be pulled if there are no matching signatures in case you passed `--verify` flag.\n\n> REMINDER: For keyless flows to work, you need to set either --cosign-certificate-identity or --cosign-certificate-identity-regexp, and either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp. The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth.\n\n```shell\n# Verify the image with Keyless mode\n$ nerdctl pull --verify=cosign --certificate-identity=name@example.com --certificate-oidc-issuer=https://accounts.example.com devopps/hello-world\nINFO[0004] cosign:\nINFO[0004] cosign: [{\"critical\":{\"identity\":...}]\ndocker.io/devopps/nginx-new:latest:                                               resolved       |++++++++++++++++++++++++++++++++++++++|\nmanifest-sha256:0910d404e58dd320c3c0c7ea31bf5fbfe7544b26905c5eccaf87c3af7bcf9b88: done           |++++++++++++++++++++++++++++++++++++++|\nconfig-sha256:1de1c4fb5122ac8650e349e018fba189c51300cf8800d619e92e595d6ddda40e:   done           |++++++++++++++++++++++++++++++++++++++|\nelapsed: 1.4 s                                                                    total:  1.3 Ki (928.0 B/s)\n\n# You can not verify the image if it is not signed\n$ nerdctl pull --verify=cosign --cosign-key cosign.pub devopps/hello-world-bad\nINFO[0003] cosign: Error: no matching signatures:\nINFO[0003] cosign: failed to verify signature\nINFO[0003] cosign: main.go:46: error during command execution: no matching signatures:\nINFO[0003] cosign: failed to verify signature\n```\n\n## Cosign in Compose\n\n> Cosign support in Compose is also experimental and implemented based on Compose's [extension](https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension) capibility.\n\ncosign is supported in `nerdctl compose up|run|push|pull`. You can use cosign in Compose by adding the following fields in your compose yaml. These fields are _per service_, and you can enable only `verify` or only `sign` (or both).\n\n```yaml\n# only put cosign related fields under the service you want to sign/verify.\nservices:\n  svc0:\n    build: .\n    image: ${REGISTRY}/svc0_image # replace with your registry\n    # `x-nerdctl-verify` and `x-nerdctl-cosign-public-key` are for verify\n    # required for `nerdctl compose up|run|pull`\n    x-nerdctl-verify: cosign\n    x-nerdctl-cosign-public-key: /path/to/cosign.pub\n    # `x-nerdctl-sign` and `x-nerdctl-cosign-private-key` are for sign\n    # required for `nerdctl compose push`\n    x-nerdctl-sign: cosign\n    x-nerdctl-cosign-private-key: /path/to/cosign.key\n    ports:\n    - 8080:80\n  svc1:\n    build: .\n    image: ${REGISTRY}/svc1_image # replace with your registry\n    ports:\n    - 8081:80\n```\n\nFollowing the cosign tutorial above, first set up environment and prepare cosign key pair:\n\n```shell\n# Generate a key-pair: cosign.key and cosign.pub\n$ cosign generate-key-pair\n\n# Export your COSIGN_PASSWORD to prevent CLI prompting\n$ export COSIGN_PASSWORD=$COSIGN_PASSWORD\n```\n\nWe'll use the following `Dockerfile` and `docker-compose.yaml`:\n\n```shell\n$ cat Dockerfile\nFROM nginx:1.19-alpine\nRUN uname -m > /usr/share/nginx/html/index.html\n\n$ cat docker-compose.yml\nservices:\n  svc0:\n    build: .\n    image: ${REGISTRY}/svc1_image # replace with your registry\n    x-nerdctl-verify: cosign\n    x-nerdctl-cosign-public-key: ./cosign.pub\n    x-nerdctl-sign: cosign\n    x-nerdctl-cosign-private-key: ./cosign.key\n    ports:\n    - 8080:80\n  svc1:\n    build: .\n    image: ${REGISTRY}/svc1_image # replace with your registry\n    ports:\n    - 8081:80\n```\n\nFor keyless mode, the `docker-compose.yaml` will be:\n```\n$ cat docker-compose.yml\nservices:\n  svc0:\n    build: .\n    image: ${REGISTRY}/svc1_image # replace with your registry\n    x-nerdctl-verify: cosign\n    x-nerdctl-sign: cosign\n    x-nerdctl-cosign-certificate-identity: name@example.com # or x-nerdctl-cosign-certificate-identity-regexp\n    x-nerdctl-cosign-certificate-oidc-issuer: https://accounts.example.com # or x-nerdctl-cosign-certificate-oidc-issuer-regexp\n    ports:\n    - 8080:80\n  svc1:\n    build: .\n    image: ${REGISTRY}/svc1_image # replace with your registry\n    ports:\n    - 8081:80\n```\n\n> The `env \"COSIGN_PASSWORD=\"$COSIGN_PASSWORD\"\"` part in the below commands is a walkaround to use rootful nerdctl and make the env variable visible to root (in sudo). You don't need this part if (1) you're using rootless, or (2) your `COSIGN_PASSWORD` is visible in root.\n\nFirst let's `build` and `push` the two services:\n\n```shell\n$ sudo nerdctl compose build\nINFO[0000] Building image xxxxx/svc0_image\n...\nINFO[0000] Building image xxxxx/svc1_image\n[+] Building 0.2s (6/6) FINISHED\n\n$ sudo env \"COSIGN_PASSWORD=\"$COSIGN_PASSWORD\"\" nerdctl compose --experimental=true push\nINFO[0000] Pushing image xxxxx/svc1_image\n...\nINFO[0000] Pushing image xxxxx/svc0_image\nINFO[0000] pushing as a reduced-platform image (application/vnd.docker.distribution.manifest.v2+json, sha256:4329abc3143b1545835de17e1302c8313a9417798b836022f4c8c8dc8b10a3e9)\nINFO[0000] cosign: WARNING: Image reference xxxxx/svc0_image uses a tag, not a digest, to identify the image to sign.\nINFO[0000] cosign:\nINFO[0000] cosign: This can lead you to sign a different image than the intended one. Please use a\nINFO[0000] cosign: digest (example.com/ubuntu@sha256:abc123...) rather than tag\nINFO[0000] cosign: (example.com/ubuntu:latest) for the input to cosign. The ability to refer to\nINFO[0000] cosign: images by tag will be removed in a future release.\nINFO[0000] cosign: Pushing signature to: xxxxx/svc0_image\n```\n\nThen we can `pull` and `up` services (`run` is similar to up):\n\n```shell\n# ensure built images are removed and pull is performed.\n$ sudo nerdctl compose down\n$ sudo env \"COSIGN_PASSWORD=\"$COSIGN_PASSWORD\"\" nerdctl compose --experimental=true pull\n$ sudo env \"COSIGN_PASSWORD=\"$COSIGN_PASSWORD\"\" nerdctl compose --experimental=true up\n$ sudo env \"COSIGN_PASSWORD=\"$COSIGN_PASSWORD\"\" nerdctl compose --experimental=true run svc0 -- echo \"hello\"\n# clean up compose resources.\n$ sudo nerdctl compose down\n```\n\nCheck your logs to confirm that svc0 is verified by cosign (have cosign logs) and svc1 is not. You can also change the public key in `docker-compose.yaml` to a random value to see verify failure will stop the container being `pull|up|run`."
  },
  {
    "path": "docs/cvmfs.md",
    "content": "# Lazy-pulling using CernVM-FS Snapshotter\n\nCernVM-FS Snapshotter is a containerd snapshotter plugin. It is a specialized component responsible for assembling\nall the layers of container images into a stacked file system that containerd can use. The snapshotter takes as input the list\nof required layers and outputs a directory containing the final file system. It is also responsible to clean up the output\ndirectory when containers using it are stopped.\n\nSee the official [documentation](https://cvmfs.readthedocs.io/en/latest/cpt-containers.html#how-to-use-the-cernvm-fs-snapshotter) to learn further information.\n\n## Prerequisites\n\n- Install containerd remote snapshotter plugin (`cvmfs-snapshotter`) from [here](https://github.com/cvmfs/cvmfs/tree/devel/snapshotter).\n\n- Add the following to `/etc/containerd/config.toml`:\n```toml\n# Ask containerd to use this particular snapshotter\n[plugins.\"io.containerd.grpc.v1.cri\".containerd]\n    snapshotter = \"cvmfs-snapshotter\"\n    disable_snapshot_annotations = false\n\n# Set the communication endpoint between containerd and the snapshotter\n[proxy_plugins]\n    [proxy_plugins.cvmfs]\n        type = \"snapshot\"\n        address = \"/run/containerd-cvmfs-grpc/containerd-cvmfs-grpc.sock\"\n```\n- The default CernVM-FS repository hosting the flat root filesystems of the container images is `unpacked.cern.ch`.\n  The container images are unpacked into the CernVM-FS repository by the [DUCC](https://cvmfs.readthedocs.io/en/latest/cpt-ducc.html)\n  (Daemon that Unpacks Container Images into CernVM-FS) tool.\n  You can change the repository adding the following line to `/etc/containerd-cvmfs-grpc/config.toml`:\n```toml\nrepository = \"myrepo.mydomain\"\n```\n- Launch `containerd` and `cvmfs-snapshotter`:\n```console\n$ systemctl start containerd cvmfs-snapshotter\n```\n\n## Enable CernVM-FS Snapshotter for `nerdctl run` and `nerdctl pull`\n\n| :zap: Requirement | nerdctl >= 1.6.3 |\n| ----------------- | ---------------- |\n\n- Run `nerdctl` with `--snapshotter cvmfs-snapshotter` as in the example below:\n```console\n$ nerdctl run -it --rm --snapshotter cvmfs-snapshotter clelange/cms-higgs-4l-full:latest\n```\n\n- You can also only pull the image with CernVM-FS Snapshotter without running the container:\n```console\n$ nerdctl pull --snapshotter cvmfs-snapshotter clelange/cms-higgs-4l-full:latest\n```\n\nThe speedup for pulling this 9 GB (4.3 GB compressed) image is shown below:\n- #### with the snapshotter:\n```console\n$ nerdctl --snapshotter cvmfs-snapshotter pull clelange/cms-higgs-4l-full:latest\ndocker.io/clelange/cms-higgs-4l-full:latest:                                      resolved       |++++++++++++++++++++++++++++++++++++++|\nmanifest-sha256:b8acbe80629dd28d213c03cf1ffd3d46d39e573f54215a281fabce7494b3d546: done           |++++++++++++++++++++++++++++++++++++++|\nconfig-sha256:89ef54b6c4fbbedeeeb29b1df2b9916b6d157c87cf1878ea882bff86a3093b5c:   done           |++++++++++++++++++++++++++++++++++++++|\nelapsed: 4.7 s                                                                    total:  19.8 K (4.2 KiB/s)\n\n$ nerdctl images\nREPOSITORY                    TAG       IMAGE ID        CREATED           PLATFORM       SIZE     BLOB SIZE\nclelange/cms-higgs-4l-full    latest    b8acbe80629d    20 seconds ago    linux/amd64    0.0 B    4.3 GiB\n```\n- #### without the snapshotter:\n```console\n$ nerdctl pull clelange/cms-higgs-4l-full:latest\ndocker.io/clelange/cms-higgs-4l-full:latest:                                      resolved       |++++++++++++++++++++++++++++++++++++++|\nmanifest-sha256:b8acbe80629dd28d213c03cf1ffd3d46d39e573f54215a281fabce7494b3d546: exists         |++++++++++++++++++++++++++++++++++++++|\nconfig-sha256:89ef54b6c4fbbedeeeb29b1df2b9916b6d157c87cf1878ea882bff86a3093b5c:   exists         |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:e8114d4b0d10b33aaaa4fbc3c6da22bbbcf6f0ef0291170837e7c8092b73840a:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:a3eda0944a81e87c7a44b117b1c2e707bc8d18e9b7b478e21698c11ce3e8b819:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:8f3160776e8e8736ea9e3f6c870d14cd104143824bbcabe78697315daca0b9ad:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:22a5c05baa9db0aa7bba56ffdb2dd21246b9cf3ce938fc6d7bf20e92a067060e:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:bfcf9d498f92b72426c9d5b73663504d87249d6783c6b58d71fbafc275349ab9:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:0563e1549926b9c8beac62407bc6a420fa35bcf6f9844e5d8beeb9165325a872:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:6fff5fd7fb4eeb79a1399d9508614a84191d05e53f094832062d689245599640:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:25c39bfa66e1157415236703abc512d06cc1db31bd00fe8c3030c6d6d249dc4e:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:3cc0a0eb55eb3fb7ef0760c6bf1e567dfc56933ba5f11b5415f89228af751b72:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:a8850244786303e508b94bb31c8569310765e678c9c73bf1199310729209b803:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:32cdf5fc12485ac061347eb8b5c3b4a28505ce8564a7f3f83ac4241f03911176:    done           |++++++++++++++++++++++++++++++++++++++|\nelapsed: 181.8s                                                                   total:  4.3 Gi (24.2 MiB/s)\n\n$ nerdctl images\nREPOSITORY                    TAG       IMAGE ID        CREATED          PLATFORM       SIZE       BLOB SIZE\nclelange/cms-higgs-4l-full    latest    b8acbe80629d    4 minutes ago    linux/amd64    9.0 GiB    4.3 GiB\n```\n"
  },
  {
    "path": "docs/dev/auditing_dockerfile.md",
    "content": "# Auditing dockerfile\n\nBecause of the nature of GitHub cache, and the time it takes to build the dockerfile for testing, it is desirable\nto be able to audit what is going on there.\n\nThis document provides a few pointers on how to do that, and some results as of 2025-02-26 (run inside lima, nerdctl main,\non a macbook pro M1).\n\n## Intercept network traffic\n\n### On macOS\n\nUse Charles:\n- start SSL proxying\n- enable SOCKS proxy\n- export the root certificate\n\n### On linux\n\nLeft as an exercise to the reader.\n\n### If using lima\n\n- restart your lima instance with `HTTP_PROXY=http://X.Y.Z.W:8888 HTTPS_PROXY=socks5://X.Y.Z.W:8888 limactl start instance` - where XYZW\nis the local ip of the Charles proxy (non-localhost)\n\n### On the host where you are running containerd\n\n- copy the root certificate from above into `/usr/local/share/ca-certificates/charles-ssl-proxying-certificate.crt`\n- update your host: `sudo update-ca-certificates`\n- now copy the root certificate again to your current nerdctl clone\n\n### Hack the dockerfile to insert our certificate\n\nAdd the following stages in the dockerfile:\n```dockerfile\nFROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-trixie AS hack-build-base-debian\nRUN apt-get update -qq; apt-get -qq install ca-certificates\nCOPY charles-ssl-proxying-certificate.crt /usr/local/share/ca-certificates/\nRUN update-ca-certificates\n\nFROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS hack-build-base\nRUN apk add --no-cache ca-certificates\nCOPY charles-ssl-proxying-certificate.crt /usr/local/share/ca-certificates/\nRUN update-ca-certificates\n\nFROM ubuntu:${UBUNTU_VERSION} AS hack-base\nRUN apt-get update -qq; apt-get -qq install ca-certificates\nCOPY charles-ssl-proxying-certificate.crt /usr/local/share/ca-certificates/\nRUN update-ca-certificates\n```\n\nThen replace any later \"FROM\" with our modified bases:\n```\ngolang:${GO_VERSION}-trixie => hack-build-base-debian\ngolang:${GO_VERSION}-alpine => hack-build-base\nubuntu:${UBUNTU_VERSION} => hack-base\n```\n\n## Mimicking what the CI is doing\n\nA quick helper:\n\n```bash\nrun(){\n  local no_cache=\"${1:-}\"\n  local platform=\"${2:-arm64}\"\n  local dockerfile=\"${3:-Dockerfile}\"\n  local target=\"${4:-test-integration}\"\n\n  local cache_shard=\"$CONTAINERD_VERSION\"-\"$platform\"\n  local shard=\"$cache_shard\"-\"$target\"-\"$UBUNTU_VERSION\"-\"$no_cache\"-\"$dockerfile\"\n\n  local cache_location=$HOME/bk-cache-\"$cache_shard\"\n  local destination=$HOME/bk-output-\"$shard\"\n  local logs=\"$HOME\"/bk-debug-\"$shard\"\n\n  if [ \"$no_cache\" != \"\" ]; then\n    nerdctl system prune -af\n    nerdctl builder prune -af\n    rm -Rf \"$cache_location\"\n  fi\n\n  nerdctl build \\\n    --build-arg UBUNTU_VERSION=\"$UBUNTU_VERSION\" \\\n    --build-arg CONTAINERD_VERSION=\"$CONTAINERD_VERSION\" \\\n    --platform=\"$platform\" \\\n    --output type=tar,dest=\"$destination\" \\\n    --progress plain \\\n    --build-arg HTTP_PROXY=$HTTP_PROXY \\\n    --build-arg HTTPS_PROXY=$HTTPS_PROXY \\\n    --cache-to type=local,dest=\"$cache_location\",mode=max \\\n    --cache-from type=local,src=\"$cache_location\" \\\n    --target \"$target\" \\\n    -f \"$dockerfile\" . 2>&1 | tee \"$logs\"\n}\n```\n\nAnd here is what the CI is doing:\n\n```bash\nci_run(){\n  local no_cache=\"${1:-}\"\n  export UBUNTU_VERSION=24.04\n\n  # The actual version may differ\n  CONTAINERD_VERSION=v1.7.25 run \"$no_cache\"  arm64 Dockerfile.origin build-dependencies\n  UBUNTU_VERSION=22.04 CONTAINERD_VERSION=v1.7.25 run \"\" arm64 Dockerfile.origin test-integration\n\n  CONTAINERD_VERSION=v2.0.3 run \"$no_cache\"  arm64 Dockerfile.origin build-dependencies\n  UBUNTU_VERSION=24.04 CONTAINERD_VERSION=v2.0.3 run \"\" arm64 Dockerfile.origin test-integration\n\n  CONTAINERD_VERSION=v2.0.3 run \"$no_cache\"  amd64 Dockerfile.origin build-dependencies\n  UBUNTU_VERSION=24.04 CONTAINERD_VERSION=v2.0.3 run \"\" amd64 Dockerfile.origin test-integration\n}\n\n# To simulate what happens when there is no cache, go with:\nci_run no_cache\n\n# Once you have a cached run, you can simulate what happens with cache\n# First modify something in the nerdctl tree\n# Then run it\ntouch mimick_nerdctl_change\nci_run\n```\n\n## Analyzing results\n\n### Network\n\n#### Full CI run, cold cache (the first three pipelines, and part of the fourth)\n\nThe following numbers are based on the above script, with cold cache.\n\nUnfortunately golang did segfault on me during the last (cross-run targetting amd), so, these numbers should be taken\nas (slightly) underestimated.\n\nTotal number of requests: 7190\n\nTotal network duration: 13 minutes 11 seconds\n\nOutbound: 1.31MB\n\nInbound: 5202MB\n\nBreakdown per domain\n\n| Destination                                  | # requests        | through    | duration        |\n|----------------------------------------------|-------------------|------------|-----------------|\n| https://registry-1.docker.io                 | 123 (2 failed)    | 1.22MB     | 26s             |\n| https://production.cloudflare.docker.com     | 60                | 1242.41MB  | 2m6s            |\n| http://deb.debian.org                        | 207               | 107.14MB   | 13s             |\n| https://github.com                           | 105               | 977.88MB   | 1m25s           |\n| https://proxy.golang.org                     | 5343 (57 failed)  | 753.69MB   | 4m8s            |\n| https://objects.githubusercontent.com        | 42                | 900.22MB   | 50s             |\n| https://raw.githubusercontent.com            | 8                 | 92KB       | 2s              |\n| https://storage.googleapis.com               | 19 (3 failed)     | 537.21MB   | 35s             |\n| https://ghcr.io                              | 65                | 588.68KB   | 13s             |\n| https://auth.docker.io                       | 10                | 259KB      | 5s              |\n| https://pkg-containers.githubusercontent.com | 48                | 183.63MB   | 20s             |\n| http://ports.ubuntu.com                      | 300               | 165.36MB   | 1m55s           |\n| https://golang.org                           | 4                 | 228.93KB   | <1s             |\n| https://go.dev                               | 4                 | 95.51KB    | <1s             |\n| https://dl.google.com                        | 4                 | 271.42MB   | 11s             |\n| https://sum.golang.org                       | 746               | 3.89MB     | 17s             |\n| http://security.ubuntu.com                   | 7                 | 2.70MB     | 3s              |\n| http://archive.ubuntu.com                    | 95                | 55.95MB    | 19s             |\n|                                              | -                 | -          | -               |\n| Total                                        | 7190              | 5203MB     | 13 mins 11 secs |\n\n\n#### Full CI run, warm cache (only the first three pipelines)\n\n| Destination                              | # requests       | through | duration       |\n|------------------------------------------|------------------|---------|----------------|\n| https://registry-1.docker.io             | 25               | 537KB   | 14s            |\n| https://production.cloudflare.docker.com | 2                | 25MB    | 1s             |\n| https://github.com                       | 7 (1 failed)     | 105KB   | 2s             |\n| https://proxy.golang.org                 | 930 (11 failed)  | 150MB   | 37s            |\n| https://objects.githubusercontent.com    | 4                | 86MB    | 4s             |\n| https://storage.googleapis.com           | 3                | 112MB   | 6s             |\n| https://auth.docker.io                   | 1                | 26KB    | <1s            |\n| http://ports.ubuntu.com                  | 133              | 67MB    | 50s            |\n| https://golang.org                       | 2                | 114KB   | <1s            |\n| https://go.dev                           | 2                | 45KB    | <1s            |\n| https://dl.google.com                    | 2                | 134MB   | 5s             |\n| https://sum.golang.org                   | 484              | 3MB     | 11s            |\n|                                          | -                | -       | -              |\n| Total                                    | 1595 (12 failed) | 579MB   | 2 mins 10 secs |\n\n\n#### Analysis\n\n##### Docker Hub\n\nImages from Docker Hub are clearly a source of concern (made even worse by the fact they apply strict limitations on the\nnumber of requests permitted).\n\nWhen the cache is cold, this is about 1GB per run, for 200 requests and 3 minutes.\n\nActions:\n- [ ] reduce the number of images\n  - we currently use 2 golang images, which does not make sense\n- [ ] reduce the round trips\n  - there is no reason why any of the images should be queried more than once per build\n- [ ] move away from Hub golang image, and instead use a raw distro + golang download\n  - Hub golang is a source of pain and issues (diverging version scheme forces ugly shell contorsions, delay in availability creates\nbroken situations)\n  - we are already downloading the go release tarball anyhow, so, this is just wasted bandwidth with no added value\n\nSuccess criteria:\n- on a cold cache, reduce the total number of requests against Docker properties by 50% or more\n- on a cold cache, cut the data transfer and time in half\n\n##### Distro packages\n\nOn a WARM cache, close to 1 minute is spent fetching Ubuntu packages.\nThis should not happen, and distro downloads should always be cached.\n\nOn a cold cache, distro packages download near 3 minutes.\nVery likely there is stage duplication that could be reduced and some of that could be cut of.\n\nActions:\n- [ ] ensure distro package downloading is staged in a way we can cache it\n- [ ] review stages to reduce package installation duplication\n\nSuccess criteria:\n- [ ] 0 package installation on a warm cache\n- [ ] cut cold cache package install time by 50% (XXX not realistic?)\n\n\n##### GitHub repositories\n\nClones from GitHub do clock in at 1GB on a cold cache.\nContainerd alone counts for more than half of it (at 160MB+ x4).\n\nHopefully, on a warm cache it is practically non-existent.\n\nBut then, this is ridiculous.\n\nActions:\n- [ ] shallow clone\n\nSuccess criteria:\n- [ ] reduce network traffic from cloning by 80%\n\n##### Go modules\n\nAt 750+MB and over 4 minutes, this is the number one speed bottleneck on a cold cache.\n\nOn a warm cache, it is still over 150MB and 30+ seconds.\n\nIn and of itself, this is hard to reduce, as we need these...\n\nActions:\n- [ ] we could cache the module download location to reduce round-trips on modules that are shared across\ndifferent projects\n- [ ] we are likely installing nerdctl modules six times - (once per architecture during the build phase, then once per\nubuntu version and architecture during the tests runs (this is not even accounted for in the audit above)) - it should\nonly happen twice (once per architecture)\n\nSuccess criteria:\n- [ ] achieve 20% reduction of total time spent downloading go modules\n\n##### Other downloads\n\n1. At 500MB+ and 30 seconds, storage.googleapis.com is serving a SINGLE go module that gets special treatment: klauspost/compress.\nThis module is very small, but does serve along with it a very large `testdata` folder.\nThe fact that nerdctl downloads its module multiple times is further compounding the effect.\n\n2. the golang archive is downloaded multiple times - it should be downloaded only once per run, and only on a cold cache\n\n3. some of the binary releases we are retrieving are also being retrieved with a warm cache, and they are generally quite large.\nWe could consider building certain things from source instead, and in all cases ensure that we are only downloading with a cold cache.\n\nSuccess criteria:\n- [ ] 0 static downloads on a warm cache\n- [ ] cut extra downloads by 20%\n\n#### Duration\n\nUnscientific numbers, per pipeline\n\ndependencies, no cache:\n- 224 seconds total\n- 53 seconds exporting cache\n\ndependencies, with cache:\n- 12 seconds\n\ntest-integration, no cache:\n- 282 seconds\n\n#### Caching\n\nNumber of layers in cache:\n```\nafter dependencies stage: 78\nintermediate size: 1.5G\nafter test-integration stage: 118\ntotal size: 2.8G\n```\n\n## Generic considerations\n\n### Caching compression\n\nThis is obviously heavily dependent on the runner properties.\n\nWith local cache, on high-performance IO (laptop SSD), zstd is definitely considerably better (about twice as fast).\n\nWith GHA, the impact is minimal, since network IO is heavily dominant, but zstd still has the upper\nhand with regard to cache size.\n\n### Output\n\nLoading image into the Docker store comes at a somewhat significant cost.\nIt is quite possible that a significant performance boost could be achieved by using\nbuildkit containerd worker and nerdctl instead.\n"
  },
  {
    "path": "docs/dev/store.md",
    "content": "# About `pkg/store`\n\n## TL;DR\n\nYou _may_ want to read this if you are developing something in nerdctl that would involve storing persistent information.\n\nIf there is a \"store\" already in the codebase (eg: volumestore, namestore, etc) that does provide the methods that you need,\nyou are fine and should just stick to that.\n\nOn the other hand, if you are curious, or if what you want to write is \"new\", then _you should_ have a look at this document:\nit does provide extended information about how we manage persistent data storage, especially with regard to concurrency\nand atomicity.\n\n## Motivation\n\nThe core of nerdctl aims at keeping its dependencies as lightweight as possible.\nFor that reason, nerdctl does not use a database to store persistent information, but instead uses the filesystem,\nunder a variety of directories.\n\nThat \"information\" is typically volumes metadata, containers lifecycle info, the \"name store\" (which does ensure no two\ncontainers can be named the same), etc.\n\nHowever, storing data on the filesystem in a reliable way comes with challenges:\n- incomplete writes may happen (because of a system restart, or an application crash), leaving important structured files\nin a broken state\n- concurrent writes, or reading while writing would obviously be a problem as well, be it across goroutines, or between\nconcurrent executions of the nerdctl binary, or embedded in a third-party application that does concurrently access resources\n\nThe `pkg/store` package does provide a \"storage\" abstraction that takes care of these issues, generally providing\nguarantees that concurrent operations can be performed safely, and that writes are \"atomic\", ensuring we never brick\nuser installs.\n\nFor details about how, and what is done, read-on.\n\n## The problem with writing a file\n\nA write may very well be interrupted.\n\nWhile reading the resulting mangled file will typically break `json.Unmarshall` for example, and while we should still\nhandle such cases gracefully and provide meaningful information to the user about which file is damaged (which could be due\nto the user manually modifying them), using \"atomic\" writes will (almost always (*)) prevent this from happening\non our part.\n\nAn \"atomic\" write is usually performed by first writing data to a temporary file, and then, only if the write operation\nsucceeded, move that temporary file to its final destination.\n\nThe `rename` syscall (see https://man7.org/linux/man-pages/man2/rename.2.html) is indeed \"atomic\"\n(eg: it fully succeeds, or fails), providing said guarantees that you end-up with a complete file that has the entirety\nof what was meant to be written.\n\nThis is an \"almost always\", as an _operating system crash_ MAY break that promise (this is highly dependent on specifics\nthat are out of scope here, and that nerdctl has no control over).\nThough, crashing operating systems is (hopefully) a sufficiently rare event that we can consider we \"always\" have atomic writes.\n\nThere is one caveat with \"rename-based atomic writes\" though: if you mount the file itself inside a container,\nan atomic write will not work as you expect, as the inode will (obviously) change when you modify the file,\nand these changes will not be propagated inside the container.\n\nThis caveat is the reason why `hostsstore` does NOT use an atomic write to update the `hosts` file, but a traditional write.\n\n## Concurrency between go routines\n\nThis is a (simple) well-known problem. Just use a mutex to prevent concurrent modifications of the same object.\n\nNote that this is not much of a problem right now in nerdctl itself - but it might be in third-party applications using\nour codebase.\n\nThis is just generally good hygiene when building concurrency-safe packages.\n\n## Concurrency between distinct binary invocations\n\nThis is much more of a problem.\n\nThere are many good reasons and real-life scenarios where concurrent binary execution may happen.\nA third-party deployment tool (similar to terraform for eg), that will batch a bunch of operations to be performed\nto achieve a desired infrastructure state, and call many `nerdctl` invocations in parallel to achieve that.\nThis is also common-place in testing (subpackages).\nAnd of course, a third-party tool that would be long-running and allow parallel execution, leveraging nerdctl codebase\nas a library, may certainly produce these circumstances.\n\nThe known answer to that problem is to use a filesystem lock (or flock).\n\nConcretely, the first process will \"lock\" a directory. All other processes trying to do the same will then be put\nin a queue and wait for the prior lock to be released before they can \"lock\" themselves, in turn.\n\nFilesystem locking comes with its own set of challenges:\n- implementation is somewhat low-level (the golang core library keeps their implementation internal, and you have to\nreimplement your own with platform-dependent APIs and syscalls)\n- it is tricky to get right - there are reasons why golang core does not make it public\n- locking \"scope\" should be done carefully: having ONE global lock for everything will definitely hurt performance badly,\nas you will basically make everything \"sequential\", effectively destroying some of the benefits of parallelizing code\nin the first place...\n\n## Lock design...\n\nWhile it is tempting to just provide self-locking, individual methods as an API (`Get`, `Set`), this is not the right\nanswer.\n\nImagine a context where consuming code would first like to check if something exists, then later on create it if it does not:\n```golang\nif !store.Exists(\"something\") {\n\t// do things\n\t// ...\n\t// Now, create\n\tstore.Set([]byte(\"data\"), \"something\")\n}\n```\n\nYou do have two methods (`Get` and `Set`) that _may individually_ guarantee they are the sole user of that resource,\nbut a concurrent change _in between_ these two calls may very well (and _will_) happen and change the state of the world.\n\nEffectively, in that case, `Set` might overwrite changes made by another go routine or concurrent execution, possibly\nwrecking havoc in another process.\n\n_When_ to lock, and _for how long_, is a decision that only the embedding code can make.\n\nA good example is container creation.\nIt may require the creation of several different volumes.\nIn that case, you want to lock at the start of the container creation process, and only release the lock when you are fully\ndone creating the container - not just when done creating a volume (nor even when done creating all volumes).\n\n## ... while safeguarding the developer\n\nnerdctl still provides some safeguards for the developer.\n\nAny store method that DOES require locking will fail loudly if it does not detect a lock.\n\nThis is obviously not bullet-proof.\nFor example, the lock may belong to another goroutine instead of the one we are in (and we cannot detect that).\nBut this is still better than nothing, and will help developers making sure they **do** lock.\n\n## Using the `store` api to implement your own storage\n\nWhile - as mentioned above - the store does _not_ lock on its own, specific \"stores implementations\" may, and should,\nprovide higher-level methods that best fit their data-model usage, and that **do** lock on their own.\n\nFor example, the namestore (which is the simplest store), does provide three simple methods:\n- Acquire\n- Release\n- Rename\n\nUsers of the `namestore` do not have to bother with locking. These methods are safe to use concurrently.\n\nThis is a good example of how to leverage core store primitives to implement a developer friendly, safe storage for\n\"something\" (in that case \"names\").\n\nFinaly note an important point - mentioned above: locking should be done to the smallest possible \"segment\" of sub-directories.\nSpecifically, any store should lock only - at most - resources under the _namespace_ being manipulated.\n\nFor example, a container lifecycle storage should not lock out any other container, but only its own private directory.\n\n## Scope, ambitions and future\n\n`pkg/store` has no ambition whatsoever to be a generic solution, usable outside nerdctl.\n\nIt is solely designed to fit nerdctl needs, and if it was to be made usable standalone, would probably have to be modified\nextensively, which is clearly out of scope here.\n\nFurthermore, there are already much more advanced generic solutions out there that you should use instead for outside-of-nerdctl projects.\n\nAs for future, one nice thing we should consider is to implement read-only locks in addition to the exclusive, write-locks\nwe currently use.\nThe net benefit would be a performance boost in certain contexts (massively parallel, mostly read environments)."
  },
  {
    "path": "docs/dir.md",
    "content": "# nerdctl directory layout\n\n## Config\n**Default**: `/etc/nerdctl/nerdctl.toml` (rootful), `~/.config/nerdctl/nerdctl.toml` (rootless)\n\nThe configuration file of nerdctl. See [`config.md`](./config.md).\n\nCan be overridden with environment variable `$NERDCTL_TOML`.\n\nThis file is unrelated to the daemon config file `/etc/containerd/config.toml`.\n\n## Data\n### `<DATAROOT>`\n**Default**: `/var/lib/nerdctl` (rootful), `~/.local/share/nerdctl` (rootless)\n\nCan be overridden with `nerdctl --data-root=<DATAROOT>` flag.\n\nThe directory is solely managed by nerdctl, not by containerd.\nThe directory has nothing to do with containerd data root `/var/lib/containerd`.\n\n### `<DATAROOT>/<ADDRHASH>`\ne.g. `/var/lib/nerdctl/1935db59`\n\n`1935db9` is from `$(echo -n \"/run/containerd/containerd.sock\" | sha256sum | cut -c1-8)`\n\nThis directory is also called \"data store\" in the implementation.\n\n### `<DATAROOT>/<ADDRHASH>/containers/<NAMESPACE>/<CID>`\ne.g. `/var/lib/nerdctl/1935db59/containers/default/c4ed811cc361d26faffdee8d696ddbc45a9d93c571b5b3c54d3da01cb29caeb1`\n\nFiles:\n- `resolv.conf`: mounted to the container as `/etc/resolv.conf`\n- `hostname`: mounted to the container as `/etc/hostname`\n- `log-config.json`: used for storing the `--log-opts` map of `nerdctl run`\n- `<CID>-json.log`: used by `nerdctl logs`\n- `oci-hook.*.log`: logs of the OCI hook\n- `lifecycle.json`: used to store stateful information about the container that can only be retrieved through OCI hooks\n- `network-config.json`: used to store container-specific network configuration, such as port mappings.\n\n### `<DATAROOT>/<ADDRHASH>/names/<NAMESPACE>`\ne.g. `/var/lib/nerdctl/1935db59/names/default`\n\nFiles:\n- `<NAME>`: contains the container ID (CID). Represents that the name is taken by that container. \n\nFiles must be operated with a `LOCK_EX` lock against the `<DATAROOT>/<ADDRHASH>/names/<NAMESPACE>` directory.\n\n### `<DATAROOT>/<ADDRHASH>/etchosts/<NAMESPACE>/<CID>`\ne.g. `/var/lib/nerdctl/1935db59/etchosts/default/c4ed811cc361d26faffdee8d696ddbc45a9d93c571b5b3c54d3da01cb29caeb1`\n\nFiles:\n- `hosts`: mounted to the container as `/etc/hosts`\n- `meta.json`: metadata\n\nFiles must be operated with a `LOCK_EX` lock against the `<DATAROOT>/<ADDRHASH>/etchosts` directory.\n\n### `<DATAROOT>/<ADDRHASH>/volumes/<NAMESPACE>/<VOLNAME>/_data`\ne.g. `/var/lib/nerdctl/1935db59/volumes/default/foo/_data`\n\nData volume\n\n## CNI\n\n### `<NETCONFPATH>`\n**Default**: `/etc/cni/net.d` (rootful), `~/.config/cni/net.d` (rootless)\n\nCan be overridden with `nerdctl --cni-netconfpath=<NETCONFPATH>` flag and environment variable `$NETCONFPATH`.\n\nAt the top-level of <NETCONFPATH>, network (files) are shared across all namespaces.\nSub-folders inside <NETCONFPATH> are only available to the namespace bearing the same name,\nand its networks definitions are private.\n\nFiles:\n- `nerdctl-<NWNAME>.conflist`: CNI conf list created by nerdctl\n"
  },
  {
    "path": "docs/experimental.md",
    "content": "# Experimental features of nerdctl\n\nThe following features are experimental and subject to change.\nSee [`./config.md`](config.md) about how to enable these features.\n\n- [Windows containers](https://github.com/containerd/nerdctl/issues/28)\n- [FreeBSD containers](./freebsd.md)\n- Flags of `nerdctl image convert`: `--estargz-record-in=FILE` and `--zstdchunked-record-in=FILE` (Importing an external eStargz record JSON file), `--estargz-external-toc` (Separating TOC JSON to another image).\n  eStargz and zstd themselves are out of experimental.\n- [Image Distribution on IPFS](./ipfs.md)\n- [Image Sign and Verify (cosign)](./cosign.md)\n- [Image Sign and Verify (notation)](./notation.md)\n- [Rootless container networking acceleration with bypass4netns](./rootless.md#bypass4netns)\n- [Interactive debugging of Dockerfile](./builder-debug.md)\n- Kubernetes (`cri`) log viewer: `nerdctl --namespace=k8s.io logs`\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# FAQs and Troubleshooting\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Project](#project)\n  - [How is nerdctl different from `docker` ?](#how-is-nerdctl-different-from-docker-)\n  - [How is nerdctl different from `ctr` and `crictl` ?](#how-is-nerdctl-different-from-ctr-and-crictl-)\n- [Mac & Windows](#mac--windows)\n  - [Does nerdctl run on macOS ?](#does-nerdctl-run-on-macos-)\n  - [Does nerdctl run on Windows ?](#does-nerdctl-run-on-windows-)\n- [Configuration](#configuration)\n  - [nerdctl ignores `[plugins.\"io.containerd.grpc.v1.cri\"]` config](#nerdctl-ignores-pluginsiocontainerdgrpcv1cri-config)\n  - [How to login to a registry?](#how-to-login-to-a-registry)\n  - [How to use a non-HTTPS registry?](#how-to-use-a-non-https-registry)\n  - [How to specify the CA certificate of the registry?](#how-to-specify-the-ca-certificate-of-the-registry)\n  - [How to change the cgroup driver?](#how-to-change-the-cgroup-driver)\n  - [How to change the snapshotter?](#how-to-change-the-snapshotter)\n  - [How to change the runtime?](#how-to-change-the-runtime)\n  - [How to change the CNI binary path?](#how-to-change-the-cni-binary-path)\n- [Kubernetes](#kubernetes)\n  - [`nerdctl ps -a` does not show Kubernetes containers](#nerdctl-ps--a-does-not-show-kubernetes-containers)\n  - [How to build an image for Kubernetes?](#how-to-build-an-image-for-kubernetes)\n- [containerd socket address](#containerd-socket-address)\n  - [Does nerdctl have an equivalent of `DOCKER_HOST=ssh://<USER>@<REMOTEHOST>` ?](#does-nerdctl-have-an-equivalent-of-docker_hostsshuserremotehost-)\n  - [Does nerdctl have an equivalent of `sudo usermod -aG docker <USER>` ?](#does-nerdctl-have-an-equivalent-of-sudo-usermod--ag-docker-user-)\n- [Rootless](#rootless)\n  - [How to use nerdctl as a non-root user? (Rootless mode)](#how-to-use-nerdctl-as-a-non-root-user-rootless-mode)\n  - [`nerdctl run -p <PORT>` does not propagate source IP](#nerdctl-run--p-port-does-not-propagate-source-ip)\n  - [`nerdctl run -p <PORT>` does not work with port numbers below 1024](#nerdctl-run--p-port-does-not-work-with-port-numbers-below-1024)\n  - [Can't ping](#cant-ping)\n  - [Containers do not automatically start after rebooting the host](#containers-do-not-automatically-start-after-rebooting-the-host)\n  - [Error `failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: unable to apply cgroup configuration: unable to start unit ... {Name:Slice Value:\"user.slice\"} {Name:Delegate Value:true} ... Permission denied: unknown`](#error-failed-to-create-shim-task-oci-runtime-create-failed-runc-create-failed-unable-to-start-container-process-unable-to-apply-cgroup-configuration-unable-to-start-unit--nameslice-valueuserslice-namedelegate-valuetrue--permission-denied-unknown)\n  - [How to uninstall ? / Can't remove `~/.local/share/containerd`](#how-to-uninstall---cant-remove-localsharecontainerd)\n  - [How to clean a dangling cache of buildkit?](#how-to-clean-a-dangling-cache-of-buildkit)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Project\n\n### How is nerdctl different from `docker` ?\n\nThe goal of nerdctl is to facilitate experimenting the cutting-edge features of containerd that are not present in Docker.\n\nSuch features include, but not limited to, [on-demand image pulling (lazy-pulling)](./stargz.md) and [image encryption/decryption](./ocicrypt.md).\nSee also [`../README.md`](../README.md) for the list of the features present in nerdctl but not present in Docker (and vice versa).\n\nNote that competing with Docker is _not_ the goal of nerdctl. Those cutting-edge features are expected to be eventually available in Docker as well.\n\n### How is nerdctl different from `ctr` and `crictl` ?\n\n[`ctr`](https://github.com/containerd/containerd/tree/main/cmd/ctr) is a debugging utility bundled with containerd.\n\nctr is incompatible with Docker CLI, and not friendly to users.\n\nNotably, `ctr` lacks the equivalents of the following nerdctl commands:\n- `nerdctl run -p <PORT>`\n- `nerdctl run --restart=always --net=bridge`\n- `nerdctl pull` with `~/.docker/config.json` and credential helper binaries such as `docker-credential-ecr-login`\n- `nerdctl logs`\n- `nerdctl build`\n- `nerdctl compose up`\n\n[`crictl`](https://github.com/kubernetes-sigs/cri-tools) has similar restrictions too.\n\n## Mac & Windows\n\n### Does nerdctl run on macOS ?\n\nYes, via a Linux virtual machine.\n\n[Lima](https://github.com/lima-vm/lima) project provides Linux virtual machines for macOS, with built-in integration for nerdctl.\n\n```console\n$ brew install lima\n$ limactl start\n$ lima nerdctl run -d --name nginx -p 127.0.0.1:8080:80 nginx:alpine\n```\n\n[Rancher Desktop for Mac](https://rancherdesktop.io/) and [colima](https://github.com/abiosoft/colima) also provide custom Lima machines with nerdctl.\n\n### Does nerdctl run on Windows ?\n\nWindows containers: Yes, but experimental.\n\nLinux containers: Yes, via WSL2. [Rancher Desktop for Windows](https://rancherdesktop.io/) provides a `nerdctl.exe` that wraps nerdctl binary in a WSL2 machine.\n\n## Configuration\n\n### nerdctl ignores `[plugins.\"io.containerd.grpc.v1.cri\"]` config\n\nExpected behavior, because nerdctl does not use CRI (Kubernetes Container Runtime Interface) API.\n\nSee the questions below for how to configure nerdctl.\n\n### How to login to a registry?\n\nUse `nerdctl login`, or just create `~/.docker/config.json`.\nnerdctl also supports credential helper binaries such as `docker-credential-ecr-login`.\n\n### How to use a non-HTTPS registry?\n\nUse `nerdctl --insecure-registry run <IMAGE>`. See also [`registry.md`](./registry.md).\n\n### How to specify the CA certificate of the registry?\n\n| :zap: Requirement | nerdctl >= 0.16 |\n|-------------------|-----------------|\n\nCreate `~/.config/containerd/certs.d/<HOST:PORT>/hosts.toml` (or `/etc/containerd/certs.d/...` for rootful) to specify `ca` certificates.\n\n```toml\n# An example of ~/.config/containerd/certs.d/192.168.12.34:5000/hosts.toml\n# (The path is \"/etc/containerd/certs.d/192.168.12.34:5000/hosts.toml\" for rootful)\n\nserver = \"https://192.168.12.34:5000\"\n[host.\"https://192.168.12.34:5000\"]\n  ca = \"/path/to/ca.crt\"\n```\n\nSee https://github.com/containerd/containerd/blob/main/docs/hosts.md for the syntax of `hosts.toml` .\n\nDocker-style directories are also supported.\nThe path is `~/.config/docker/certs.d` for rootless, `/etc/docker/certs.d` for rootful.\n\nSee also [`registry.md`](./registry.md).\n\n### How to change the cgroup driver?\n\n- Option 1: `nerdctl --cgroup-manager=(cgroupfs|systemd|none)`.\n- Option 2: Set `cgroup_manager` property in [`nerdctl.toml`](config.md)\n\nThe default value is `systemd` on cgroup v2 hosts (both rootful and rootless), `cgroupfs` on cgroup v1 rootful hosts, `none` on cgroup v1 rootless hosts.\n\n<details>\n<summary>Hint: The corresponding configuration for Kubernetes (<code>io.containerd.grpc.v1.cri</code>)</summary>\n\n<p>\n\n```toml\n# An example of /etc/containerd/config.toml for Kubernetes\nversion = 2\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n  SystemdCgroup = true\n```\n\nIn addition to containerd, you have to configure kubelet too:\n\n```yaml\n# An example of /var/lib/kubelet/config.yaml for Kubernetes\nkind: KubeletConfiguration\napiVersion: kubelet.config.k8s.io/v1beta1\ncgroupDriver: \"systemd\"\n```\nSee also https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/\n\n</p>\n</details>\n\n### How to change the snapshotter?\n- Option 1: Use `nerdctl --snapshotter=(overlayfs|native|btrfs|...)`\n- Option 2: Set `$CONTAINERD_SNAPSHOTTER`\n- Option 3: Set `snapshotter` property in [`nerdctl.toml`](config.md)\n\nThe default value is `overlayfs`.\n\n<details>\n<summary>Hint: The corresponding configuration for Kubernetes (<code>io.containerd.grpc.v1.cri</code>)</summary>\n\n<p>\n\n```toml\n# An example of /etc/containerd/config.toml for Kubernetes\nversion = 2\n[plugins.\"io.containerd.grpc.v1.cri\".containerd]\n  snapshotter = \"overlayfs\"\n```\n\n</p>\n</details>\n\n### How to change the runtime?\nUse `nerdctl run --runtime=<RUNTIME>`.\n\nThe `<RUNTIME>` string can be either a containerd runtime plugin name (such as `io.containerd.runc.v2`),\nor a path to a runc-compatible binary (such as `/usr/local/sbin/runc`).\n\n<details>\n<summary>Hint: The corresponding configuration for Kubernetes (<code>io.containerd.grpc.v1.cri</code>)</summary>\n\n<p>\n\n```toml\n# An example of /etc/containerd/config.toml for Kubernetes\nversion = 2\n[plugins.\"io.containerd.grpc.v1.cri\".containerd]\n  default_runtime_name = \"crun\"\n  [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes]\n    [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.crun]\n      runtime_type = \"io.containerd.runc.v2\"\n      [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.crun.options]\n        BinaryName = \"/usr/local/bin/crun\"\n```\n\n</p>\n</details>\n\n\n### How to change the CNI binary path?\n\n- Option 1: Use `nerdctl --cni-path=<PATH>`\n- Option 2: Set `$CNI_PATH`\n- Option 3: Set `cni_path` property in [`nerdctl.toml`](config.md).\n\nThe default value is automatically detected by checking the following candidates:\n- `~/.local/libexec/cni`\n- `~/.local/lib/cni`\n- `~/opt/cni/bin`\n- `/usr/local/libexec/cni`\n- `/usr/local/lib/cni`\n- `/home/linuxbrew/.linuxbrew/opt/cni-plugins/bin`\n- `/usr/libexec/cni`\n- `/usr/lib/cni`\n- `/opt/cni/bin`\n\n<details>\n<summary>Hint: The corresponding configuration for Kubernetes (<code>io.containerd.grpc.v1.cri</code>)</summary>\n\n<p>\n\n```toml\n# An example of /etc/containerd/config.toml for Kubernetes\nversion = 2\n[plugins.\"io.containerd.grpc.v1.cri\".cni]\n   bin_dir = \"/opt/cni/bin\"\n```\n\n</p>\n</details>\n\n\n## Kubernetes\n\n### `nerdctl ps -a` does not show Kubernetes containers\nTry `sudo nerdctl --namespace=k8s.io ps -a` .\n\nNote: k3s users have to specify `--address` too: `sudo nerdctl --address=/run/k3s/containerd/containerd.sock --namespace=k8s.io ps -a`\n\n### How to build an image for Kubernetes?\n\nFor a multi-node cluster:\n```console\n$ nerdctl build -t example.com/foo /some-dockerfile-directory\n$ nerdctl push example.com/foo\n```\n\nFor a single-node cluster w/o registry:\n```console\n# nerdctl --namespace k8s.io build -t foo /some-dockerfile-directory\n# kubectl apply -f - <<EOF\napiVersion: v1\nkind: Pod\nmetadata:\n  name: foo\nspec:\n  containers:\n    - name: foo\n      image: foo\n      imagePullPolicy: Never\nEOF\n```\n\n## containerd socket address\n\n- rootful: `/run/containerd/containerd.sock`\n- rootless (e.g., default [Lima](https://github.com/lima-vm/lima) instance): `/proc/<PID of containerd>/root/run/containerd/containerd.sock`, or you can run this command to find out: `echo /proc/$(cat $XDG_RUNTIME_DIR/containerd-rootless/child_pid)/root/run/containerd/containerd.sock` (why it works:  [`rootlessutil.ParentMain`](https://github.com/containerd/nerdctl/blob/b160d8173fb765b76cf0920d26621843d377bc3f/pkg/rootlessutil/parent.go#L66))\n\n### Does nerdctl have an equivalent of `DOCKER_HOST=ssh://<USER>@<REMOTEHOST>` ?\n\nNo. Just use `ssh -l <USER> <REMOTEHOST> nerdctl`.\n\n### Does nerdctl have an equivalent of `sudo usermod -aG docker <USER>` ?\n\nNot exactly same, but setting SETUID bit (`chmod +s`) on `nerdctl` binary gives similar user experience.\n\n```\nmkdir -p $HOME/bin\nchmod 700 $HOME/bin\ncp /usr/local/bin/nerdctl $HOME/bin\nsudo chown root $HOME/bin/nerdctl\nsudo chmod +s $HOME/bin/nerdctl\nexport PATH=$HOME/bin:$PATH\n```\n\nChmodding `$HOME/bin` to `700` is important, otherwise an unintended user may gain the root privilege via the SETUID bit.\n\nUsing SETUID bit is highly discouraged. Consider using [Rootless mode](#rootless) instead whenever possible.\n\n## Rootless\n\n### How to use nerdctl as a non-root user? (Rootless mode)\n\n```\ncontainerd-rootless-setuptool.sh install\nnerdctl run -d --name nginx -p 8080:80 nginx:alpine\n```\n\nSee also:\n- [`rootless.md`](./rootless.md)\n- https://rootlesscontaine.rs/getting-started/common/\n- https://rootlesscontaine.rs/getting-started/containerd/\n\n### `nerdctl run -p <PORT>` does not propagate source IP\nExpected behavior with the default `rootlesskit` port driver.\n\nThe solution is to change the port driver to `slirp4netns` (sacrifices performance).\n\nSee https://rootlesscontaine.rs/getting-started/containerd/#changing-the-port-forwarder .\n\n### `nerdctl run -p <PORT>` does not work with port numbers below 1024\n\nSet sysctl value `net.ipv4.ip_unprivileged_port_start=0` .\n\nSee https://rootlesscontaine.rs/getting-started/common/sysctl/#optional-allowing-listening-on-tcp--udp-ports-below-1024\n\n### Can't ping\n\nSet sysctl value `net.ipv4.ping_group_range=0 2147483647` .\n\nSee https://rootlesscontaine.rs/getting-started/common/sysctl/#optional-allowing-ping\n\n### Containers do not automatically start after rebooting the host\nRun `sudo loginctl enable-linger $(whoami)` .\n\nSee https://rootlesscontaine.rs/getting-started/common/login/ .\n\n### Error `failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: unable to apply cgroup configuration: unable to start unit ... {Name:Slice Value:\"user.slice\"} {Name:Delegate Value:true} ... Permission denied: unknown`\n\nRunning a rootless container with `systemd` cgroup driver requires dbus to be running as a user session service.\n\nOtherwise runc may fail with an error like below:\n```\nFATA[0000] failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: unable to apply cgroup configuration: unable to start unit \"nerdctl-7bda4abaa1f006ab9feeb98c06953db43f212f1c0aaf658fb8a88d6f63dff9f9.scope\" (properties [{Name:Description Value:\"libcontainer container 7bda4abaa1f006ab9feeb98c06953db43f212f1c0aaf658fb8a88d6f63dff9f9\"} {Name:Slice Value:\"user.slice\"} {Name:Delegate Value:true} {Name:PIDs Value:@au [1154]} {Name:MemoryAccounting Value:true} {Name:CPUAccounting Value:true} {Name:IOAccounting Value:true} {Name:TasksAccounting Value:true} {Name:DefaultDependencies Value:false}]): Permission denied: unknown\n```\n\nSolution:\n```\nsudo apt-get install -y dbus-user-session\n\nsystemctl --user start dbus\n```\n\n### How to uninstall ? / Can't remove `~/.local/share/containerd`\n\nRun the following commands:\n```\ncontainerd-rootless-setuptool.sh uninstall\nrootlesskit rm -rf ~/.local/share/containerd ~/.local/share/nerdctl ~/.config/containerd\n```\n\n### How to clean a dangling cache of buildkit?\n\n`buildkit` cache directory is located at `$HOME/.local/share/buildkit/`\nin rootless mode, which has same folder structure `/var/lib/buildkit/` in\nroot mode.\n\nYou can clear the cache objects by running the following command:\n```\nnerdctl builder prune\n```\nThe command produce a progress message of id and size of removed objects.\n"
  },
  {
    "path": "docs/freebsd.md",
    "content": "# FreeBSD\n\n\n| :zap:        FreeBSD runtimes are at the very early stage of development |\n|--------------------------------------------------------------------------|\n\nnerdctl provides experimental support for running FreeBSD jails on FreeBSD hosts.\n\n## Installation\n\nYou will need the most up-to-date containerd build along with a containerd shim,\nsuch as [runj](https://github.com/samuelkarp/runj). Follow the build\ninstructions in the respective repositories.\n\n## Usage\n\nYou can use the `dougrabson/freebsd13.2-small` image to run a FreeBSD 13 jail:\n\n```sh\nnerdctl run --net none -it dougrabson/freebsd13.2-small\n```\n\nAlternatively use `--platform` parameter to run linux containers\n\n```sh\nnerdctl run --platform linux --net none -it amazonlinux:2\n```\n\n\n## Limitations & Bugs\n\n- :warning: CNI & CNI plugins are not yet ported to FreeBSD. The only supported\n  network type is `none`\n"
  },
  {
    "path": "docs/gpu.md",
    "content": "# Using GPUs inside containers\n\n| :zap: Requirement | nerdctl >= 0.9 |\n|-------------------|----------------|\n\n> [!NOTE]\n> The description in this section applies to nerdctl v2.3 or later.\n> Users of prior releases of nerdctl should refer to <https://github.com/containerd/nerdctl/blob/v2.2.0/docs/gpu.md>\n\nnerdctl provides docker-compatible NVIDIA and AMD GPU support.\n\n## Prerequisites\n\n- GPU Drivers\n  - Same requirement as when you use GPUs on Docker. For details, please refer to these docs by [NVIDIA](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#pre-requisites) and [AMD](https://instinct.docs.amd.com/projects/container-toolkit/en/latest/container-runtime/quick-start-guide.html#step-2-install-the-amdgpu-driver).\n- Container Toolkit\n  - containerd relies on vendor Container Toolkits to make GPUs available to the containers. You can install those by following the official installation instructions from [NVIDIA](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) and [AMD](https://instinct.docs.amd.com/projects/container-toolkit/en/latest/container-runtime/quick-start-guide.html).\n- CDI Specification\n  - Container Device Interface (CDI) specification for the GPU devices is required for the GPU support to work. Follow the official documentation from [NVIDIA](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/cdi-support.html) and [AMD](https://instinct.docs.amd.com/projects/container-toolkit/en/latest/container-runtime/cdi-guide.html) to ensure that the required CDI specifications are present on the system.\n\n## Options for `nerdctl run --gpus`\n\n`nerdctl run --gpus` is compatible to [`docker run --gpus`](https://docs.docker.com/engine/reference/commandline/run/#access-an-nvidia-gpu).\n\nYou can specify number of GPUs to use via `--gpus` option.\nThe following examples expose all available GPUs to the container.\n\n```\nnerdctl run -it --rm --gpus all nvidia/cuda:12.3.1-base-ubuntu20.04 nvidia-smi\n```\n\nor\n\n```\nnerdctl run -it --rm --gpus=all rocm/rocm-terminal rocm-smi\n```\n\nYou can also pass detailed configuration to `--gpus` option as a list of key-value pairs. The following options are provided.\n\n- `count`: number of GPUs to use. `all` exposes all available GPUs.\n- `device`: IDs of GPUs to use. UUID or numbers of GPUs can be specified. This only works for NVIDIA GPUs.\n\nThe following example exposes a specific NVIDIA GPU to the container.\n\n```\nnerdctl run -it --rm --gpus 'device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a' nvidia/cuda:12.3.1-base-ubuntu20.04 nvidia-smi\n```\n\nNote that although `capabilities` options may be provided, these are ignored when processing the GPU request since nerdctl v2.3.\n\n## Fields for `nerdctl compose`\n\n`nerdctl compose` also supports GPUs following [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#devices).\n\nYou can use GPUs on compose when you specify the `driver` as `nvidia` or one or\nmore of the following `capabilities` in `services.demo.deploy.resources.reservations.devices`.\n\n- `gpu`\n- `nvidia`\n\nAvailable fields are the same as `nerdctl run --gpus`.\n\nThe following exposes all available GPUs to the container.\n\n```\nversion: \"3.8\"\nservices:\n  demo:\n    image: nvidia/cuda:12.3.1-base-ubuntu20.04\n    command: nvidia-smi\n    deploy:\n      resources:\n        reservations:\n          devices:\n          - driver: nvidia\n            count: all\n```\n\n## Trouble Shooting\n\n### `nerdctl run --gpus` fails due to an unresolvable CDI device\n\nIf the required CDI specifications for your GPU devices are not available on the\nsystem, the `nerdctl run` command will fail with an error similar to: `CDI device injection failed: unresolvable CDI devices nvidia.com/gpu=all` (the\nexact error message will depend on the vendor and the device(s) requested).\n\nThis should be the same error message that is reported when the `--device` flag\nis used to request a CDI device:\n```\nnerdctl run --device=nvidia.com/gpu=all\n```\n\nEnsure that the NVIDIA (or AMD) Container Toolkit is installed and the requested CDI devices are present in the ouptut of `nvidia-ctk cdi list` (or `amd-ctk cdi list` for AMD GPUs):\n\n```\n$ nvidia-ctk cdi list\nINFO[0000] Found 3 CDI devices\nnvidia.com/gpu=0\nnvidia.com/gpu=GPU-3eb87630-93d5-b2b6-b8ff-9b359caf4ee2\nnvidia.com/gpu=all\n```\n\nFor NVIDIA Container Toolkit, version >= v1.18.0 is recommended. See the NVIDIA Container Toolkit [CDI documentation](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/cdi-support.html) for more information.\n\nFor AMD Container Toolkit, version >= v1.2.0 is recommended. See the AMD Container Toolkit [CDI documentation](https://instinct.docs.amd.com/projects/container-toolkit/en/latest/container-runtime/cdi-guide.html) for more information.\n\n\n### `nerdctl run --gpus` fails when using the Nvidia gpu-operator\n\nIf the Nvidia driver is installed by the [gpu-operator](https://github.com/NVIDIA/gpu-operator).The `nerdctl run` will fail with the error message `(FATA[0000] exec: \"nvidia-container-cli\": executable file not found in $PATH)`.\n\nSo, the `nvidia-container-cli` needs to be added to the PATH environment variable.\n\nYou can do this by adding the following line to your $HOME/.profile or /etc/profile (for a system-wide installation):\n```\nexport PATH=$PATH:/usr/local/nvidia/toolkit\n```\n\nThe shared libraries also need to be added to the system.\n```\necho \"/run/nvidia/driver/usr/lib/x86_64-linux-gnu\" > /etc/ld.so.conf.d/nvidia.conf\nldconfig\n```\n\nAnd then, the `nerdctl run --gpus` can run successfully.\n"
  },
  {
    "path": "docs/healthchecks.md",
    "content": "# Health Check Support in nerdctl\n\n`nerdctl` supports Docker-compatible health checks for containers, allowing users to monitor container health via a user-defined command.\n\n## Configuration Options\n| :zap: Requirement | nerdctl >= 2.1.5 |\n|-------------------|----------------|\n\nHealth checks can be configured in multiple ways:\n\n1. At container creation time using `nerdctl run` or `nerdctl create` with these flags:\n   - `--health-cmd`: Command to run to check health\n   - `--health-interval`: Time between running the check (default: 30s)\n   - `--health-timeout`: Maximum time to allow one check to run (default: 30s)\n   - `--health-retries`: Consecutive failures needed to report unhealthy (default: 3)\n   - `--health-start-period`: Start period for the container to initialize before starting health-retries countdown\n   - `--no-healthcheck`: Disable any container-specified HEALTHCHECK\n\n2. At image build time using HEALTHCHECK in a Dockerfile\n\n**Note:** The `--health-start-interval` option is currently not supported by nerdctl.\n\n## Configuration Priority\n\nWhen a container is created, nerdctl determines the health check configuration based on this priority:\n\n1. CLI flags take highest precedence (e.g., `--health-cmd`, etc.)\n2. If no CLI flags are set, nerdctl will use any health check defined in the image\n3. If neither is present, no health check will be configured\n\n### Disabling Health Checks\n\nYou can disable health checks using the following flag during container create/run:\n\n```bash\n--no-healthcheck\n```\n\n### Running Health Checks Manually\n\nnerdctl provides a container healthcheck command that can be manually triggered by the user. This command runs the\nconfigured health check inside the container and reports the result. It serves as the entry point for executing\nhealth checks, especially in scenarios where external scheduling is used.\n\nExample:\n```\nnerdctl container healthcheck <container-id>\n```\n\n## Automatic Health Checks with systemd\n\nOn Linux systems with systemd, nerdctl automatically creates and manages systemd timer units to execute health checks at the configured intervals. This provides reliable scheduling and execution of health checks without requiring a persistent daemon.\n\n### Requirements for Automatic Health Checks\n\n- systemd must be available on the system\n- Container must not be running in rootless mode\n- Configuration property `disable_hc_systemd` must not be set to `true` in nerdctl.toml\n\n### How It Works\n\n1. When a container with health checks is created, nerdctl:\n   - Creates a systemd timer unit for the container\n   - Configures the timer according to the health check interval\n   - Starts monitoring the container's health status\n\n2. The health check status can be one of:\n   - `starting`: During container initialization\n   - `healthy`: When health checks are passing\n   - `unhealthy`: After specified number of consecutive failures\n## Examples\n\n1. Basic health check that verifies a web server:\n```bash\nnerdctl run -d --name web \\\n  --health-cmd=\"curl -f http://localhost/ || exit 1\" \\\n  --health-interval=5s \\\n  --health-retries=3 \\\n  nginx\n```\n\n2. Health check with initialization period:\n```bash\nnerdctl run -d --name app \\\n  --health-cmd=\"./health-check.sh\" \\\n  --health-interval=30s \\\n  --health-timeout=10s \\\n  --health-retries=3 \\\n  --health-start-period=60s \\\n  myapp\n```\n\n3. Disable health checks:\n```bash\nnerdctl run --no-healthcheck myapp\n```\n"
  },
  {
    "path": "docs/ipfs.md",
    "content": "# Distribute Container Images on IPFS (Experimental)\n\n| :zap: Requirement | nerdctl >= 0.14 |\n|-------------------|-----------------|\n\nYou can distribute container images without registries, using IPFS.\n\nIPFS support is completely optional. Your host is NOT connected to any P2P network, unless you opt in to [install and run IPFS daemon](https://docs.ipfs.io/install/).\n\n## Prerequisites\n\n### ipfs daemon\n\nMake sure an IPFS daemon such as [Kubo](https://github.com/ipfs/kubo) (former go-ipfs) is running on your host.\nFor example, you can run Kubo using the following command.\n\n```\nipfs daemon\n```\n\nIn rootless mode, you need to install ipfs daemon using `containerd-rootless-setuptool.sh`.\n\n```\ncontainerd-rootless-setuptool.sh -- install-ipfs --init\n```\n\n> NOTE: correctly set IPFS_PATH as described in the output of the above command.\n\n:information_source: If you want to expose some ports of ipfs daemon (e.g. 4001), you can install rootless containerd using `containerd-rootless-setuptool.sh install` with `CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS=\"--publish=0.0.0.0:4001:4001/tcp\"` environment variable.\n\n:information_source: If you don't want IPFS to communicate with nodes on the internet, you can run IPFS daemon in offline mode using `--offline` flag or you can create a private IPFS network as described [here](https://github.com/containerd/stargz-snapshotter/blob/main/docs/ipfs.md#appendix-1-creating-ipfs-private-network).\n\n:information_source: Instead of locally launching IPFS daemon, you can specify the address of the IPFS API using `--ipfs-address` flag.\n\n## IPFS-enabled image and OCI Compatibility\n\nImage distribution on IPFS is achieved by OCI-compatible *IPFS-enabled image format*.\nnerdctl automatically converts an image to IPFS-enabled when necessary.\nFor example, when nerdctl pushes an image to IPFS, if that image isn't an IPFS-enabled one, it converts that image to the IPFS-enabled one.\n\nPlease see [the doc in stargz-snapshotter project](https://github.com/containerd/stargz-snapshotter/blob/v0.10.0/docs/ipfs.md) for details about IPFS-enabled image format.\n\n## Using nerdctl with IPFS\n\nnerdctl supports an image name prefix `ipfs://` to handle images on IPFS.\n\n### `nerdctl push ipfs://<image-name>`\n\nFor `nerdctl push`, you can specify `ipfs://` prefix for arbitrary image names stored in containerd.\nWhen this prefix is specified, nerdctl pushes that image to IPFS.\n\n```console\n> nerdctl push ipfs://ubuntu:20.04\nINFO[0000] pushing image \"ubuntu:20.04\" to IPFS\nINFO[0000] ensuring image contents\nbafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\n```\n\nAt last line of the output, the IPFS CID of the pushed image is printed.\nYou can use this CID to pull this image from IPFS.\n\nYou can also specify `--estargz` option to enable [eStargz-based lazy pulling](https://github.com/containerd/stargz-snapshotter/blob/v0.10.0/docs/ipfs.md) on IPFS.\nPlease see the later section for details.\n\n```console\n> nerdctl push --estargz ipfs://fedora:36\nINFO[0000] pushing image \"fedora:36\" to IPFS\nINFO[0000] ensuring image contents\nINFO[0011] converted \"application/vnd.docker.image.rootfs.diff.tar.gzip\" to sha256:cd4be969f12ef45dee7270f3643f796364045edf94cfa9ef6744d91d5cdf2208\nbafkreibp2ncujcia663uum25ustwvmyoguxqyzjnxnlhebhsgk2zowscye\n```\n\n### `nerdctl pull ipfs://<CID>` and `nerdctl run ipfs://<CID>`\n\nYou can pull an image from IPFS by specifying `ipfs://<CID>` where `CID` is the CID of the image.\n\n```console\n> nerdctl pull ipfs://bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\nbafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze:                      resolved       |++++++++++++++++++++++++++++++++++++++|\nindex-sha256:28bfa1fc6d491d3bee91bab451cab29c747e72917efacb0adc4e73faffe1f51c:    done           |++++++++++++++++++++++++++++++++++++++|\nmanifest-sha256:f6eed19a2880f1000be1d46fb5d114d094a59e350f9d025580f7297c8d9527d5: done           |++++++++++++++++++++++++++++++++++++++|\nconfig-sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1:   done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:7b1a6ab2e44dbac178598dabe7cff59bd67233dba0b27e4fbd1f9d4b3c877a54:    done           |++++++++++++++++++++++++++++++++++++++|\nelapsed: 1.2 s                                                                    total:  27.2 M (22.7 MiB/s)\n```\n\n`nerdctl run` also supports the same image name syntax.\nWhen specified, this command pulls the image from IPFS.\n\n```console\n> nerdctl run --rm -it ipfs://bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze echo hello\nhello\n```\n\nYou can also push that image to the container registry.\n\n```\nnerdctl tag ipfs://bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze ghcr.io/ktock/ubuntu:20.04-ipfs\nnerdctl push ghcr.io/ktock/ubuntu:20.04-ipfs\n```\n\nThe pushed image can run on other (IPFS-agnostic) runtimes.\n\n```console\n> docker run --rm -it ghcr.io/ktock/ubuntu:20.04-ipfs echo hello\nhello\n```\n\n:information_source: Note that though the IPFS-enabled image is OCI compatible, some runtimes including [containerd](https://github.com/containerd/containerd/pull/6221) and [podman](https://github.com/containers/image/pull/1403) had bugs and failed to pull that image. Containerd fixed this since v1.5.8, podman fixed this since commit [`b55fb86c28b7d743cf59701332cd78d4294c7c54`](https://github.com/containers/image/commit/b55fb86c28b7d743cf59701332cd78d4294c7c54).\n\n### `nerdctl build` and `localhost:5050/ipfs/<CID>` image reference\n\nYou can build images using base images on IPFS.\nBuildKit >= v0.9.3 is needed.\n\nIn Dockerfile, instead of `ipfs://` prefix, you need to use the following image reference to point to an image on IPFS.\n\n```\nlocalhost:5050/ipfs/<CID>\n```\n\nHere, `CID` is the IPFS CID of the image.\n\n:information_source: In the future version of nerdctl and BuildKit, `ipfs://` prefix should be supported in Dockerfile.\n\nUsing this image reference, you can build an image on IPFS.\n\n```dockerfile\nFROM localhost:5050/ipfs/bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\nRUN echo hello > /hello\n```\n\nMake sure that `nerdctl ipfs registry serve` is running.\nThis allows `nerdctl build` to pull images from IPFS.\n\n```\n$ nerdctl ipfs registry serve &\n```\n\nThen you can build this Dockerfile using `nerdctl build`.\n\n```console\n> nerdctl build -t hello .\n[+] Building 5.3s (6/6) FINISHED\n => [internal] load build definition from Dockerfile                                                                                              0.0s\n => => transferring dockerfile: 146B                                                                                                              0.0s\n => [internal] load .dockerignore                                                                                                                 0.0s\n => => transferring context: 2B                                                                                                                   0.0s\n => [internal] load metadata for localhost:5050/ipfs/bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze:latest                           0.1s\n => [1/2] FROM localhost:5050/ipfs/bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze@sha256:28bfa1fc6d491d3bee91bab451cab29c747e72917e  3.8s\n => => resolve localhost:5050/ipfs/bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze@sha256:28bfa1fc6d491d3bee91bab451cab29c747e72917e  0.0s\n => => sha256:7b1a6ab2e44dbac178598dabe7cff59bd67233dba0b27e4fbd1f9d4b3c877a54 28.57MB / 28.57MB                                                  2.1s\n => => extracting sha256:7b1a6ab2e44dbac178598dabe7cff59bd67233dba0b27e4fbd1f9d4b3c877a54                                                         1.7s\n => [2/2] RUN echo hello > /hello                                                                                                                 0.6s\n => exporting to oci image format                                                                                                                 0.6s\n => => exporting layers                                                                                                                           0.1s\n => => exporting manifest sha256:b96d490d134221ab121af91a42b13195dd8c5bf941012d7bfe07eabcf5259eda                                                 0.0s\n => => exporting config sha256:bd706574eab19009585b98826b06e63cf6eacf8d7193504dae75caa760332ca2                                                   0.0s\n => => sending tarball                                                                                                                            0.5s\nunpacking docker.io/library/hello:latest (sha256:b96d490d134221ab121af91a42b13195dd8c5bf941012d7bfe07eabcf5259eda)...done\n> nerdctl run --rm -it hello cat /hello\nhello\n```\n\n> NOTE: `--ipfs` flag has been removed since v1.2.0. You need to launch the localhost registry by yourself using `nerdctl ipfs registry serve`.\n\n#### Details about `localhost:5050/ipfs/<CID>` and `nerdctl ipfs registry`\n\nAs of now, BuildKit doesn't support `ipfs://` prefix so nerdctl achieves builds on IPFS by having a read-only local registry backed by IPFS.\nThis registry converts registry API requests to IPFS operations.\nSo IPFS-agnostic tools can pull images from IPFS via this registry.\n\nThis registry is provided as a subcommand `nerdctl ipfs registry`.\nThis command starts the registry backed by the IPFS repo of the current `$IPFS_PATH`\nBy default, nerdctl exposes the registry at `localhost:5050` (configurable via flags).\n\n<details>\n<summary>Creating systemd unit file for `nerdctl ipfs registry`</summary>\n\nOptionally you can create systemd unit file of `nerdctl ipfs registry serve`.\nAn example systemd unit file for `nerdctl ipfs registry serve` can be the following.\n`nerdctl ipfs registry serve` is aware of environment variables for configuring the behaviour (e.g. listening port) so you can use `EnvironmentFile` for configuring it.\n\n```\n[Unit]\nDescription=nerdctl ipfs registry serve\n\n[Service]\nEnvironmentFile-=/run/nerdctl-ipfs-registry-serve/env\nExecStart=nerdctl ipfs registry serve\n\n[Install]\nWantedBy=default.target\n```\n\n</details>\n\nThe following example starts the registry on `localhost:5555` instead of `localhost:5050`.\n\n```\nnerdctl ipfs registry serve --listen-registry=localhost:5555\n```\n\n> NOTE: You'll also need to restart the registry when you change `$IPFS_PATH` to use.\n\n> NOTE: `nerdctl ipfs registry [up|down]` has been removed since v1.2.0. You need to launch the localhost registry using `nerdctl ipfs registry serve` instead.\n\n### Compose on IPFS\n\n`nerdctl compose` supports same image name syntax to pull images from IPFS.\n\n```yaml\nversion: \"3.8\"\nservices:\n  ubuntu:\n    image: ipfs://bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\n    command: echo hello\n```\n\nWhen you build images using base images on IPFS, you can use `localhost:5050/ipfs/<CID>` image reference in Dockerfile as mentioned above.\n\n```\nnerdctl compose up --build\n```\n\n```\nnerdctl compose build\n```\n\n> NOTE: `--ipfs` flag has been removed since v1.2.0. You need to launch the localhost registry by yourself using `nerdctl ipfs registry serve`.\n\n### Encryption\n\nYou can distribute [encrypted images](./ocicrypt.md) on IPFS using OCIcrypt.\nPlease see [`/docs/ocicrypt.md`](./ocicrypt.md) for details about how to encrypt and decrypt an image.\n\nSame as normal images, the encrypted image can be pushed to IPFS using `ipfs://` prefix.\n\n```console\n> nerdctl image encrypt --recipient=jwe:mypubkey.pem ubuntu:20.04 ubuntu:20.04-encrypted\nsha256:a5c57411f3d11bb058b584934def0710c6c5b5a4a2d7e9b78f5480ecfc450740\n> nerdctl push ipfs://ubuntu:20.04-encrypted\nINFO[0000] pushing image \"ubuntu:20.04-encrypted\" to IPFS\nINFO[0000] ensuring image contents\nbafkreifajsysbvhtgd7fdgrfesszexdq6v5zbj5y2jnjfwxdjyqws2s3s4\n```\n\nYou can pull the encrypted image from IPFS using `ipfs://` prefix and can decrypt it in the same way as described in [`/docs/ocicrypt.md`](./ocicrypt.md).\n\n```console\n> nerdctl pull --unpack=false ipfs://bafkreifajsysbvhtgd7fdgrfesszexdq6v5zbj5y2jnjfwxdjyqws2s3s4\nbafkreifajsysbvhtgd7fdgrfesszexdq6v5zbj5y2jnjfwxdjyqws2s3s4:                      resolved       |++++++++++++++++++++++++++++++++++++++|\nindex-sha256:73334fee83139d1d8dbf488b28ad100767c38428b2a62504c758905c475c1d6c:    done           |++++++++++++++++++++++++++++++++++++++|\nmanifest-sha256:8855ae825902045ea2b27940634673ba410b61885f91b9f038f6b3303f48727c: done           |++++++++++++++++++++++++++++++++++++++|\nconfig-sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1:   done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:e74a9a7749e808e4ad1e90d5a81ce3146ce270de0fbdf22429cd465df8f10a13:    done           |++++++++++++++++++++++++++++++++++++++|\nelapsed: 0.3 s                                                                    total:  22.0 M (73.2 MiB/s)\n> nerdctl image decrypt --key=mykey.pem ipfs://bafkreifajsysbvhtgd7fdgrfesszexdq6v5zbj5y2jnjfwxdjyqws2s3s4 ubuntu:20.04-decrypted\nsha256:b0ccaddb7e7e4e702420de126468eab263eb0f3c25abf0b957ce8adcd1e82105\n> nerdctl run --rm -it ubuntu:20.04-decrypted echo hello\nhello\n```\n\n## Running containers on IPFS with eStargz-based lazy pulling\n\nnerdctl supports running eStargz images on IPFS with lazy pulling using Stargz Snapshotter.\n\nIn this configuration, Stargz Snapshotter mounts the eStargz image from IPFS to the container's rootfs using FUSE with lazy pulling support.\nThus the container can startup without waiting for the entire image contents to be locally available.\nYou can see faster container cold-start.\n\nTo use this feature, you need to enable Stargz Snapshotter following [`/docs/stargz.md`](./stargz.md).\nYou also need to add the following configuration to `config.toml` of Stargz Snapshotter (typically located at `/etc/containerd-stargz-grpc/config.toml`).\n\n```toml\nipfs = true\n```\n\nYou can push an arbitrary image to IPFS with converting it to eStargz using `--estargz` option.\n\n```\nnerdctl push --estargz ipfs://fedora:36\n```\n\nYou can pull and run that eStargz image with lazy pulling.\n\n```\nnerdctl run --rm -it ipfs://bafkreibp2ncujcia663uum25ustwvmyoguxqyzjnxnlhebhsgk2zowscye echo hello\n```\n\n- See [the doc in stargz-snapshotter project](https://github.com/containerd/stargz-snapshotter/blob/v0.10.0/docs/ipfs.md) for details about lazy pulling on IPFS.\n- See [`/docs/stargz.md`](./stargz.md) for details about the configuration of nerdctl for Stargz Snapshotter.\n"
  },
  {
    "path": "docs/multi-platform.md",
    "content": "# Multi-platform\n\n| :zap: Requirement | nerdctl >= 0.13 |\n|-------------------|-----------------|\n\nnerdctl can execute non-native container images using QEMU.\ne.g., ARM on Intel, and vice versa.\n\n## Preparation: Register QEMU to `/proc/sys/fs/binfmt_misc`\n\n```console\n$ sudo systemctl start containerd\n\n$ sudo nerdctl run --privileged --rm tonistiigi/binfmt:master --install all\n\n$ ls -1 /proc/sys/fs/binfmt_misc/qemu*\n/proc/sys/fs/binfmt_misc/qemu-aarch64\n/proc/sys/fs/binfmt_misc/qemu-arm\n/proc/sys/fs/binfmt_misc/qemu-loongarch64\n/proc/sys/fs/binfmt_misc/qemu-mips64\n/proc/sys/fs/binfmt_misc/qemu-mips64el\n/proc/sys/fs/binfmt_misc/qemu-ppc64le\n/proc/sys/fs/binfmt_misc/qemu-riscv64\n/proc/sys/fs/binfmt_misc/qemu-s390x\n```\n\nThe `tonistiigi/binfmt` container must be executed with `--privileged`, and with rootful mode (`sudo`).\n\nThis container is not a daemon, and exits immediately after registering QEMU to `/proc/sys/fs/binfmt_misc`.\nRun `ls -1 /proc/sys/fs/binfmt_misc/qemu*` to confirm registration.\n\nSee also https://github.com/tonistiigi/binfmt\n\n## Usage\n### Pull & Run\n\n```console\n$ nerdctl pull --platform=arm64,s390x alpine\n\n$ nerdctl run --rm --platform=arm64 alpine uname -a\nLinux e6227935cf12 5.13.0-19-generic #19-Ubuntu SMP Thu Oct 7 21:58:00 UTC 2021 aarch64 Linux\n\n$ nerdctl run --rm --platform=s390x alpine uname -a\nLinux b39da08fbdbf 5.13.0-19-generic #19-Ubuntu SMP Thu Oct 7 21:58:00 UTC 2021 s390x Linux\n```\n\n### Build & Push\n```console\n$ nerdctl build --platform=amd64,arm64 --output type=image,name=example.com/foo:latest,push=true .\n```\n\nOr\n\n```console\n$ nerdctl build --platform=amd64,arm64 -t example.com/foo:latest .\n$ nerdctl push --all-platforms example.com/foo:latest\n```\n\n### Compose\nSee [`../examples/compose-multi-platform`](../examples/compose-multi-platform)\n\n## macOS + Lima\n\nAs of 2025-03-01, qemu seems to be broken in most Apple-silicon setups.\nThis might be due to qemu handling of host vs. guest page sizes\n(unconfirmed, see https://github.com/containerd/nerdctl/issues/3948 for more information).\n\nIt should also be noted that Linux 6.11 introduced a change to the VDSO (on ARM)\nthat does break Rosetta.\n\nThe take-away here is that presumably your only shot at running non-native binaries\non Apple-silicon is to use an older kernel for your guest (<6.11), typically as shipped by Debian stable,\nand also to use VZ+Rosetta and not qemu (eg: `limactl create --vm-type=vz --rosetta`)."
  },
  {
    "path": "docs/notation.md",
    "content": "# Container Image Sign and Verify with notation tool\n\n| :zap: Requirement | nerdctl >= 1.3.0 |\n|-------------------|------------------|\n\n[notation](https://github.com/notaryproject/notation) is a project to add signatures as standard items in the registry ecosystem, and to build a set of simple tooling for signing and verifying these signatures.\n\nYou can enable container signing and verifying features with `push` and `pull` commands of `nerdctl` by using `notation`\nunder the hood with make use of flags `--sign` while pushing the container image, and `--verify` while pulling the\ncontainer image.\n\n* Ensure notation executable in your `$PATH`.\n* You can install notation by following this page: https://notaryproject.dev/docs/user-guides/installation/cli/\n* Notation follows the RC of OCI spec v1.1.0. Follow the [instruction](https://notaryproject.dev/docs/quickstart/#create-an-oci-compatible-registry) to set up the local registry with the compliance for testing purpose.\n\nPrepare your environment:\n\n```shell\n# Create a sample Dockerfile\n$ cat <<EOF | tee Dockerfile.dummy\nFROM alpine:latest\nCMD [ \"echo\", \"Hello World\" ]\nEOF\n```\n\n> Please do not forget, we won't be validating the base images, which is `alpine:latest` in this case, of the container image that was built on,\n> we'll only verify the container image itself once we sign it.\n\n```shell\n\n# Build the image\n$ nerdctl build -t localhost:5000/my-test -f Dockerfile.dummy .\n\n# Generate a key-pair in notation's key store and trust store\n$ notation cert generate-test --default \"test\"\n\n# Confirm the signing key is correctly configured. Key name with a * prefix is the default key.\n$ notation key ls\n\n# Confirm the certificate is stored in the trust store.\n$ notation cert ls\n```\n\nSign the container image while pushing:\n\n```\n# Sign the image and store the signature in the registry\n$ nerdctl push --sign=notation --notation-key-name test localhost:5000/my-test\n```\n\nVerify the container image while pulling:\n\n> REMINDER: Image won't be pulled if there are no matching signatures with the cert in the [trust policy](https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#trust-policy) in case you passed `--verify` flag.\n\n```shell\n# Create `trustpolicy.json` under $XDG_CONFIG_HOME/notation (XDG_CONFIG_HOME is ~/.config below)\ncat <<EOF | tee ~/.config/notation/trustpolicy.json\n{\n    \"version\": \"1.0\",\n    \"trustPolicies\": [\n        {\n            \"name\": \"test-images\",\n            \"registryScopes\": [ \"*\" ],\n            \"signatureVerification\": {\n                \"level\" : \"strict\"\n            },\n            \"trustStores\": [ \"ca:test\" ],\n            \"trustedIdentities\": [\n                \"*\"\n            ]\n        }\n    ]\n}\nEOF\n\n# Verify the image\n$ nerdctl pull --verify=notation localhost:5000/my-test\n\n# You can not verify the image if it is not signed by the cert in the trust policy\n$ nerdctl pull --verify=notation localhost:5000/my-test-bad\n```\n"
  },
  {
    "path": "docs/nydus.md",
    "content": "# Lazy-pulling using Nydus Snapshotter\n\n| :zap: Requirement | nerdctl >= 0.22 |\n| ----------------- | --------------- |\n\nNydus snapshotter is a remote snapshotter plugin of containerd for [Nydus](https://github.com/dragonflyoss/image-service) image service which implements a chunk-based content-addressable filesystem that improves the current OCI image specification, in terms of container launching speed, image space, and network bandwidth efficiency, as well as data integrity with several runtime backends: FUSE, virtiofs and in-kernel EROFS (Linux kernel 5.19+).\n\n## Enable lazy-pulling for `nerdctl run`\n\n- Install containerd remote snapshotter plugin (`containerd-nydus-grpc`) from https://github.com/containerd/nydus-snapshotter\n\n- Add the following to `/etc/containerd/config.toml`:\n```toml\n[proxy_plugins]\n  [proxy_plugins.nydus]\n    type = \"snapshot\"\n    address = \"/run/containerd-nydus-grpc/containerd-nydus-grpc.sock\"\n\n# Optional: Configure nydus for image unpacking (allows automatic snapshotter selection)\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"nydus\"\n```\n\n- Launch `containerd` and `containerd-nydus-grpc`\n\n- Run `nerdctl` with `--snapshotter=nydus`\n```console\n# nerdctl --snapshotter=nydus run -it --rm ghcr.io/dragonflyoss/image-service/ubuntu:nydus-nightly-v5\n```\n\nFor the list of pre-converted Nydus images, see https://github.com/orgs/dragonflyoss/packages?page=1&repo_name=image-service\n\n## Build Nydus image using `nerdctl image convert`\n\nNerdctl supports to convert an OCI image or docker format v2 image to Nydus image by using the `nerdctl image convert` command.\n\nBefore the conversion, you should have the `nydus-image` binary installed, which is contained in the [\"nydus static package\"](https://github.com/dragonflyoss/image-service/releases). You can run the command like `nerdctl image convert --nydus --oci --nydus-builder-path <the_path_of_nydus_image_binary> <source_image> <target_image>` to convert the `<source_image>` to a Nydus image whose tag is `<target_image>`.\n\nBy now, the converted Nydus image cannot be run directly. It shoud be unpacked to nydus snapshotter before `nerdctl run`, which is a part of the processing flow of `nerdctl image pull`. So you need to push the converted image to a registry after the conversion and use `nerdctl --snapshotter nydus image pull` to unpack it to the nydus snapshotter before running the image.\n\nOptionally, you can use the nydusify conversion tool to check if the format of the converted Nydus image is valid. For more details about the Nydus image validation and how to build Nydus image, please refer to [nydusify](https://github.com/dragonflyoss/image-service/blob/master/docs/nydusify.md) and [acceld](https://github.com/goharbor/acceleration-service).\n"
  },
  {
    "path": "docs/ocicrypt.md",
    "content": "# OCIcrypt\n\n| :zap: Requirement | nerdctl >= 0.7 |\n|-------------------|----------------|\n\nnerdctl supports encryption and decryption using [OCIcrypt](https://github.com/containers/ocicrypt)\n(aka [imgcrypt](https://github.com/containerd/imgcrypt) for containerd).\n\n## JWE mode\n\n### Encryption\n\nUse `openssl` to create a private key (`mykey.pem`) and the corresponding public key (`mypubkey.pem`):\n```bash\nopenssl genrsa -out mykey.pem\nopenssl rsa -in mykey.pem -pubout -out mypubkey.pem\n```\n\nUse `nerdctl image encrypt` to create an encrypted image:\n```bash\nnerdctl image encrypt --recipient=jwe:mypubkey.pem --platform=linux/amd64,linux/arm64 foo example.com/foo:encrypted\nnerdctl push example.com/foo:encrypted\n```\n\n:warning: CAUTION: This command only encrypts image layers, but does NOT encrypt [container configuration such as `Env` and `Cmd`](https://github.com/opencontainers/image-spec/blob/v1.0.1/config.md#example).\nTo see non-encrypted information, run `nerdctl image inspect --mode=native --platform=PLATFORM example.com/foo:encrypted` .\n\n### Decryption\n\n#### Configuration\nPut the private key files to `/etc/containerd/ocicrypt/keys` (for rootless `~/.config/containerd/ocicrypt/keys`).\n\n<details>\n<summary>Extra step for containerd 1.4 and older</summary>\n\n<p>\n\ncontainerd 1.4 and older requires adding the following configuration to `/etc/containerd/config.toml`\n(for rootless `~/.config/containerd/config.toml`):\n\n```toml\nversion = 2\n\n[stream_processors]\n  [stream_processors.\"io.containerd.ocicrypt.decoder.v1.tar.gzip\"]\n    accepts = [\"application/vnd.oci.image.layer.v1.tar+gzip+encrypted\"]\n    returns = \"application/vnd.oci.image.layer.v1.tar+gzip\"\n    path = \"ctd-decoder\"\n    args = [\"--decryption-keys-path\", \"/etc/containerd/ocicrypt/keys\"]\n  [stream_processors.\"io.containerd.ocicrypt.decoder.v1.tar\"]\n    accepts = [\"application/vnd.oci.image.layer.v1.tar+encrypted\"]\n    returns = \"application/vnd.oci.image.layer.v1.tar\"\n    path = \"ctd-decoder\"\n    args = [\"--decryption-keys-path\", \"/etc/containerd/ocicrypt/keys\"]\n\n# NOTE: On rootless, ~/.config/containerd is mounted as /etc/containerd in the namespace.\n```\n\n</p>\n\n</details>\n\n#### Running nerdctl\n\nNo flag is needed for running encrypted images with `nerdctl run`, as long as the private key is stored\nin `/etc/containerd/ocicrypt/keys` (for rootless `~/.config/containerd/ocicrypt/keys`).\n\nJust run `nerdctl run example.com/encrypted-image`.\n\nTo decrypt an image without running a container, use `nerdctl image decrypt` command:\n```bash\nnerdctl pull --unpack=false example.com/foo:encrypted\nnerdctl image decrypt --key=mykey.pem example.com/foo:encrypted foo:decrypted\n```\n\n## PGP (GPG) mode\n(Undocumented yet)\n\n## PKCS7 mode\n(Undocumented yet)\n\n## PKCS11 mode\n(Undocumented yet)\n\n## More information\n- https://github.com/containerd/imgcrypt (High-level library for containerd, using `containers/ocicrypt`)\n- https://github.com/containers/ocicrypt (Low-level library, used by `containerd/imgcrypt`)\n- https://github.com/opencontainers/image-spec/pull/775 (Proposal for OCI Image Spec)\n- https://github.com/containerd/containerd/blob/main/docs/cri/decryption.md (configuration guide)\n  - The `plugins.\"io.containerd.grpc.v1.cri\"` section does not apply to nerdctl, as nerdctl does not use CRI\n"
  },
  {
    "path": "docs/overlaybd.md",
    "content": "# Lazy-pulling using OverlayBD Snapshotter\n\n| :zap: Requirement | nerdctl >= 0.15.0 |\n| ----------------- | --------------- |\n\nOverlayBD is a remote container image format base on block-device which 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)\n\nSee https://github.com/containerd/accelerated-container-image to learn further information.\n\n## Enable lazy-pulling for `nerdctl run`\n\n- Install containerd remote snapshotter plugin (`overlaybd`) from https://github.com/containerd/accelerated-container-image/blob/main/docs/BUILDING.md\n\n- Add the following to `/etc/containerd/config.toml`:\n```toml\n[proxy_plugins]\n  [proxy_plugins.overlaybd]\n    type = \"snapshot\"\n    address = \"/run/overlaybd-snapshotter/overlaybd.sock\"\n\n# Optional: Configure overlaybd for image unpacking (allows automatic snapshotter selection)\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"overlaybd\"\n```\n\n- Launch `containerd` and `overlaybd-snapshotter`\n\n- Run `nerdctl` with `--snapshotter=overlaybd`\n```console\nnerdctl run --net host -it --rm --snapshotter=overlaybd registry.hub.docker.com/overlaybd/redis:6.2.1_obd\n```\n\nFor more details about how to build overlaybd image, please refer to [accelerated-container-image](https://github.com/containerd/accelerated-container-image/blob/main/docs/IMAGE_CONVERTOR.md) conversion tool.\n\n## Build OverlayBD image using `nerdctl image convert`\n\nNerdctl supports to convert an OCI image or docker format v2 image to OverlayBD image by using the `nerdctl image convert` command.\n\nBefore the conversion, you should have the `overlaybd-snapshotter` binary installed, which build from [accelerated-container-image](https://github.com/containerd/accelerated-container-image). You can run the command like `nerdctl image convert --overlaybd --oci <source_image> <target_image>` to convert the `<source_image>` to a OverlayBD image whose tag is `<target_image>`.\n"
  },
  {
    "path": "docs/registry.md",
    "content": "# registry authentication\n\nnerdctl uses `${DOCKER_CONFIG}/config.json` for the authentication with image registries.\n\n`$DOCKER_CONFIG` defaults to `$HOME/.docker`.\n\n## Using insecure registry\n\nIf you face `http: server gave HTTP response to HTTPS client` and you cannot configure TLS for the registry, try `--insecure-registry` flag:\n\ne.g.,\n```console\n$ nerdctl --insecure-registry run --rm 192.168.12.34:5000/foo\n```\n\n## Specifying certificates\n\n\n| :zap: Requirement | nerdctl >= 0.16 |\n|-------------------|-----------------|\n\n\nCreate `~/.config/containerd/certs.d/<HOST:PORT>/hosts.toml` (or `/etc/containerd/certs.d/...` for rootful) to specify `ca` certificates.\n\n```toml\n# An example of ~/.config/containerd/certs.d/192.168.12.34:5000/hosts.toml\n# (The path is \"/etc/containerd/certs.d/192.168.12.34:5000/hosts.toml\" for rootful)\n\nserver = \"https://192.168.12.34:5000\"\n[host.\"https://192.168.12.34:5000\"]\n  ca = \"/path/to/ca.crt\"\n```\n\nSee https://github.com/containerd/containerd/blob/main/docs/hosts.md for the syntax of `hosts.toml` .\n\nDocker-style directories are also supported.\nThe path is `~/.config/docker/certs.d` for rootless, `/etc/docker/certs.d` for rootful.\n\n## Accessing 127.0.0.1 from rootless nerdctl\n\nCurrently, rootless nerdctl cannot pull images from 127.0.0.1, because\nthe pull operation occurs in RootlessKit's network namespace.\n\nSee https://github.com/containerd/nerdctl/issues/86 for the discussion about workarounds.\n\n- - -\n\n# Using managed registry services\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n\n- [Amazon Elastic Container Registry (ECR)](#amazon-elastic-container-registry-ecr)\n  - [Logging in](#logging-in)\n  - [Creating a repo](#creating-a-repo)\n  - [Pushing an image](#pushing-an-image)\n- [Azure Container Registry (ACR)](#azure-container-registry-acr)\n  - [Creating a registry](#creating-a-registry)\n  - [Logging in](#logging-in-1)\n  - [Creating a repo](#creating-a-repo-1)\n  - [Pushing an image](#pushing-an-image-1)\n- [Docker Hub](#docker-hub)\n  - [Logging in](#logging-in-2)\n  - [Creating a repo](#creating-a-repo-2)\n  - [Pushing an image](#pushing-an-image-2)\n- [GitHub Container Registry (GHCR)](#github-container-registry-ghcr)\n  - [Logging in](#logging-in-3)\n  - [Creating a repo](#creating-a-repo-3)\n  - [Pushing an image](#pushing-an-image-3)\n- [GitLab Container Registry](#gitlab-container-registry)\n  - [Logging in](#logging-in-4)\n  - [Creating a repo](#creating-a-repo-4)\n  - [Pushing an image](#pushing-an-image-4)\n- [Google Artifact Registry (pkg.dev)](#google-artifact-registry-pkgdev)\n  - [Logging in](#logging-in-5)\n  - [Creating a repo](#creating-a-repo-5)\n  - [Pushing an image](#pushing-an-image-5)\n- [Google Container Registry (GCR) [DEPRECATED]](#google-container-registry-gcr-deprecated)\n  - [Logging in](#logging-in-6)\n  - [Creating a repo](#creating-a-repo-6)\n  - [Pushing an image](#pushing-an-image-6)\n- [JFrog Artifactory (Cloud/On-Prem)](#jfrog-artifactory-cloudon-prem)\n  - [Logging in](#logging-in-7)\n  - [Creating a repo](#creating-a-repo-7)\n  - [Pushing an image](#pushing-an-image-7)\n- [Quay.io](#quayio)\n  - [Logging in](#logging-in-8)\n  - [Creating a repo](#creating-a-repo-8)\n  - [Pushing an image](#pushing-an-image-8)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Amazon Elastic Container Registry (ECR)\n\nSee also https://aws.amazon.com/ecr\n\n### Logging in\n\n```console\n$ aws ecr get-login-password --region <REGION> | nerdctl login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com\nLogin Succeeded\n```\n\n<details>\n<summary>Alternative method: <code>docker-credential-ecr-login</code></summary>\n\nThis methods is more secure but needs an external dependency.\n\n<p>\n\nInstall `docker-credential-ecr-login` from https://github.com/awslabs/amazon-ecr-credential-helper , and create the following files:\n\n`~/.docker/config.json`:\n```json\n{\n  \"credHelpers\": {\n    \"public.ecr.aws\": \"ecr-login\",\n    \"<AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com\": \"ecr-login\"\n  }\n}\n```\n\n`~/.aws/credentials`:\n```\n[default]\naws_access_key_id = ...\naws_secret_access_key = ...\n```\n\n> **Note**: If you are running nerdctl inside a VM (including Lima, Colima, Rancher Desktop, and WSL2), `docker-credential-ecr-login` has to be installed inside the guest, not the host.\n> Same applies to the path of `~/.docker/config.json` and `~/.aws/credentials`, too.\n\n</p>\n</details>\n\n### Creating a repo\n\nYou have to create a repository via https://console.aws.amazon.com/ecr/home/ .\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<REPO>\n$ nerdctl push <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<REPO>\n```\n\nThe pushed image appears in the repository you manually created in the previous step.\n\n## Azure Container Registry (ACR)\nSee also https://azure.microsoft.com/en-us/services/container-registry/#overview\n\n### Creating a registry\n\nYou have to create a \"Container registry\" resource manually via [the Azure portal](https://portal.azure.com/).\n\n### Logging in\n```console\n$ nerdctl login -u <USERNAME> <REGISTRY>.azurecr.io\nEnter Password: ********[Enter]\n\nLogin Succeeded\n```\n\nThe login credentials can be found as \"Access keys\" in [the Azure portal](https://portal.azure.com/).\nSee also https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication .\n\n> **Note**: nerdctl prior to v0.16.1 had a bug that required pressing the Enter key twice.\n\n### Creating a repo\nYou do not need to create a repo explicitly.\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world <REGISTRY>.azurecr.io/hello-world\n$ nerdctl push <REGISTRY>.azurecr.io/hello-world\n```\n\nThe pushed image appears in [the Azure portal](https://portal.azure.com/).\nPrivate as default.\n\n## Docker Hub\nSee also https://hub.docker.com/\n\n### Logging in\n```console\n$ nerdctl login -u <USERNAME>\nEnter Password: ********[Enter]\n\nLogin Succeeded\n```\n\n> **Note**: nerdctl prior to v0.16.1 had a bug that required pressing the Enter key twice.\n\n### Creating a repo\nYou do not need to create a repo explicitly, for public images.\n\nTo create a private repo, see https://hub.docker.com/repositories .\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world <USERNAME>/hello-world\n$ nerdctl push <USERNAME>/hello-world\n```\n\nThe pushed image appears in https://hub.docker.com/repositories .\n**Public** by default.\n\n## GitHub Container Registry (GHCR)\nSee also https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry\n\n### Logging in\n\n```console\n$ nerdctl login ghcr.io -u <USERNAME>\nEnter Password: ********[Enter]\n\nLogin Succeeded\n```\n\nThe `<USERNAME>` is your GitHub username but in lower characters.\n\nThe \"Password\" here is a [GitHub Personal access token](https://github.com/settings/tokens), with `read:packages` and `write:packages` scopes.\n\n> **Note**: nerdctl prior to v0.16.1 had a bug that required pressing the Enter key twice.\n\n### Creating a repo\nYou do not need to create a repo explicitly.\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world ghcr.io/<USERNAME>/hello-world\n$ nerdctl push ghcr.io/<USERNAME>/hello-world\n```\n\nThe pushed image appears in the \"Packages\" tab of your GitHub profile.\nPrivate as default.\n\n## GitLab Container Registry\nSee also https://docs.gitlab.com/ee/user/packages/container_registry/\n\n### Logging in\n\n```console\n$ nerdctl login registry.gitlab.com -u <USERNAME>\nEnter Password: ********[Enter]\n\nLogin Succeeded\n```\n\nThe `<USERNAME>` is your GitLab username.\n\nThe \"Password\" here is either a [GitLab Personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) or a [GitLab Deploy token](https://docs.gitlab.com/ee/user/project/deploy_tokens/index.html). Both options require minimum scope of `read_registry` for pull access and both `write_registry` and `read_registry` scopes for push access.\n\n> **Note**: nerdctl prior to v0.16.1 had a bug that required pressing the Enter key twice.\n\n### Creating a repo\nContainer registries in GitLab are created at the project level. A project in GitLab must exist first before you begin working with its container registry.\n\n### Pushing an image\n\nIn this example we have created a GitLab project named `myproject`.\n\n```console\n$ nerdctl tag hello-world registry.gitlab.com/<USERNAME>/myproject/hello-world:latest\n$ nerdctl push registry.gitlab.com/<USERNAME>/myproject/hello-world:latest\n```\n\nThe pushed image appears under the \"Packages & Registries -> Container Registry\" tab of your project on GitLab.\n\n## Google Artifact Registry (pkg.dev)\nSee also https://cloud.google.com/artifact-registry/docs/docker/quickstart\n\n### Logging in\n\nCreate a [GCP Service Account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating), grant\n`Artifact Registry Reader` and `Artifact Registry Writer` roles, and download the key as a JSON file.\n\nThen run the following command:\n\n```console\n$ cat <GCP_SERVICE_ACCOUNT_KEY_JSON> | nerdctl login -u _json_key --password-stdin https://<REGION>-docker.pkg.dev\nWARNING! Your password will be stored unencrypted in /home/<USERNAME>/.docker/config.json.\nConfigure a credential helper to remove this warning. See\nhttps://docs.docker.com/engine/reference/commandline/login/#credentials-store\n\nLogin Succeeded\n```\n\nSee also https://cloud.google.com/artifact-registry/docs/docker/authentication\n\n\n<details>\n<summary>Alternative method: <code>docker-credential-gcloud</code> (<code>gcloud auth configure-docker</code>)</summary>\n\nThis methods is more secure but needs an external dependency.\n\n<p>\n\nRun `gcloud auth configure-docker <REGION>-docker.pkg.dev`, e.g.,\n\n```console\n$ gcloud auth configure-docker asia-northeast1-docker.pkg.dev\nAdding credentials for: asia-northeast1-docker.pkg.dev\nAfter update, the following will be written to your Docker config file located at [/home/<USERNAME>/.docker/config.json]:\n {\n  \"credHelpers\": {\n    \"asia-northeast1-docker.pkg.dev\": \"gcloud\"\n  }\n}\n\nDo you want to continue (Y/n)?  y\n\nDocker configuration file updated.\n```\n\nGoogle Cloud SDK (`gcloud`, `docker-credential-gcloud`) has to be installed, see https://cloud.google.com/sdk/docs/quickstart .\n\n> **Note**: If you are running nerdctl inside a VM (including Lima, Colima, Rancher Desktop, and WSL2), the Google Cloud SDK has to be installed inside the guest, not the host.\n\n</p>\n</details>\n\n### Creating a repo\n\nYou have to create a repository via https://console.cloud.google.com/artifacts .\nChoose \"Docker\" as the repository format.\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world <REGION>-docker.pkg.dev/<GCP_PROJECT_ID>/<REPO>/hello-world\n$ nerdctl push <REGION>-docker.pkg.dev/<GCP_PROJECT_ID>/<REPO>/hello-world\n```\n\nThe pushed image appears in the repository you manually created in the previous step.\n\n## Google Container Registry (GCR) [DEPRECATED]\nSee also https://cloud.google.com/container-registry/docs/advanced-authentication\n\n### Logging in\n\nCreate a [GCP Service Account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating), grant\n`Storage Object Admin` role, and download the key as a JSON file.\n\nThen run the following command:\n\n```console\n$ cat <GCP_SERVICE_ACCOUNT_KEY_JSON> | nerdctl login -u _json_key --password-stdin https://asia.gcr.io\nWARNING! Your password will be stored unencrypted in /home/<USERNAME>/.docker/config.json.\nConfigure a credential helper to remove this warning. See\nhttps://docs.docker.com/engine/reference/commandline/login/#credentials-store\n\nLogin Succeeded\n```\n\nSee also https://cloud.google.com/container-registry/docs/advanced-authentication\n\n<details>\n<summary>Alternative method: <code>docker-credential-gcloud</code> (<code>gcloud auth configure-docker</code>)</summary>\n\nThis methods is more secure but needs an external dependency.\n\n<p>\n\n```console\n$ gcloud auth configure-docker\nAdding credentials for all GCR repositories.\nWARNING: A long list of credential helpers may cause delays running 'docker build'. We recommend passing the registry name to configure only the registry you are using.\nAfter update, the following will be written to your Docker config file located at [/home/<USERNAME>/.docker/config.json]:\n {\n  \"credHelpers\": {\n    \"gcr.io\": \"gcloud\",\n    \"us.gcr.io\": \"gcloud\",\n    \"eu.gcr.io\": \"gcloud\",\n    \"asia.gcr.io\": \"gcloud\",\n    \"staging-k8s.gcr.io\": \"gcloud\",\n    \"marketplace.gcr.io\": \"gcloud\"\n  }\n}\n\nDo you want to continue (Y/n)?  y\n\nDocker configuration file updated.\n```\n\nGoogle Cloud SDK (`gcloud`, `docker-credential-gcloud`) has to be installed, see https://cloud.google.com/sdk/docs/quickstart .\n\n> **Note**: If you are running nerdctl inside a VM (including Lima, Colima, Rancher Desktop, and WSL2), the Google Cloud SDK has to be installed inside the guest, not the host.\n\n</p>\n</details>\n\n### Creating a repo\nYou do not need to create a repo explicitly.\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world asia.gcr.io/<GCP_PROJECT_ID>/hello-world\n$ nerdctl push asia.gcr.io/<GCP_PROJECT_ID>/hello-world\n```\n\nThe pushed image appears in https://console.cloud.google.com/gcr/ .\nPrivate by default.\n\n## JFrog Artifactory (Cloud/On-Prem)\nSee also https://www.jfrog.com/confluence/display/JFROG/Getting+Started+with+Artifactory+as+a+Docker+Registry\n\n### Logging in\n```console\n$ nerdctl login <SERVER_NAME>.jfrog.io -u <USERNAME>\nEnter Password: ********[Enter]\n\nLogin Succeeded\n```\n\nLogin using the default username: admin, and password: password for the on-prem installation, or the credentials provided to you by email for the cloud installation.\nJFrog Platform is integrated with OAuth allowing you to delegate authentication requests to external providers (the provider types supported are Google, OpenID Connect, GitHub Enterprise, and Cloud Foundry UAA)\n\n> **Note**: nerdctl prior to v0.16.1 had a bug that required pressing the Enter key twice.\n\n### Creating a repo\n1. Add local Docker repository\n   1. Add a new Local Repository with the Docker package type via `https://<server-name>.jfrog.io/ui/admin/repositories/local/new`.\n2. Add virtual Docker repository\n   1. Add a new virtual repository with the Docker package type via `https://<server-name>.jfrog.io/ui/admin/repositories/virtual/new`.\n   2. Add the local docker repository you created in Steps 1 (move it from Available Repositories to Selected Repositories using the arrow buttons).\n   3. Set local repository as a default local deployment repository.\n\n### Pushing an image\n```console\n$ nerdctl tag hello-world <SERVER_NAME>.jfrog.io/<VIRTUAL_REPO_NAME>/hello-world\n$ nerdctl push <SERVER_NAME>.jfrog.io/<VIRTUAL_REPO_NAME>/hello-world\n```\n\nThe `SERVER_NAME` is the first part of the URL given to you for your environment: `https://<SERVER_NAME>.jfrog.io`\n\nThe `VIRTUAL_REPO_NAME` is the name “docker” that you assigned to your virtual repository in 2.i .\n\nThe pushed image appears in `https://<SERVER_NAME>.jfrog.io/ui/repos/tree/General/<VIRTUAL_REPO_NAME>` .\nPrivate by default.\n\n## Quay.io\nSee also https://docs.quay.io/solution/getting-started.html\n\n### Logging in\n\n```console\n$ nerdctl login quay.io -u <USERNAME>\nEnter Password: ********[Enter]\n\nLogin Succeeded\n```\n\n> **Note**: nerdctl prior to v0.16.1 had a bug that required pressing the Enter key twice.\n\n### Creating a repo\nYou do not need to create a repo explicitly.\n\n### Pushing an image\n\n```console\n$ nerdctl tag hello-world quay.io/<USERNAME>/hello-world\n$ nerdctl push quay.io/<USERNAME>/hello-world\n```\n\nThe pushed image appears in https://quay.io/repository/ .\nPrivate as default.\n"
  },
  {
    "path": "docs/rootless.md",
    "content": "# Rootless mode\n\nSee https://rootlesscontaine.rs/getting-started/common/ for the prerequisites.\n\n## Daemon (containerd)\n\nUse [`containerd-rootless-setuptool.sh`](../extras/rootless) to set up rootless containerd.\n\n```console\n$ containerd-rootless-setuptool.sh install\n[INFO] Checking RootlessKit functionality\n[INFO] Checking cgroup v2\n[INFO] Checking overlayfs\n[INFO] Creating /home/testuser/.config/systemd/user/containerd.service\n...\n[INFO] Installed containerd.service successfully.\n[INFO] To control containerd.service, run: `systemctl --user (start|stop|restart) containerd.service`\n[INFO] To run containerd.service on system startup, run: `sudo loginctl enable-linger testuser`\n\n[INFO] Use `nerdctl` to connect to the rootless containerd.\n[INFO] You do NOT need to specify $CONTAINERD_ADDRESS explicitly.\n```\n\nThe usage of `containerd-rootless-setuptool.sh` is almost same as [`dockerd-rootless-setuptool.sh`](https://rootlesscontaine.rs/getting-started/docker/) .\n\nResource limitation flags such as `nerdctl run --memory` require systemd and cgroup v2: https://rootlesscontaine.rs/getting-started/common/cgroup2/\n\n#### AppArmor Profile for Ubuntu 24.04+\n\nConfiguring AppArmor is needed only on Ubuntu 24.04+, with RootlessKit installed under a non-standard path: https://rootlesscontaine.rs/getting-started/common/apparmor/\n\n## Client (nerdctl)\n\nJust execute `nerdctl`. No need to specify the socket address manually.\n\n```console\n$ nerdctl run -it --rm alpine\n```\n\nDepending on your kernel version, you may need to enable FUSE-OverlayFS or set `export CONTAINERD_SNAPSHOTTER=native`.\n(See below.)\n\n## Add-ons\n### BuildKit\nTo enable BuildKit, run the following command:\n\n```console\n$ containerd-rootless-setuptool.sh install-buildkit\n```\n\n## Snapshotters\n\n### OverlayFS\n\nThe default `overlayfs` snapshotter only works on the following hosts:\n- Any distro, with kernel >= 5.13\n- Non-SELinux distro, with kernel >= 5.11\n- Ubuntu since 2015\n\nFor other hosts, [`fuse-overlayfs` snapshotter](https://github.com/containerd/fuse-overlayfs-snapshotter) needs to be used instead.\n\n### FUSE-OverlayFS\n\nTo enable `fuse-overlayfs` snapshotter, run the following command:\n```console\n$ containerd-rootless-setuptool.sh install-fuse-overlayfs\n```\n\nThen, add the following config to `~/.config/containerd/config.toml`, and run `systemctl --user restart containerd.service`:\n```toml\n[proxy_plugins]\n  [proxy_plugins.\"fuse-overlayfs\"]\n      type = \"snapshot\"\n# NOTE: replace \"1000\" with your actual UID\n      address = \"/run/user/1000/containerd-fuse-overlayfs.sock\"\n\n# Optional: Configure fuse-overlayfs for image unpacking (allows automatic snapshotter selection)\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"fuse-overlayfs\"\n```\n\nThe snapshotter can be specified as `$CONTAINERD_SNAPSHOTTER`.\n```console\n$ export CONTAINERD_SNAPSHOTTER=fuse-overlayfs\n$ nerdctl run -it --rm alpine\n```\n\nIf `fuse-overlayfs` does not work, try `export CONTAINERD_SNAPSHOTTER=native`.\n\n### Stargz Snapshotter\n[Stargz Snapshotter](./stargz.md) enables lazy-pulling of images.\n\nTo enable Stargz snapshotter, run the following command:\n```console\n$ containerd-rootless-setuptool.sh install-stargz\n```\n\nThen, add the following config to `~/.config/containerd/config.toml` and run `systemctl --user restart containerd.service`:\n```toml\n[proxy_plugins]\n  [proxy_plugins.\"stargz\"]\n      type = \"snapshot\"\n# NOTE: replace \"1000\" with your actual UID\n      address = \"/run/user/1000/containerd-stargz-grpc/containerd-stargz-grpc.sock\"\n\n# Optional: Configure stargz for image unpacking (allows automatic snapshotter selection)\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"stargz\"\n```\n\nThe snapshotter can be specified as `$CONTAINERD_SNAPSHOTTER`.\n```console\n$ export CONTAINERD_SNAPSHOTTER=stargz\n$ nerdctl run -it --rm ghcr.io/stargz-containers/alpine:3.10.2-esgz\n```\n\nSee https://github.com/containerd/stargz-snapshotter/blob/main/docs/pre-converted-images.md for the image list.\n\n## bypass4netns\n| :zap: Requirement | nerdctl >= 0.17 |\n|-------------------|-----------------|\n\n\n[bypass4netns](https://github.com/rootless-containers/bypass4netns) is an accelerator for rootless networking.\n\nThis improves **outgoing or incoming (with --publish option) networking performance.**\n\nThe performance benchmark with iperf3 on Ubuntu 21.10 on Hyper-V VM is shown below.\n| iperf3 benchmark  | without bypass4netns | with bypass4netns |\n| ----------------- | -------------------- | ----------------- |\n| container -> host | 0.398 Gbps           | **42.2 Gbps**         |\n| host -> container | 20.6 Gbps             | **47.4 Gbps**         |\n\nThis benchmark can be reproduced with [https://github.com/rootless-containers/bypass4netns/blob/f009d96139e9e38ce69a2ea8a9a746349bad273c/Vagrantfile](https://github.com/rootless-containers/bypass4netns/blob/f009d96139e9e38ce69a2ea8a9a746349bad273c/Vagrantfile)\n\nAcceleration with bypass4netns is available with:\n- `--annotation nerdctl/bypass4netns=true` (for nerdctl v2.0 and later)\n- `--label nerdctl/bypass4netns=true` (deprecated form, used in nerdctl prior to v2.0).\n\nYou also need to have `bypass4netnsd` (bypass4netns daemon) to be running.\nExample\n```console\n$ containerd-rootless-setuptool.sh install-bypass4netnsd\n$ nerdctl run -it --rm -p 8080:80 --annotation nerdctl/bypass4netns=true alpine\n```\n\nMore detail is available at [https://github.com/rootless-containers/bypass4netns/blob/master/README.md](https://github.com/rootless-containers/bypass4netns/blob/master/README.md)\n\n## Configuring RootlessKit\n\nRootless containerd recognizes the following environment variables to configure the behavior of [RootlessKit](https://github.com/rootless-containers/rootlesskit):\n\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_STATE_DIR=DIR`: the rootlesskit state dir. Defaults to `$XDG_RUNTIME_DIR/containerd-rootless`.\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_NET=(slirp4netns|vpnkit|lxc-user-nic)`: the rootlesskit network driver. Defaults to \"slirp4netns\" if slirp4netns (>= v0.4.0) is installed. Otherwise defaults to \"vpnkit\".\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_MTU=NUM`: the MTU value for the rootlesskit network driver. Defaults to 65520 for slirp4netns, 1500 for other drivers.\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=(builtin|slirp4netns)`: the rootlesskit port driver. Defaults to \"builtin\" (this driver does not propagate the container's source IP address and always uses 127.0.0.1. Please check [Port Drivers](https://github.com/rootless-containers/rootlesskit/blob/master/docs/port.md#port-drivers) for more details).\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX=(auto|true|false)`: whether to protect slirp4netns with a dedicated mount namespace. Defaults to \"auto\".\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP=(auto|true|false)`: whether to protect slirp4netns with seccomp. Defaults to \"auto\".\n* `CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS=(auto|true|false)`: whether to launch rootlesskit with the \"detach-netns\" mode.\n  Defaults to \"auto\", which is resolved to \"true\" if RootlessKit >= 2.0 is installed.\n  The \"detached-netns\" mode accelerates `nerdctl (pull|push|build)` and enables `nerdctl run --net=host`,\n  however, there is a relatively minor drawback with BuildKit prior to v0.13:\n  the host loopback IP address (127.0.0.1) and abstract sockets are exposed to Dockerfile's \"RUN\" instructions during `nerdctl build` (not `nerdctl run`).\n  The drawback is fixed in BuildKit v0.13. Upgrading from a prior version of BuildKit needs removing the old systemd unit:\n  `containerd-rootless-setuptool.sh uninstall-buildkit && rm -f ~/.config/buildkit/buildkitd.toml`\n\nTo set these variables, create `~/.config/systemd/user/containerd.service.d/override.conf` as follows:\n```ini\n[Service]\nEnvironment=CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS=\"false\"\n```\n\nAnd then run the following commands:\n```bash\nsystemctl --user daemon-reload\nsystemctl --user restart containerd\n```\n\n## Troubleshooting\n\n### Hint to Fedora users\n- If SELinux is enabled on your host and your kernel is older than 5.13, you need to use [`fuse-overlayfs` instead of `overlayfs`](#fuse-overlayfs).\n\n## Rootlesskit Network Design\n\nIn `detach-netns` mode:\n\n- Network namespace is detached and stored in `$ROOTLESSKIT_STATE_DIR/netns`.\n- The child command executes within the host's network namespace, allowing actions like `pull` and `push` to happen in the host network namespace.\n- For creating and configuring the container's network namespace, the child command switches temporarily to the relevant namespace located in `$ROOTLESSKIT_STATE_DIR/netns`. This ensures necessary network setup while maintaining isolation in the host namespace.\n\n![rootlessKit-network-design.png](images/rootlessKit-network-design.png)\n\n- Rootlesskit Parent NetNS and Child NetNS are already configured by the startup script [containerd-rootless.sh](https://github.com/containerd/nerdctl/blob/main/extras/rootless/containerd-rootless.sh)\n- Rootlesskit Parent NetNS is the host network namespace\n- step1: `nerdctl` calls `containerd` in the host network namespace.\n- step2: `containerd` calls `runc` in the host network namespace.\n- step3: `runc` creates container with dedicated namespaces (e.g network ns) in the Parent netns.\n- step4: `runc` nsenter Rootlesskit Child NetNS before triggering nerdctl ocihook.\n- step5: `nerdctl` ocihook module leverages CNI.\n- step6: CNI configures container network namespace: create network interfaces `eth0` -> `veth0` -> `nerdctl0`.\n"
  },
  {
    "path": "docs/soci.md",
    "content": "# Lazy-pulling using SOCI Snapshotter\n\nSOCI Snapshotter is a containerd snapshotter plugin. It enables standard OCI images to be lazily loaded without requiring a build-time conversion step. \"SOCI\" is short for \"Seekable OCI\", and is pronounced \"so-CHEE\".\n\nSee https://github.com/awslabs/soci-snapshotter to learn further information.\n\n## SOCI Index Manifest Versions\n\nSOCI supports two index manifest versions:\n\n- **v1**: Original format using OCI Referrers API (disabled by default in SOCI v0.10.0+)\n- **v2**: New format that packages SOCI index with the image (default in SOCI v0.10.0+)\n\nTo enable v1 indices in SOCI v0.10.0+, add to `/etc/soci-snapshotter-grpc/config.toml`:\n```toml\n[pull_modes]\n  [pull_modes.soci_v1]\n    enable = true\n```\n\nFor detailed information about the differences between v1 and v2, see the [SOCI Index Manifest v2 documentation](https://github.com/awslabs/soci-snapshotter/blob/main/docs/soci-index-manifest-v2.md).\n\n## Prerequisites\n\n- Install containerd remote snapshotter plugin (`soci-snapshotter-grpc`) from https://github.com/awslabs/soci-snapshotter/blob/main/docs/getting-started.md\n\n- Add the following to `/etc/containerd/config.toml`:\n```toml\n[proxy_plugins]\n  [proxy_plugins.soci]\n    type = \"snapshot\"\n    address = \"/run/soci-snapshotter-grpc/soci-snapshotter-grpc.sock\"\n\n# Optional: Configure soci for image unpacking (allows automatic snapshotter selection)\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"soci\"\n```\n\n- Launch `containerd` and `soci-snapshotter-grpc`\n\n## Enable SOCI for `nerdctl run` and `nerdctl pull`\n\n| :zap: Requirement | nerdctl >= 1.5.0 |\n| ----------------- | ---------------- |\n\n- Run `nerdctl` with `--snapshotter=soci`\n```console\nnerdctl run -it --rm --snapshotter=soci public.ecr.aws/soci-workshop-examples/ffmpeg:latest\n```\n\n- You can also only pull the image with SOCI without running the container.\n```console\nnerdctl pull --snapshotter=soci public.ecr.aws/soci-workshop-examples/ffmpeg:latest\n```\n\nFor images that already have SOCI indices, see https://gallery.ecr.aws/soci-workshop-examples\n\n## Enable SOCI for `nerdctl push`\n\n| :zap: Requirement | nerdctl >= 1.6.0 |\n| ----------------- | ---------------- |\n\n- Push the image with SOCI index. Adding `--snapshotter=soci` arg to `nerdctl pull`, `nerdctl` will create the SOCI index and push the index to same destination as the image.\n```console\nnerdctl push --snapshotter=soci --soci-span-size=2097152 --soci-min-layer-size=20971520 public.ecr.aws/my-registry/my-repo:latest\n```\n--soci-span-size and --soci-min-layer-size are two properties to customize the SOCI index. See [Command Reference](https://github.com/containerd/nerdctl/blob/377b2077bb616194a8ef1e19ccde32aa1ffd6c84/docs/command-reference.md?plain=1#L773) for further details.\n\n> **Note**: With SOCI v0.10.0+, When using `nerdctl push --snapshotter=soci`, it creates and pushes v1 indices. When pushing a converted image (created with `nerdctl image convert --soci`), it will push v2 indices.\n\n## Enable SOCI for `nerdctl image convert`\n\n| :zap: Requirement | nerdctl >= 2.1.3 |\n| ----------------- | ---------------- |\n\n| :zap: Requirement | soci-snapshotter >= 0.10.0 |\n| ----------------- | ---------------- |\n\n- Convert an image to generate SOCI Index artifacts v2. Running the `nerdctl image convert` with the `--soci` flag and a `srcImg` and `dstImg`, `nerdctl` will create the SOCI v2 indices and the new image will be present in the `dstImg` address.\n```console\nnerdctl image convert --soci --soci-span-size=2097152 --soci-min-layer-size=20971520 public.ecr.aws/my-registry/my-repo:latest public.ecr.aws/my-registry/my-repo:soci\n```\n--soci-span-size and --soci-min-layer-size are two properties to customize the SOCI index. See [Command Reference](https://github.com/containerd/nerdctl/blob/377b2077bb616194a8ef1e19ccde32aa1ffd6c84/docs/command-reference.md?plain=1#L773) for further details.\n\nThe `image convert` command with `--soci` flag creates SOCI-enabled images using SOCI Index Manifest v2, which combines the SOCI index and the original image into a single artifact.\n"
  },
  {
    "path": "docs/stargz.md",
    "content": "# Lazy-pulling using Stargz Snapshotter\n\n| :zap: Requirement | nerdctl >= 0.0.1 |\n|-------------------|------------------|\n\nLazy-pulling is a technique to running containers before completion of pulling the images.\n\nSee https://github.com/containerd/stargz-snapshotter to learn further information.\n\n[![asciicast](https://asciinema.org/a/378377.svg)](https://asciinema.org/a/378377)\n\n## Enable lazy-pulling for `nerdctl run`\n\n> **NOTE**\n> For rootless installation, see [`rootless.md`](./rootless.md#stargz-snapshotter)\n\n- Install Stargz plugin (`containerd-stargz-grpc`) from https://github.com/containerd/stargz-snapshotter \n\n- Add the following to `/etc/containerd/config.toml`:\n```toml\n[proxy_plugins]\n  [proxy_plugins.stargz]                                                                                  \n    type = \"snapshot\"\n    address = \"/run/containerd-stargz-grpc/containerd-stargz-grpc.sock\"\n\n# Optional: Configure stargz for image unpacking (allows automatic snapshotter selection)\n[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n  platform = \"linux\"\n  snapshotter = \"stargz\"\n```\n\n- Launch `containerd` and `containerd-stargz-grpc`\n\n- Run `nerdctl` with `--snapshotter=stargz`\n```console\n# nerdctl --snapshotter=stargz run -it --rm ghcr.io/stargz-containers/fedora:30-esgz\n```\n\nFor the list of pre-converted Stargz images, see https://github.com/containerd/stargz-snapshotter/blob/main/docs/pre-converted-images.md\n\n### Benchmark result (Dec 9, 2020)\nFor running `python3 -c print(\"hi\")`, eStargz with Stargz Snapshotter is 3-4 times faster than the legacy OCI with overlayfs snapshotter.\n\nLegacy OCI with overlayfs snapshotter:\n```console\n# time nerdctl --snapshotter=overlayfs run -it --rm ghcr.io/stargz-containers/python:3.7-org python3 -c 'print(\"hi\")'\nghcr.io/stargz-containers/python:3.7-org:                                         resolved       |++++++++++++++++++++++++++++++++++++++|\nindex-sha256:6008006c63b0a6043a11ac151cee572e0c8676b4ba3130ff23deff5f5d711237:    done           |++++++++++++++++++++++++++++++++++++++|\nmanifest-sha256:48eafda05f80010a6677294473d51a530e8f15375b6447195b6fb04dc2a30ce7: done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:f860607a6cd9751ac8db2f33cbc3ce1777a44eb3c04853e116763441a304fbf6:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:96b2c1e36db5f5910f58da2ca4f9311b0690810c7107fb055ee1541498b5061f:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:c495e8de12d26c9843a7a2bf8c68de1e5652e66d80d9bc869279f9af6f86736a:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:33382189822a108b249cf3ccd234d04c3a8dfe7d593df19c751dcfab3675d5f2:    done           |++++++++++++++++++++++++++++++++++++++|\nconfig-sha256:94c9a318e47ab8a318582e2712bb495f92f17a7c1e50f13cc8a3e362c1b09290:   done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:6eaa0b6b8562fb4a02e140ae53b3910fc4d0db6e68660390eaef993f42e21102:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:adbdcbacafe93bf0791e49c8d3689bb78d9e60d02d384d4e14433aedae39f52c:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:756975cb9c7e7933d824af9319b512dd72a50894232761d06ef3be59981df838:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:d77915b4e630d47296770ce4cf481894885978072432456615172af463433cc5:    done           |++++++++++++++++++++++++++++++++++++++|\nlayer-sha256:5f37a0a41b6b03489dd7de0aa2a79e369fd8b219bbc36b52f3f9790dc128e74b:    done           |++++++++++++++++++++++++++++++++++++++|\nelapsed: 41.9s                                                                    total:  321.3  (7.7 MiB/s)                                       \nhi                                                                                                        \n                                                     \nreal    0m51.754s                                                                                         \nuser    0m2.687s\nsys     0m5.533s \n```\n\neStargz with Stargz Snapshotter:\n```console\n# time nerdctl --snapshotter=stargz run -it --rm ghcr.io/stargz-containers/python:3.7-esgz python3 -c 'print(\"hi\")'\nfetching sha256:2ea0dd96... application/vnd.oci.image.index.v1+json\nfetching sha256:9612ff73... application/vnd.docker.distribution.manifest.v2+json\nfetching sha256:34e5920e... application/vnd.docker.container.image.v1+json\nhi\n\nreal    0m13.589s\nuser    0m0.132s\nsys     0m0.158s\n```\n\n## Enable lazy-pulling for pulling base images during `nerdctl build`\n\n- Launch `buildkitd` with `--oci-worker-snapshotter=stargz` (or `--containerd-worker-snapshotter=stargz` if you use containerd worker)\n- Launch `nerdctl build`. No need to specify `--snapshotter` for `nerdctl`.\n\n## Building stargz images using `nerdctl build`\n\n```console\n$ nerdctl build -t example.com/foo .\n$ nerdctl image convert --estargz --oci example.com/foo example.com/foo:estargz\n$ nerdctl push example.com/foo:estargz\n```\n\nNOTE: `--estargz` should be specified in conjunction with `--oci`\n\nStargz Snapshotter is not needed for building stargz images.\n\n## Tips for image conversion\n\n### Tips 1: Using gzip helper to speed up image conversion\n\nWhen converting a traditional overlayfs image encoded as tar.gz to an estargz format image, nerdctl supports specifying an additional command‑line decompression tool to speed up the conversion process. You can set `--estargz-gzip-helper` to choose different CLI gzip tools. Even using the gzip command corresponding to the Go gzip library can achieve approximately 32% speed improvement. For more details, see: [Using decompression commands to improve the layer decompression speed of gzip-formatted images](https://github.com/containerd/stargz-snapshotter/pull/2117). Currently, `--estargz-gzip-helper` supports `pigz`, `igzip`, and `gzip`. The recommended order is `pigz` > `igzip` > `gzip`.\n\n```console\n# nerdctl image convert --oci --estargz --estargz-gzip-helper pigz ghcr.io/stargz-containers/ubuntu:22.04 ghcr.io/stargz-containers/ubuntu:22.04-esgz\nsha256:aa6543b9885867b8b485925b6ec69d8e018e8fce40835ea6359cbb573683a014\n# nerdctl image ls\nREPOSITORY                             TAG              IMAGE ID        CREATED               PLATFORM        SIZE       BLOB SIZE\nghcr.io/stargz-containers/ubuntu       22.04-esgz       aa6543b98858    About a minute ago    linux/amd64     0B         32.43MB\nghcr.io/stargz-containers/ubuntu       22.04            20fa2d7bb4de    2 minutes ago         linux/amd64     87.47MB    30.43MB\n```\n\n### Tips 2: Creating smaller eStargz images\n\n`nerdctl image convert` allows the following flags for optionally creating a smaller eStargz image.\nThe result image requires stargz-snapshotter >= v0.13.0 for lazy pulling.\n\n- `--estargz-min-chunk-size`: The minimal number of bytes of data must be written in one gzip stream. If it's > 0, multiple files and chunks can be written into one gzip stream. Smaller number of gzip header and smaller size of the result blob can be expected. `--estargz-min-chunk-size=0` produces normal eStargz.\n\n- `--estargz-external-toc`: Separate TOC JSON metadata into another image (called \"TOC image\"). The result eStargz doesn't contain TOC so we can expect a smaller size than normal eStargz. This is an [experimental](./experimental.md) feature.\n\n#### `--estargz-min-chunk-size` usage\n\nconversion:\n\n```console\n# nerdctl image convert --oci --estargz --estargz-min-chunk-size=50000 ghcr.io/stargz-containers/ubuntu:22.04 registry2:5000/ubuntu:22.04-chunk50000\n# nerdctl image ls\nREPOSITORY                          TAG                 IMAGE ID        CREATED           PLATFORM       SIZE        BLOB SIZE\nghcr.io/stargz-containers/ubuntu    22.04               20fa2d7bb4de    14 seconds ago    linux/amd64    83.4 MiB    29.0 MiB\nregistry2:5000/ubuntu               22.04-chunk50000    562e09e1b3c1    2 seconds ago     linux/amd64    0.0 B       29.2 MiB\n# nerdctl push --insecure-registry registry2:5000/ubuntu:22.04-chunk50000\n```\n\nPull it lazily:\n\n```console\n# nerdctl pull --snapshotter=stargz --insecure-registry registry2:5000/ubuntu:22.04-chunk50000\n# mount | grep \"stargz on\"\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/1/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\n```\n\n#### `--estargz-external-toc` usage\n\nconvert:\n\n```console\n# nerdctl image convert --oci --estargz --estargz-external-toc ghcr.io/stargz-containers/ubuntu:22.04 registry2:5000/ubuntu:22.04-ex\nINFO[0005] Extra image(0) registry2:5000/ubuntu:22.04-ex-esgztoc\nsha256:3059dd5d9c404344e0b7c43d9782de8cae908531897262b7772103a0b585bbee\n# nerdctl images\nREPOSITORY                          TAG                 IMAGE ID        CREATED           PLATFORM       SIZE        BLOB SIZE\nghcr.io/stargz-containers/ubuntu    22.04                20fa2d7bb4de    9 seconds ago    linux/amd64    83.4 MiB    29.0 MiB\nregistry2:5000/ubuntu               22.04-ex             3059dd5d9c40    1 second ago     linux/amd64    0.0 B       30.8 MiB\nregistry2:5000/ubuntu               22.04-ex-esgztoc     18c042b6eb8b    1 second ago     linux          0.0 B       151.3 KiB\n```\n\nThen push eStargz(`registry2:5000/ubuntu:22.04-ex`) and TOC image(`registry2:5000/ubuntu:22.04-ex-esgztoc`) to the same registry (`registry2` is used in this example but you can use arbitrary registries):\n\n```console\n# nerdctl push --insecure-registry registry2:5000/ubuntu:22.04-ex\n# nerdctl push --insecure-registry registry2:5000/ubuntu:22.04-ex-esgztoc\n```\n\nPull it lazily:\n\n```console\n# nerdctl pull --insecure-registry --snapshotter=stargz registry2:5000/ubuntu:22.04-ex\n```\n\nStargz Snapshotter automatically refers to the TOC image on the same registry.\n\n##### optional `--estargz-keep-diff-id` flag for conversion without changing layer diffID\n\n`nerdctl image convert` supports optional flag `--estargz-keep-diff-id` specified with `--estargz-external-toc`.\nThis converts an image to eStargz without changing the diffID (uncompressed digest) so even eStargz-agnostic gzip decompressor (e.g. gunzip) can restore the original tar blob.\n\n```console\n# nerdctl image convert --oci --estargz --estargz-external-toc --estargz-keep-diff-id ghcr.io/stargz-containers/ubuntu:22.04 registry2:5000/ubuntu:22.04-ex-keepdiff\n# nerdctl push --insecure-registry registry2:5000/ubuntu:22.04-ex-keepdiff\n# nerdctl push --insecure-registry registry2:5000/ubuntu:22.04-ex-keepdiff-esgztoc\n# crane --insecure blob registry2:5000/ubuntu:22.04-ex-keepdiff@sha256:2dc39ba059dcd42ade30aae30147b5692777ba9ff0779a62ad93a74de02e3e1f | jq -r '.rootfs.diff_ids[]'\nsha256:7f5cbd8cc787c8d628630756bcc7240e6c96b876c2882e6fc980a8b60cdfa274\n# crane blob ghcr.io/stargz-containers/ubuntu:22.04@sha256:2dc39ba059dcd42ade30aae30147b5692777ba9ff0779a62ad93a74de02e3e1f | jq -r '.rootfs.diff_ids[]'\nsha256:7f5cbd8cc787c8d628630756bcc7240e6c96b876c2882e6fc980a8b60cdfa274\n```\n\n### Tips 3: Using zstd instead of gzip (a.k.a. zstd:chunked)\n\nYou can use zstd compression with lazy pulling support (a.k.a zstd:chunked) instead of gzip.\n\n- Pros\n  - [Faster](https://github.com/facebook/zstd/tree/v1.5.2#benchmarks) compression/decompression.\n- Cons\n  - Old tools might not support. And unsupported by some tools yet.\n    - zstd supported by OCI Image Specification is still under rc (2022/11). will be added to [v1.1.0](https://github.com/opencontainers/image-spec/commit/1a29e8675a64a5cdd2d93b6fa879a82d9a4d926a).\n    - zstd supported by [docker >=v23.0.0](https://github.com/moby/moby/releases/tag/v23.0.0).\n    - zstd supported by [containerd >= v1.5](https://github.com/containerd/containerd/releases/tag/v1.5.0).\n  - `min-chunk-size`, `external-toc` (described in Tips 1) are unsupported yet.\n\n```console\n$ nerdctl build -t example.com/foo .\n$ nerdctl image convert --zstdchunked --oci example.com/foo example.com/foo:zstdchunked\n$ nerdctl push example.com/foo:zstdchunked\n```\n"
  },
  {
    "path": "docs/testing/README.md",
    "content": "# Testing nerdctl\n\nThis document covers basic usage of nerdctl testing tasks, and generic recommendations\nand principles about writing tests.\n\nFor more comprehensive information about nerdctl test tools, see [tools.md](tools.md).\n\n## Code, fix, lint, rinse, repeat\n\n```\n# Do hack\n\n# When done:\n# This will ensure proper import formatting, modules, and basic formatting of your code\nmake fix\n\n# And this will run all linters and report if any modification is required\nmake lint\n```\n\nNote that both these tasks will work on any OS, including macOS.\n\nAlso note that `make lint` does call on subtask `make lint-commits` which is going to lint commits for the range\n`main..HEAD` by default (this is fine for most development flows that expect to be merged in main).\n\nIf you are targeting a specific branch that is not `main` (for example working against `release/1.7`),\nyou likely want to explicitly set the commit range to that instead.\n\neg:\n`LINT_COMMIT_RANGE=target_branch..HEAD make lint-commits`\nor\n`LINT_COMMIT_RANGE=target_branch..HEAD make lint`\n\n## Unit testing\n\n```\n# This will run basic unit testing\nmake test\n```\n\nThis task must be run on a supported OS (linux, windows, or freebsd).\n\n## Integration testing\n\n### TL;DR\n\nBe sure to first `make && sudo make install`\n\n```bash\n# Test all with nerdctl (rootless mode, if running go as a non-root user)\ngo test -p 1 ./cmd/nerdctl/...\n\n# Test all with nerdctl rootful\ngo test -p 1 -exec sudo ./cmd/nerdctl/...\n\n# Test all with docker\ngo test -p 1 ./cmd/nerdctl/... -args -test.target=docker\n\n# Test just the tests(s) which names match TestVolume.*\ngo test -p 1 ./cmd/nerdctl/... -run \"TestVolume.*\"\n# Or alternatively, just test the subpackage\ngo test ./cmd/nerdctl/volume\n```\n\n### About parallelization\n\nBy default, when `go test ./foo/...` finds subpackages, it does create _a separate test binary\nper sub-package_, and execute them _in parallel_.\nThis effectively will make distinct tests in different subpackages to be executed in\nparallel, regardless of whether they called `t.Parallel` or not.\n\nThe `-p 1` flag does inhibit this behavior, and forces go to run each sub-package\nsequentially.\n\nNote that this is different from the `--parallel` flag, which controls the amount of\nparallelization that a single go test binary will use when faced with tests that do\nexplicitly allow it (with a call to `t.Parallel()`).\n\n### Or test in a container\n\n```bash\ndocker build -t test-integration --target test-integration .\ndocker run -t --rm --privileged test-integration\n```\n\n### Principles\n\n#### Tests should be parallelized (with best effort)\n\n##### General case\n\nIt should be possible to parallelize all tests - as such, please make sure you:\n- name all resources your test is manipulating after the test identifier (`testutil.Identifier(t)`)\nto guarantee your test will not interact with other tests\n- do NOT use `os.Setenv` - instead, add into `base.Env`\n- use `t.Parallel()` at the beginning of your test (and subtests as well of course)\n- in the very exceptional case where your test for some reason can NOT be parallelized, be sure to mark it explicitly as such\nwith a comment explaining why\n\n##### For \"blanket\" destructive operations\n\nIf you are going to use blanket destructive operations (like `prune`), please:\n- use a dedicated namespace: instead of calling `testutil.Base`, call `testutil.BaseWithNamespace` \nand be sure that your namespace is named after the test id\n- remove the namespace in your test `Cleanup`\n- since docker does not support namespaces, be sure to:\n  - only enable `Parallel` if the target is NOT docker: `\tif !nerdtest.IsDocker() { t.Parallel() }`\n  - double check that what you do in the default namespace is safe\n\n#### Clean-up after (and before) yourself\n\n- do NOT use `defer`, use `t.Cleanup`\n- do NOT test the result of commands doing the cleanup - it is fine if they fail, \nand they are not test failure per-se - they are here to garbage collect\n- you should call your cleanup routine BEFORE doing anything, in case there is any \nleftovers from previous runs, typically:\n```\ntearDown := func(){\n    // Do some cleanup\n}\n\ntearDown()\nt.Cleanup(tearDown)\n```\n\n#### Test what you are testing, and not something else\n\nYou should only test atomically.\n\nIf you are testing `nerdctl volume create`, make sure that your test will not fail\nbecause of changes in `nerdctl volume inspect`.\n\nThat obviously means there are certain things you cannot test \"yet\".\nJust put the right test in the right place with this simple rule of thumb:\nif your test requires another nerdctl command to validate the result, then it does\nnot belong here. Instead, it should be a test for that other command.\n\nOf course, this is not perfect, and changes in `create` may now fail in `inspect` tests \nwhile `create` could be faulty, but it does beat the alternative, because of this principle:\nit is easier to walk *backwards* from a failure."
  },
  {
    "path": "docs/testing/tools.md",
    "content": "# Nerdctl testing tools\n\n## Preamble\n\nThe integration test suite in nerdctl is meant to apply to both nerdctl and docker,\nand further support additional test properties to target specific contexts (ipv6, kube).\n\nBasic _usage_ is covered in the [testing docs](README.md).\n\nThis here covers how to write tests, leveraging nerdctl `pkg/testutil/test`\nwhich has been specifically developed to take care of repetitive tasks,\nprotect the developer against unintended side effects across tests, and generally\nencourage clear testing structure with good debug-ability and a relatively simple API for\nmost cases.\n\n## Using `test.Case`\n\nStarting from scratch, the simplest, basic structure of a new test is:\n\n```go\npackage main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n)\n\nfunc TestMyThing(t *testing.T) {\n\t// Declare your test\n\tmyTest := nerdtest.Setup()\n\t// This is going to run `nerdctl info` (or `docker info`)\n\tmytest.Command = test.Command(\"info\")\n    // Verify the command exits with 0, and stdout contains the word `Kernel`\n    myTest.Expected = test.Expects(0, nil, expect.Contains(\"Kernel\"))\n\t// Run it\n\tmyTest.Run(t)\n}\n```\n\n## Expectations\n\nThere are a handful of helpers for \"expectations\".\n\nYou already saw two (`test.Expects` and `test.Contains`):\n\nFirst, `test.Expects(exitCode int, errors []error, outputCompare Comparator)`, which is\nconvenient to quickly describe what you expect overall.\n\n`exitCode` is obvious (note that passing -1 as an exit code will just\nverify the commands does fail without comparing the code, and -2 will not verify the exit\ncode at all).\n\n`errors` is a slice of go `error`, that allows you to compare what is seen on stderr\nwith existing errors (for example: `errdefs.ErrNotFound`), or more generally\nany string you want to match.\n\n`outputCompare` can be either your own comparison function, or\none of the comparison helper.\n\nSecondly, `test.Contains` - which is a `Comparator`.\n\n### Comparators\n\nBesides `expect.Contains(string)`, there are a few more:\n- `expect.DoesNotContain(string)`\n- `expect.Equals(string)`\n- `expect.Match(*regexp.Regexp)`\n- `expect.All(comparators ...Comparator)`, which allows you to bundle together a bunch of other comparators\n\nThe following example shows how to implement your own custom `Comparator`\n(this is actually the `Equals` comparator).\n\n```go\npackage whatever\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n)\n\nfunc MyComparator(compare string) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\tt.Helper()\n\t\tassert.Assert(t, stdout == compare)\n\t}\n}\n```\n\n### Advanced expectations\n\nYou may want to have expectations that contain a certain piece of data\nthat is being used in the command or at other stages of your test (Setup).\n\nFor example, creating a container with a certain name, you might want to verify\nthat this name is then visible in the list of containers.\n\nTo achieve that, you should write your own `Manager`, leveraging test `Data`.\n\nHere is an example, where we are using `data.Labels().Get(\"sometestdata\")`.\n\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n)\n\nfunc TestMyThing(t *testing.T) {\n\tnerdtest.Setup()\n\n\t// Declare your test\n\tmyTest := &test.Case{\n\t\tData:        test.WithLabels(map[string]string{\"sometestdata\": \"blah\"}),\n\t\tCommand:     test.Command(\"info\"),\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tExitCode: 1,\n\t\t\t\tErrors: []error{\n\t\t\t\t\terrors.New(\"foobla\"),\n\t\t\t\t\terrdefs.ErrNotFound,\n\t\t\t\t},\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, stdout == data.Labels().Get(\"sometestdata\"))\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\tmyTest.Run(t)\n}\n```\n\n## On `Data`\n\n`Data` is provided to allow storing mutable key-value information that pertain to the test.\n\nWhile it can be provided through `test.WithLabels(map[string]string{key: value})`,\ninside the testcase definition, it can also be dynamically manipulated inside `Setup`, or `Command`.\n\nNote that `Data` additionally exposes the following functions:\n- `Identifier(words ...string)` which returns a unique identifier associated with the _current_ test (or subtest)\n- `TempDir()` which returns the private, temporary directory associated with the test\n\n... along with the `Get(key)` and `Set(key, value)` methods.\n\nNote that Data is copied down to subtests, which is convenient to pass \"down\"\ninformation relevant to a bunch of subtests (eg: like a registry IP).\n\n## On Config\n\n`Config` is similar to `Data`, although it is meant specifically for predefined\nkeys that impact the base behavior of the binary you are testing.\n\nYou can initiate your config using `test.WithConfig(key, value)`, and you can\nmanipulate it further using `helpers.Read` and`helpers.Write`.\n\nCurrently, the following keys are defined:\n- `DockerConfig` allowing to set custom content for the `$DOCKER_CONFIG/config.json` file\n- `Namespace` (default to `nerdctl-test` if unspecified, but see \"mode private\")\n- `NerdctlToml` to set custom content for the `$NERDCTL_TOML` file\n- `HostsDir` to specify the value of the arg `--hosts-dir`\n- `DataRoot` to specify the value of the arg `--data-root`\n- `Debug` to enable debug (works for both nerdctl and docker)\n\nNote that config defined on the test case is copied over for subtests.\n\n## Commands\n\nFor simple cases, `test.Command(args ...string)` is the way to go.\n\nIt will execute the binary to test (nerdctl or docker), with the provided arguments,\nand will by default get cwd inside the temporary directory associated with the test.\n\n### Environment\n\nYou can attach custom environment variables for your test in the `Env` property of your\ntest.\n\nThese will be automatically added to the environment for your command, and also\nyour setup and cleanup routines (see below).\n\nIf you would like to override the environment specifically for a command, but not for\nothers (eg: in `Setup` or `Cleanup`), you can do so with custom commands (see below).\n\nNote that environment as defined statically in the test will be copied over for subtests.\n\n### Working directory\n\nBy default, the working directory of the command will be set to the temporary directory\nof the test.\n\nThis behavior can be overridden using custom commands.\n\n### Custom Executor\n\nCustom `Executor`s allow you to manipulate test `Data`, override important aspects\nof the command to execute (`Env`, `WorkingDir`), or otherwise give you full control\non what the command does.\n\nYou just need to implement an `Executor`:\n\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n)\n\nfunc TestMyThing(t *testing.T) {\n\tnerdtest.Setup()\n\n\t// Declare your test\n\tmyTest := &test.Case{\n\t\tData:        test.WithLabels(map[string]string{\"sometestdata\": \"blah\"}),\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--name\", data.Labels().Get(\"sometestdata\"))\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tExitCode: 1,\n\t\t\t\tErrors: []error{\n\t\t\t\t\terrors.New(\"foobla\"),\n\t\t\t\t\terrdefs.ErrNotFound,\n\t\t\t\t},\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, stdout == data.Labels().Get(\"sometestdata\"))\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\tmyTest.Run(t)\n}\n```\n\nNote that inside your `Executor` you do have access to the full palette of command options,\nincluding:\n- `Background(timeout time.Duration)` which allows you to background a command execution\n- `WithWrapper(binary string, args ...string)` which allows you to \"wrap\" your command with another binary\n- `WithStdin(io.Reader)` which allows you to pass a reader to the command stdin\n- `WithCwd(string)` which allows you to specify the working directory (default to the test temp directory)\n- `Clone()` which returns a copy of the command, with env, cwd, etc\n\nand also `WithBinary` and `WithArgs`.\n\n### On `helpers`\n\nInside a custom `Executor`, `Manager`, or `Butler`, you have access to a collection of\n`helpers` to simplify command execution:\n\n- `helpers.Ensure(args ...string)` will run a command and ensure it exits successfully\n- `helpers.Fail(args ...string)` will run a command and ensure it fails\n- `helpers.Anyhow(args ...string)` will run a command but does not care if it succeeds or fails\n- `helpers.Capture(args ...string)` will run a command, ensure it is successful, and return the output\n- `helpers.Command(args ...string)` will return a command that can then be tested against expectations\n- `helpers.Custom(binary string, args ...string)` will do the same for any arbitrary command (not limited to nerdctl)\n- `helpers.T()` which returns the appropriate `*testing.T` for your context\n\n## Setup and Cleanup\n\nTests routinely require a set of actions to be performed _before_ you can run the\ncommand you want to test.\nA setup routine will get executed before your `Command`, and have access to and can\nmanipulate your test `Data` and `Config`.\n\nConversely, you very likely want to clean things up once your test is done.\nWhile temporary directories are cleaned for you with no action needed on your part,\nthe app you are testing might have stateful data you may want to remove.\nNote that a `Cleanup` routine will get executed twice - after your `Command` has run\nits course evidently - but also, pre-emptively, before your `Setup`, so that possible leftovers from\nprevious runs are taken care of.\n\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n)\n\nfunc TestMyThing(t *testing.T) {\n\tnerdtest.Setup()\n\n\t// Declare your test\n\tmyTest := &test.Case{\n\t\tData:        test.WithLabels(map[string]string{\"sometestdata\": \"blah\"}),\n\t\tSetup: func(data *test.Data, helpers test.Helpers){\n\t\t\thelpers.Ensure(\"volume\", \"create\", \"foo\")\n\t\t\thelpers.Ensure(\"volume\", \"create\", \"bar\")\n\t\t},\n\t\tCleanup: func(data *test.Data, helpers test.Helpers){\n\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"foo\")\n\t\t\thelpers.Anyhow(\"volume\", \"rm\", \"bar\")\n\t\t},\n\t\tCommand: func(data test.Data, helpers test.Helpers) test.TestableCommand {\n\t\t\treturn helpers.Command(\"run\", \"--name\", data.Identifier()+data.Labels().Get(\"sometestdata\"))\n\t\t},\n\t\tExpected: func(data test.Data, helpers test.Helpers) *test.Expected {\n\t\t\treturn &test.Expected{\n\t\t\t\tExitCode: 1,\n\t\t\t\tErrors: []error{\n\t\t\t\t\terrors.New(\"foobla\"),\n\t\t\t\t\terrdefs.ErrNotFound,\n\t\t\t\t},\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tassert.Assert(t, stdout == data.Labels().Get(\"sometestdata\"))\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t}\n\n\tmyTest.Run(t)\n}\n```\n\n## Subtests\n\nSubtests are just regular tests, attached to the `SubTests` slice of a test.\n\nNote that a subtest will inherit its parent `Data`, `Config` and `Env`, in the state they are at\nafter the parent test has run its `Setup` and `Command` routines (but before `Cleanup`).\nThis does _not_ apply to `Identifier()` and `TempDir()`, which are unique to the subtest.\n\nAlso note that a test does not have to have a `Command`.\nThis is a convenient pattern if you just need a common `Setup` for a bunch of subtests.\n\n## Parallelism\n\nAll tests (and subtests) are assumed to be parallelizable.\n\nYou can force a specific `test.Case` to not be run in parallel though,\nby setting its `NoParallel` property to `true`.\n\nNote that if you want better isolation, it is usually better to use the requirement\n`nerdtest.Private` instead of `NoParallel` (see below).\n\n## Requirements\n\n`test.Case` has a `Require` property that allow enforcing specific, per-test requirements.\n\nA `Requirement` is expected to make you `Skip` tests when the environment does not match\nexpectations.\n\nHere are a few:\n```go\nrequire.Windows // a test runs only on Windows (or Not(Windows))\nrequire.Linux // a test runs only on Linux\ntest.Darwin // a test runs only on Darwin\ntest.OS(name string) // a test runs only on the OS `name`\nrequire.Binary(name string) // a test requires the bin `name` to be in the PATH\nrequire.Not(req Requirement) // a test runs only if the opposite of the requirement `req` is fulfilled\nrequire.All(req ...Requirement) // a test runs only if all requirements are fulfilled\n\nnerdtest.Docker // a test only run on Docker - normally used with require.Not(nerdtest.Docker)\nnerdtest.Soci // a test requires the soci snapshotter\nnerdtest.Stargz // a test requires the stargz snapshotter\nnerdtest.Rootless // a test requires Rootless\nnerdtest.Rootful // a test requires Rootful\nnerdtest.RootlessWithDetachNetNS // a test requires rootless with detached netns (RootlessKit v2)\nnerdtest.RootlessWithoutDetachNetNS // a test requires rootless without detached netns (RootlessKit v1)\nnerdtest.Build // a test requires buildkit\nnerdtest.CGroup // a test requires cgroup\nnerdtest.CgroupsAccessible // a test requires cgroup; passes if rootful, or rootless with cgroup v2\nnerdtest.CGroupV2 // a test requires cgroup v2\nnerdtest.NerdctlNeedsFixing // indicates that a test cannot be run on nerdctl yet as a fix is required\nnerdtest.BrokenTest // indicates that a test needs to be fixed and has been restricted to run only in certain cases\nnerdtest.OnlyIPv6 // a test is meant to run solely in the ipv6 environment\nnerdtest.OnlyKubernetes // a test is meant to run solely in the Kubernetes environment\nnerdtest.IsFlaky // indicates that a test will fail in a flaky way - this may be the test fault, or more likely something racy in nerdctl\nnerdtest.Private // see below\nnerdtest.Registry // a test requires a registry to be deployed\nnerdtest.IPFS // a test requires ipfs (binary present)\nnerdtest.Gomodjail // a test requires the target binary to be packed with gomodjail\nnerdtest.AllowModifyUserns // a test requires allow-modify-userns to be enabled\nnerdtest.RemapIDs // a test requires snapshotter to support ID remapping\nnerdtest.HyperV // a test requires Hyper-V (Windows)\n\nnerdtest.Info(func(info dockercompat.Info) error { ... }) // `nerdctl info` should satisfy custom conditions\nnerdtest.SociVersion(\"0.10.0\") // SOCI snapshotter version check\nnerdtest.ContainerdVersion(\"2.0.0\") // containerd version check\nnerdtest.CNIFirewallVersion(\"1.7.1\") // CNI firewall plugin version check\n```\n\n### About `nerdtest.Private`\n\nWhile all requirements above are self-descriptive or obvious, `nerdtest.Private` is  a\nspecial case.\n\nIf set, it will run tests inside a dedicated namespace that is private to the test.\nNote that subtests by default are going to be set in that same namespace, unless they\nask for private as well, or they reset the `Namespace` config key.\n\nIf the target is Docker - which does not support namespaces - asking for `Private`\nwill disable parallelization.\n\nThe purpose of private is to provide a truly clean-room environment for tests\nthat are going to have side effects on others, or that do require an exclusive, pristine\nenvironment.\n\nUsing private is generally preferable to disabling parallelization, as doing the latter\nwould slow down the run and won't have the same isolation guarantees about the environment.\n\n## Advanced command customization\n\nTesting any non-trivial binary likely assume a good amount of custom code\nto set up the right default behavior wrt environment, flags, etc.\n\nTo do that, you can pass a `test.Testable` implementation to the `test.Customize` method.\n\nIt basically lets you define your own `CustomizableCommand`, along with a hook to deal with\nambient requirements that is run after `test.Require` and before `test.Setup`.\n\n`CustomizableCommand` are typically embedding a `test.GenericCommand` and overriding both the\n`Run` and `Clone` methods.\n\nCheck the `nerdtest` implementation for details.\n\n## Utilities\n\nTBD"
  },
  {
    "path": "examples/compose-multi-platform/Dockerfile",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nFROM php:apache\nCOPY index.php /var/www/html/\n"
  },
  {
    "path": "examples/compose-multi-platform/README.md",
    "content": "# Multi-platform compose demo\n\n- Make sure QEMU is configured, see [`../../docs/multi-platform.md`](../../docs/multi-platform.md)\n- Run `nerdctl compose up -d`\n- Open http://localhost:8080 , and confirm that \"System\" is ppc64le\n- Open http://localhost:8081 , and confirm that \"System\" is s390x\n"
  },
  {
    "path": "examples/compose-multi-platform/docker-compose.yaml",
    "content": "services:\n  svc0:\n    build: .\n    platform: s390x\n    ports:\n      - 8080:80\n  svc1:\n    build: .\n    platform: ppc64le\n    ports:\n      - 8081:80\n"
  },
  {
    "path": "examples/compose-multi-platform/index.php",
    "content": "<?php phpinfo(); ?>\n"
  },
  {
    "path": "examples/compose-wordpress/README.md",
    "content": "# Demo: wordpress + mariadb\n\nUsage:\n- Substitute \"examplepass\" in [`docker-compose.yaml`](./docker-compose.yaml) to your own password.\n- Run `nerdctl compose up`.\n- Open http://localhost:8080, and make sure Wordpress is working. If you see \"Error establishing a database connection\", wait for a minute.\n\n## eStargz version\n\neStargz version enables lazy-pulling. See [`../../docs/stargz.md`](../../docs/stargz.md).\n\nUsage: `nerdctl --snapshotter=stargz compose -f docker-compose.stargz.yaml up`\n"
  },
  {
    "path": "examples/compose-wordpress/docker-compose.stargz.yaml",
    "content": "# # Docker Compose stack for Wordpress (eStargz version)\n\n# Usage: nerdctl --snapshotter=stargz compose -f docker-compose.stargz.yaml up\nservices:\n  wordpress:\n    image: ghcr.io/stargz-containers/wordpress:5.7-esgz\n    volumes:\n      # workaround for https://github.com/containerd/stargz-snapshotter/issues/444\n      - \"/run\"\n    extends:\n      file: docker-compose.yaml\n      service: wordpress\n\n  db:\n    image: ghcr.io/stargz-containers/mariadb:10.5-esgz\n    volumes:\n      # workaround for https://github.com/containerd/stargz-snapshotter/issues/444\n      - \"/run\"\n    extends:\n      file: docker-compose.yaml\n      service: db\n\nvolumes:\n  wordpress:\n  db:\n"
  },
  {
    "path": "examples/compose-wordpress/docker-compose.yaml",
    "content": "# Docker Compose stack for Wordpress, from https://hub.docker.com/_/wordpress\n\n# !!! Make sure to substitute \"examplepass\" with your own password !!!\n\nversion: '3.1'\n\nservices:\n\n  wordpress:\n    image: wordpress:5.7\n    restart: always\n    ports:\n      - 8080:80\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n\n  db:\n    image: mariadb:10.5\n    restart: always\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n\nvolumes:\n  wordpress:\n  db:\n"
  },
  {
    "path": "examples/nerdctl-as-a-library/README.md",
    "content": "# Using nerdctl as a library\n\nThis directory contains examples showing how to implement a cli communicating with containerd, using nerdctl as a library.\n"
  },
  {
    "path": "examples/nerdctl-as-a-library/run-container/main.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\tnerdctl \"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc main() {\n\t// Implement logging\n\tif len(os.Args) == 3 && os.Args[1] == logging.MagicArgv1 {\n\t\terr := logging.Main(os.Args[2])\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Get options\n\tglobalOpt := types.GlobalCommandOptions(*config.New())\n\n\t// Rootless\n\t_ = rootlessutil.ParentMain(globalOpt.HostGatewayIP)\n\n\t// Printout options for debug\n\tf, _ := json.MarshalIndent(globalOpt, \"\", \"  \")\n\tfmt.Printf(\"%s\\n\", f)\n\n\t// Create container options\n\tcreateOpt := types.ContainerCreateOptions{\n\t\tGOptions: globalOpt,\n\t\t// TODO: this example should implement oci-hook as well instead of relying on nerdctl\n\t\tNerdctlCmd:  \"/usr/local/bin/nerdctl\",\n\t\tName:        \"my-container\",\n\t\tLabel:       []string{},\n\t\tCgroupns:    \"private\",\n\t\tInRun:       true,\n\t\tRm:          false,\n\t\tPull:        \"missing\",\n\t\tLogDriver:   \"json-file\",\n\t\tStopSignal:  \"SIGTERM\",\n\t\tRestart:     \"unless-stopped\",\n\t\tInteractive: true,\n\t}\n\n\t// Create client\n\tclient, ctx, cancel, err := clientutil.NewClient(context.Background(), globalOpt.Namespace, globalOpt.Address)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tdefer cancel()\n\n\t// Create network manager\n\tnetworkManager, err := containerutil.NewNetworkingOptionsManager(createOpt.GOptions, types.NetworkOptions{\n\t\tNetworkSlice: []string{\"bridge\"},\n\t}, client)\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\t// Create container\n\tcontainer, _, err := nerdctl.Create(ctx, client, []string{\"debian\"}, networkManager, createOpt)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\t// Start container\n\terr = nerdctl.Start(ctx, client, []string{\"my-container\"}, types.ContainerStartOptions{\n\t\tAttach: true,\n\t\tStdout: os.Stdout,\n\t})\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tcc, _ := json.MarshalIndent(container, \"\", \"  \")\n\tfmt.Println(string(cc))\n}\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/README.md",
    "content": "# Examples of Node-to-Node image sharing on Kubernetes using `nerdctl ipfs registry`\n\nThis directory contains examples of node-to-node image sharing on Kubernetes with `nerdctl ipfs registry`.\n\n- [`./ipfs`](./ipfs): node-to-node image sharing using IPFS\n- [`./ipfs-cluster`](./ipfs-cluster): node-to-node image sharing with content replication using ipfs-cluster\n- [`./ipfs-stargz-snapshotter`](./ipfs-stargz-snapshotter): node-to-node image sharing with lazy pulling using eStargz and Stargz Snapshotter\n\n## Example Dockerfile of `nerdctl ipfs registry`\n\nThe above examples use `nerdctl ipfs registry` running in a Pod.\nThe image is available at [`ghcr.io/stargz-containers/nerdctl-ipfs-registry`](https://github.com/orgs/stargz-containers/packages/container/package/nerdctl-ipfs-registry).\n\nThe following Dockerfile can be used to build it by yourself.\n\n```Dockerfile\nFROM ubuntu:22.04 AS dev\n\nARG NERDCTL_VERSION=0.23.0\nARG NERDCTL_AMD64_SHA256SUM=aa00cd197de3549469e9c62753798979203dc0607f3e60f119ed632478244553\nARG NERDCTL_ARM64_SHA256SUM=bc8095b8d60a2f25da7e5c456705dce2db020a0a87d003093550994618189ea3\nARG NERDCTL_PPC64LE_SHA256SUM=162d68a636e0a9c32f705f27390ae8ed919ca8c0442832909ebf3c0e5a884fac\nARG NERDCTL_RISCV64_SHA256SUM=1580fe87e730fe4b4442ce3e5199fa487ca03dcd0761f0bfa3c7603e4be10372\nARG NERDCTL_S390X_SHA256SUM=7905ef258968c6f331944a097afe28251c793471fdbc4b7e87aae63f999e8098\n\nRUN apt-get update -y && apt-get install -y curl && \\\n    curl -sSL --output /tmp/nerdctl.${TARGETARCH:-amd64}.tgz https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${TARGETARCH:-amd64}.tar.gz && \\\n    echo \"${NERDCTL_AMD64_SHA256SUM}  /tmp/nerdctl.amd64.tgz\" | tee /tmp/nerdctl.sha256 && \\\n    echo \"${NERDCTL_ARM64_SHA256SUM}  /tmp/nerdctl.arm64.tgz\" | tee -a /tmp/nerdctl.sha256 && \\\n    echo \"${NERDCTL_PPC64LE_SHA256SUM}  /tmp/nerdctl.ppc64le.tgz\" | tee -a /tmp/nerdctl.sha256 && \\\n    echo \"${NERDCTL_RISCV64_SHA256SUM}  /tmp/nerdctl.riscv64.tgz\" | tee -a /tmp/nerdctl.sha256 && \\\n    echo \"${NERDCTL_S390X_SHA256SUM}  /tmp/nerdctl.s390x.tgz\" | tee -a /tmp/nerdctl.sha256 && \\\n    sha256sum --ignore-missing -c /tmp/nerdctl.sha256 && \\\n    tar zxvf /tmp/nerdctl.${TARGETARCH:-amd64}.tgz -C /usr/local/bin/ && \\\n    rm /tmp/nerdctl.${TARGETARCH:-amd64}.tgz\n\nFROM ubuntu:22.04\nCOPY --from=dev /usr/local/bin/nerdctl /usr/local/bin/nerdctl\nENTRYPOINT [ \"/usr/local/bin/nerdctl\", \"ipfs\", \"registry\", \"serve\" ]\n```\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs/README.md",
    "content": "# Example: Node-to-Node image sharing on Kubernetes using `nerdctl ipfs registry`\n\nThis directory contains an example Kubernetes setup for node-to-node image sharing.\n\nUsage:\n- Generate `bootstrap.yaml` by executing `bootstrap.yaml.sh` (e.g. `./bootstrap.yaml.sh > ${DIR_LOCATION}/bootstrap.yaml`)\n  - [`ipfs-swarm-key-gen`](https://github.com/Kubuxu/go-ipfs-swarm-key-gen) is required (see https://github.com/ipfs/kubo/blob/v0.15.0/docs/experimental-features.md#private-networks)\n- Deploy `bootstrap.yaml` and `nerdctl-ipfs-registry.yaml` (e.g. using `kubectl apply`)\n- Make sure nodes contain containerd >= v1.5.8\n- You might want to change some configuration written in `nerdctl-ipfs-registry.yaml` (e.g. [chaning profile based on your node's resouce requirements](https://docs.ipfs.tech/how-to/default-profile/#available-profiles))\n\n## Example on kind\n\nPrepare cluster (make sure kind nodes contain containerd >= v1.5.8).\n\n```console\n$ cat <<EOF > /tmp/kindconfig.yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\nEOF\n$ kind create cluster --image=kindest/node:v1.25.2 --config=/tmp/kindconfig.yaml\n$ ./bootstrap.yaml.sh > ./bootstrap.yaml\n$ kubectl apply -f .\n```\n\nPrepare `kind-worker` (1st node) for importing an image to IPFS\n\n(in `kind-worker`)\n\n```console\n$ docker exec -it kind-worker /bin/bash\n(kind-worker)# NERDCTL_VERSION=0.23.0\n(kind-worker)# curl -fsSL --proto '=https' --tlsv1.2 --output /tmp/nerdctl.tgz https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz\n(kind-worker)# tar zxvf /tmp/nerdctl.tgz -C /usr/local/bin/\n```\n\nAdd an image to `kind-worker`.\n\n```console\n$ docker exec -it kind-worker /bin/bash\n(kind-worker)# mkdir -p /tmp/ipfsapi ; echo -n /ip4/127.0.0.1/tcp/5001 >  /tmp/ipfsapi/api\n(kind-worker)# export IPFS_PATH=/tmp/ipfsapi\n(kind-worker)# nerdctl pull ghcr.io/stargz-containers/jenkins:2.60.3-org\n(kind-worker)# nerdctl push ipfs://ghcr.io/stargz-containers/jenkins:2.60.3-org\n(kind-worker)# nerdctl rmi ghcr.io/stargz-containers/jenkins:2.60.3-org\n```\n\nThe image added to `kind-worker` is shared to `kind-worker2` via IPFS.\nYou can run this image on all worker nodes using the following manifest.\nCID of the pushed image is printed when `nerdctl push` succeeded (we assume that the image is added to IPFS as CID `bafkreictyyoysj56v772xbfhyfrcvmgmfpa4vodmqaroz53ytvai7nof6u`).\n\n```console\n$ cat <<EOF | kubectl apply -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: jenkins\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: jenkins\n  template:\n    metadata:\n      labels:\n        app: jenkins\n    spec:\n      containers:\n      - name: jenkins\n        image: localhost:5050/ipfs/bafkreictyyoysj56v772xbfhyfrcvmgmfpa4vodmqaroz53ytvai7nof6u\n        resources:\n          requests:\n            cpu: 1\nEOF\n```\n\n> NOTE: Kubernetes doesn't support `ipfs://CID` URL on YAML as of now so we need to use `localhost:5050/ipfs/CID` form instead. In the future, this limitation should be eliminated.\n\nThe image runs on all nodes.\n\n```console\n$ kubectl get pods -owide | grep jenkins\njenkins-7bd8f96d79-2jbc6          1/1     Running   0          69s    10.244.1.3   kind-worker    <none>           <none>\njenkins-7bd8f96d79-jb5lm          1/1     Running   0          69s    10.244.2.4   kind-worker2   <none>           <none>\n```\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs/bootstrap.yaml.sh",
    "content": "#!/bin/bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# Example script to prepare swarm key secret for IPFS bootstrap,\n# Example: ./bootstrap.yaml.sh > ./bootstrap.yaml\n\nset -eu -o pipefail\n\nif ! command -v ipfs-swarm-key-gen >/dev/null 2>&1 ; then\n    echo \"ipfs-swarm-key-gen not found\"\n    exit 1\nfi\n\nSWARM_KEY=$(ipfs-swarm-key-gen | base64 | tr -d '\\n')\n\ncat <<EOF\napiVersion: v1\nkind: Secret\nmetadata:\n  name: secret-config\ntype: Opaque\ndata:\n  ipfs-swarm-key: $SWARM_KEY\nEOF\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs/nerdctl-ipfs-registry.yaml",
    "content": "# Example YAML of IPFS-based node-to-node image sharing\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ipfs-bootstrap\nspec:\n  selector:\n    matchLabels:\n      app: ipfs-bootstrap\n  template:\n    metadata:\n      labels:\n        app: ipfs-bootstrap\n    spec:\n      initContainers:\n        - name: configure-ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/configure-ipfs.sh\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n            - name: IPFS_SWARM_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: ipfs-swarm-key\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      containers:\n        - name: id\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/id-server.sh\"]\n          ports:\n            - name: id\n              protocol: TCP\n              containerPort: 8000\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n        - name: ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"ipfs\", \"daemon\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n          ports:\n            - name: swarm\n              protocol: TCP\n              containerPort: 4001\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: swarm\n            initialDelaySeconds: 30\n            timeoutSeconds: 5\n            periodSeconds: 15\n      volumes:\n        - name: configure-script\n          configMap:\n            name: ipfs-bootstrap-conf\n        - name: ipfs-storage\n          emptyDir: {}\n\n---\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: ipfs-bootstrap\n  labels:\n    app: ipfs-bootstrap\nspec:\n  type: ClusterIP\n  ports:\n    - name: id\n      targetPort: id\n      port: 8000\n    - name: swarm\n      targetPort: swarm\n      port: 4001\n  selector:\n    app: ipfs-bootstrap\n\n---\n\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: ipfs\nspec:\n  selector:\n    matchLabels:\n      app: ipfs\n  template:\n    metadata:\n      labels:\n        app: ipfs\n    spec:\n      initContainers:\n        - name: configure-ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/configure-ipfs.sh\"]\n          env:\n            - name: BOOTSTRAP_SVC_NAME\n              value: \"ipfs-bootstrap\"\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n            - name: IPFS_SWARM_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: ipfs-swarm-key\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      containers:\n        - name: ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"ipfs\", \"daemon\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n          ports:\n            - name: swarm\n              protocol: TCP\n              containerPort: 4001\n            - name: api\n              protocol: TCP\n              containerPort: 5001\n              hostPort: 5001\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: swarm\n            initialDelaySeconds: 30\n            timeoutSeconds: 5\n            periodSeconds: 15\n        - name: nerdctl-ipfs-registry\n          image: \"ghcr.io/stargz-containers/nerdctl-ipfs-registry:v0.23.0\"\n          command: [\"sh\", \"/custom/nerdctl-ipfs-registry-entrypoint.sh\"]\n          env:\n            - name: IPFS_PATH\n              value: \"/data/ipfs\"\n          ports:\n            - containerPort: 5050\n              hostPort: 5050\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      volumes:\n        - name: configure-script\n          configMap:\n            name: ipfs-peer-conf\n        - name: ipfs-storage\n          hostPath:\n            path: /var/ipfs/\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-peer-conf\ndata:\n  nerdctl-ipfs-registry-entrypoint.sh: |\n    #!/bin/sh\n    set -eu\n\n    if ! command -v curl &> /dev/null\n    then\n        echo \"curl not found. installing...\"\n        apt-get update -y && apt-get install -y curl\n    fi\n\n    # wait for ipfs daemon\n    ok=false\n    for i in $(seq 100) ; do\n        if curl -fsSL localhost:5001/api/v0/id >/dev/null 2>&1 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect ipfs api\"\n      exit 1\n    fi\n\n    exec /usr/local/bin/nerdctl ipfs registry serve --listen-registry 0.0.0.0:5050 --ipfs-address /ip4/127.0.0.1/tcp/5001 --read-retry-num 3 --read-timeout 500ms\n\n  configure-ipfs.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    # wait for bootstrap node running\n    ok=false\n    for i in $(seq 100) ; do\n        if nc -z ${BOOTSTRAP_SVC_NAME} 4001 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect bootstrap node\"\n      exit 1\n    fi\n\n    BOOTSTRAP_ID=$(wget -O - ${BOOTSTRAP_SVC_NAME}:8000/id)\n    if [ \"${BOOTSTRAP_ID}\" == \"\" ] ; then\n      echo \"failed to get bootstrap peer id\"\n      exit 1\n    fi\n    if [ \"${IPFS_SWARM_KEY}\" == \"\" ] || [ \"${LIBP2P_FORCE_PNET}\" != \"1\" ] ; then\n      echo \"must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})\"\n      exit 1\n    fi\n\n    mkdir -p /data/ipfs\n    if ! [ -z \"$(ls -A /data/ipfs)\" ]; then\n      echo \"IPFS already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs/*\n    fi\n\n    ipfs init --profile=server\n    ipfs bootstrap rm --all\n    ipfs bootstrap add /dns4/${BOOTSTRAP_SVC_NAME}/tcp/4001/ipfs/${BOOTSTRAP_ID}\n    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\n    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\n    ipfs config Datastore.StorageMax 100GB\n    ipfs config Addresses.NoAnnounce --json '[]'\n    ipfs config Swarm.AddrFilters --json '[]'\n    echo -n \"${IPFS_SWARM_KEY}\" > /data/ipfs/swarm.key\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-bootstrap-conf\ndata:\n  id-server.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    if [ ! -f /doc/id ]; then\n      mkdir /doc\n      ipfs config show | grep \"PeerID\" | sed -E 's/.*\"PeerID\": \"([a-zA-Z0-9]*)\".*/\\1/' > /doc/id\n    fi\n    exec httpd -f -p 8000 -h /doc\n\n  configure-ipfs.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    if [ \"${IPFS_SWARM_KEY}\" == \"\" ] || [ \"${LIBP2P_FORCE_PNET}\" != \"1\" ] ; then\n      echo \"must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})\"\n    fi\n\n    mkdir -p /data/ipfs\n    if ! [ -z \"$(ls -A /data/ipfs)\" ]; then\n      echo \"IPFS already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs/*\n    fi\n\n    ipfs init --profile=server\n    ipfs bootstrap rm --all\n    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\n    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\n    ipfs config Addresses.NoAnnounce --json '[]'\n    ipfs config Swarm.AddrFilters --json '[]'\n    ipfs config Datastore.StorageMax 1GB\n    echo -n \"${IPFS_SWARM_KEY}\" > /data/ipfs/swarm.key\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs-cluster/README.md",
    "content": "# Example: Node-to-Node image sharing on Kubernetes with content replication using `nerdctl ipfs registry` with ipfs-cluster\n\nThis directory contains an example Kubernetes setup for node-to-node image sharing with content replication (ipfs-cluster).\n\nUsage:\n- Generate `bootstrap.yaml` by executing `bootstrap.yaml.sh` (e.g. `./bootstrap.yaml.sh > ${DIR_LOCATION}/bootstrap.yaml`)\n  - [`ipfs-swarm-key-gen`](https://github.com/Kubuxu/go-ipfs-swarm-key-gen) is required (see https://github.com/ipfs/kubo/blob/v0.15.0/docs/experimental-features.md#private-networks)\n  - [`ipfs-key`](https://github.com/whyrusleeping/ipfs-key) is required (see https://ipfscluster.io/documentation/guides/k8s/)\n- Deploy `bootstrap.yaml` and `nerdctl-ipfs-registry.yaml` (e.g. using `kubectl apply`)\n- Make sure nodes contain containerd >= v1.5.8\n- You might want to change some configuration written in `nerdctl-ipfs-registry.yaml` (e.g. [chaning profile based on your node's resouce requirements](https://docs.ipfs.tech/how-to/default-profile/#available-profiles))\n\n## Example on kind\n\nPrepare cluster (make sure kind nodes contain containerd >= v1.5.8).\n\n```console\n$ cat <<EOF > /tmp/kindconfig.yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\n- role: worker\nEOF\n$ kind create cluster --image=kindest/node:v1.25.2 --config=/tmp/kindconfig.yaml\n$ ./bootstrap.yaml.sh > ./bootstrap.yaml\n$ kubectl apply -f .\n```\n\nPrepare `kind-worker` (1st node) for importing an image to IPFS\n\n(in `kind-worker`)\n\n```console\n$ docker exec -it kind-worker /bin/bash\n(kind-worker)# NERDCTL_VERSION=0.23.0\n(kind-worker)# curl -o /tmp/nerdctl.tgz -fsSL --proto '=https' --tlsv1.2 https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz\n(kind-worker)# tar zxvf /tmp/nerdctl.tgz -C /usr/local/bin/\n```\n\nAdd an image to `kind-worker`.\n\n> NOTE: port 9095 needs to be used as the IPFS API port instead of 5001 (see also https://cluster.ipfs.io/documentation/reference/proxy/)\n\n```console\n$ docker exec -it kind-worker /bin/bash\n(kind-worker)# mkdir -p /tmp/ipfsapi ; echo -n /ip4/127.0.0.1/tcp/9095 >  /tmp/ipfsapi/api\n(kind-worker)# export IPFS_PATH=/tmp/ipfsapi\n(kind-worker)# nerdctl pull ghcr.io/stargz-containers/jenkins:2.60.3-org\n(kind-worker)# nerdctl push ipfs://ghcr.io/stargz-containers/jenkins:2.60.3-org\n(kind-worker)# nerdctl rmi ghcr.io/stargz-containers/jenkins:2.60.3-org\n```\n\nThe image added to `kind-worker` is shared to other nodes via IPFS.\nYou can run this image on the nodes using the following manifest.\nCID of the pushed image is printed when `nerdctl push` is succeeded (we assume that the image is added to IPFS as CID `bafkreictyyoysj56v772xbfhyfrcvmgmfpa4vodmqaroz53ytvai7nof6u`).\n\n```console\n$ cat <<EOF | kubectl apply -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: jenkins\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: jenkins\n  template:\n    metadata:\n      labels:\n        app: jenkins\n    spec:\n      containers:\n      - name: jenkins\n        image: localhost:5050/ipfs/bafkreictyyoysj56v772xbfhyfrcvmgmfpa4vodmqaroz53ytvai7nof6u\n        resources:\n          requests:\n            cpu: 1\nEOF\n```\n\n> NOTE: Kubernetes doesn't support `ipfs://CID` URL on YAML as of now so we need to use `localhost:5050/ipfs/CID` form instead. In the future, this limitation should be eliminated.\n\nThe image runs on the nodes.\n\n```console\n$ kubectl get pods -owide | grep jenkins\njenkins-7fcb4687c4-9gmrr          1/1     Running   0          22s     10.244.1.3   kind-worker    <none>           <none>\njenkins-7fcb4687c4-kvdbl          1/1     Running   0          22s     10.244.3.3   kind-worker3   <none>           <none>\n```\n\nYou can see that ipfs-cluster is activated on the cluster.\n\n```console\n$ kubectl exec -it ipfs-n59wf -c ipfs-cluster -- /bin/sh\n/ # ipfs-cluster-ctl status bafkreictyyoysj56v772xbfhyfrcvmgmfpa4vodmqaroz53ytvai7nof6u\nbafkreictyyoysj56v772xbfhyfrcvmgmfpa4vodmqaroz53ytvai7nof6u:\n    > ipfs-6cmlq           : PINNED | 2022-10-18T06:28:46Z | Attempts: 0 | Priority: false\n    > ipfs-rvcb2           : PINNED | 2022-10-18T06:28:46Z | Attempts: 0 | Priority: false\n    > ipfs-2m4tm           : REMOTE | 2022-10-18T06:30:16.23637825Z | Attempts: 0 | Priority: false\n    > ipfs-bootstrap-67c54bc878-mgtg4 : REMOTE | 2022-10-18T06:30:16.23637825Z | Attempts: 0 | Priority: false\n```\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs-cluster/bootstrap.yaml.sh",
    "content": "#!/bin/bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# Example script to prepare swarm key secret for IPFS bootstrap,\n# Example: ./bootstrap.yaml.sh > ./bootstrap.yaml\n\nset -eu -o pipefail\n\nfor d in ipfs-key ipfs-swarm-key-gen ; do\n    if ! which $d >/dev/null 2>&1 ; then\n        echo \"$d not found. See https://cluster.ipfs.io/documentation/guides/k8s/\"\n        exit 1\n    fi\ndone\n\nTMPIDFILE=$(mktemp)\nBOOTSTRAP_KEY=$(ipfs-key 2>\"${TMPIDFILE}\" | base64 -w 0)\nID=$(grep \"ID \" \"${TMPIDFILE}\" | sed -E 's/[^:]*: (.*)/\\1/')\nrm \"${TMPIDFILE}\"\n\nBOOTSTRAP_PEER_PRIV_KEY=$(echo \"${BOOTSTRAP_KEY}\" | base64 -w 0)\nCLUSTER_SECRET=$(od  -vN 32 -An -tx1 /dev/urandom | tr -d ' \\n' | base64 -w 0 -)\n\nSWARM_KEY=$(ipfs-swarm-key-gen | base64 -w 0)\n\ncat <<EOF\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: env-config\ndata:\n  cluster-bootstrap-peer-id: $ID\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: secret-config\ntype: Opaque\ndata:\n  cluster-secret: $CLUSTER_SECRET\n  cluster-bootstrap-peer-priv-key: $BOOTSTRAP_PEER_PRIV_KEY\n  ipfs-swarm-key: $SWARM_KEY\nEOF\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs-cluster/nerdctl-ipfs-registry.yaml",
    "content": "# Example YAML of IPFS-based node-to-node image sharing with ipfs-cluster\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-cluster-conf\ndata:\n  # `replication_factor_max` and `replication_factor_max`\n  # https://cluster.ipfs.io/documentation/reference/configuration/\n  cluster-replication-factor: \"2\"\n\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ipfs-bootstrap\nspec:\n  selector:\n    matchLabels:\n      app: ipfs-bootstrap\n  template:\n    metadata:\n      labels:\n        app: ipfs-bootstrap\n    spec:\n      initContainers:\n        - name: configure-ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/configure-ipfs.sh\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n            - name: IPFS_SWARM_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: ipfs-swarm-key\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      containers:\n        - name: id\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/id-server.sh\"]\n          ports:\n            - name: id\n              protocol: TCP\n              containerPort: 8000\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n        - name: ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"ipfs\", \"daemon\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n          ports:\n            - name: swarm\n              protocol: TCP\n              containerPort: 4001\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: swarm\n            initialDelaySeconds: 30\n            timeoutSeconds: 5\n            periodSeconds: 15\n        - name: ipfs-cluster\n          image: \"ghcr.io/stargz-containers/ipfs/ipfs-cluster:1.0.4\"\n          command: [\"sh\", \"/custom/cluster-entrypoint.sh\"]\n          env:\n            - name: CLUSTER_REPLICATIONFACTORMIN\n              valueFrom:\n                configMapKeyRef:\n                  name: ipfs-cluster-conf\n                  key: cluster-replication-factor\n            - name: CLUSTER_REPLICATIONFACTORMAX\n              valueFrom:\n                configMapKeyRef:\n                  name: ipfs-cluster-conf\n                  key: cluster-replication-factor\n            - name: CLUSTER_BOOTSTRAP_PEER_ID\n              valueFrom:\n                configMapKeyRef:\n                  name: env-config\n                  key: cluster-bootstrap-peer-id\n            - name: CLUSTER_BOOTSTRAP_PEER_PRIV_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: cluster-bootstrap-peer-priv-key\n            - name: CLUSTER_SECRET\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: cluster-secret\n          ports:\n            - name: api-http\n              containerPort: 9094\n              protocol: TCP\n            - name: proxy-http\n              containerPort: 9095\n              protocol: TCP\n            - name: cluster-swarm\n              containerPort: 9096\n              protocol: TCP\n          volumeMounts:\n            - name: cluster-storage\n              mountPath: /data/ipfs-cluster\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: cluster-swarm\n            initialDelaySeconds: 5\n            timeoutSeconds: 5\n            periodSeconds: 10\n      volumes:\n        - name: configure-script\n          configMap:\n            name: ipfs-bootstrap-conf\n        - name: ipfs-storage\n          emptyDir: {}\n        - name: cluster-storage\n          emptyDir: {}\n\n---\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: ipfs-bootstrap\n  labels:\n    app: ipfs-bootstrap\nspec:\n  type: ClusterIP\n  ports:\n    - name: id\n      targetPort: id\n      port: 8000\n    - name: swarm\n      targetPort: swarm\n      port: 4001\n    - name: cluster-swarm\n      targetPort: cluster-swarm\n      port: 9096\n  selector:\n    app: ipfs-bootstrap\n\n---\n\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: ipfs\nspec:\n  selector:\n    matchLabels:\n      app: ipfs\n  template:\n    metadata:\n      labels:\n        app: ipfs\n    spec:\n      initContainers:\n        - name: configure-ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/configure-ipfs.sh\"]\n          env:\n            - name: BOOTSTRAP_SVC_NAME\n              value: \"ipfs-bootstrap\"\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n            - name: IPFS_SWARM_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: ipfs-swarm-key\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      containers:\n        - name: ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"ipfs\", \"daemon\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n          ports:\n            - name: swarm\n              protocol: TCP\n              containerPort: 4001\n            - name: api\n              protocol: TCP\n              containerPort: 5001\n              hostPort: 5001\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: swarm\n            initialDelaySeconds: 30\n            timeoutSeconds: 5\n            periodSeconds: 15\n        - name: ipfs-cluster\n          image: \"ghcr.io/stargz-containers/ipfs/ipfs-cluster:1.0.4\"\n          command: [\"sh\", \"/custom/cluster-entrypoint.sh\"]\n          env:\n            - name: BOOTSTRAP_SVC_NAME\n              value: \"ipfs-bootstrap\"\n            - name: CLUSTER_REPLICATIONFACTORMIN\n              valueFrom:\n                configMapKeyRef:\n                  name: ipfs-cluster-conf\n                  key: cluster-replication-factor\n            - name: CLUSTER_REPLICATIONFACTORMAX\n              valueFrom:\n                configMapKeyRef:\n                  name: ipfs-cluster-conf\n                  key: cluster-replication-factor\n            - name: CLUSTER_BOOTSTRAP_PEER_ID\n              valueFrom:\n                configMapKeyRef:\n                  name: env-config\n                  key: cluster-bootstrap-peer-id\n            - name: CLUSTER_SECRET\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: cluster-secret\n          ports:\n            - name: api-http\n              containerPort: 9094\n              protocol: TCP\n            - name: proxy-http\n              containerPort: 9095\n              protocol: TCP\n              hostPort: 9095\n            - name: cluster-swarm\n              containerPort: 9096\n              protocol: TCP\n          volumeMounts:\n            - name: cluster-storage\n              mountPath: /data/ipfs-cluster\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: cluster-swarm\n            initialDelaySeconds: 5\n            timeoutSeconds: 5\n            periodSeconds: 10\n        - name: nerdctl-ipfs-registry\n          image: \"ghcr.io/stargz-containers/nerdctl-ipfs-registry:v0.23.0\"\n          command: [\"sh\", \"/custom/nerdctl-ipfs-registry-entrypoint.sh\"]\n          env:\n            - name: IPFS_PATH\n              value: \"/data/ipfs\"\n          ports:\n            - containerPort: 5050\n              hostPort: 5050\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      volumes:\n        - name: configure-script\n          configMap:\n            name: ipfs-peer-conf\n        - name: ipfs-storage\n          hostPath:\n            path: /var/ipfs/\n        - name: cluster-storage\n          hostPath:\n            path: /var/ipfs-cluster/\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-peer-conf\ndata:\n  nerdctl-ipfs-registry-entrypoint.sh: |\n    #!/bin/sh\n    set -eu\n\n    if ! command -v curl &> /dev/null\n    then\n        echo \"curl not found. installing...\"\n        apt-get update -y && apt-get install -y curl\n    fi\n\n    # wait for ipfs daemon\n    ok=false\n    for i in $(seq 100) ; do\n        if curl -fsSL localhost:9095/api/v0/id >/dev/null 2>&1 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect ipfs api\"\n      exit 1\n    fi\n\n    exec /usr/local/bin/nerdctl ipfs registry serve --listen-registry 0.0.0.0:5050 --ipfs-address /ip4/127.0.0.1/tcp/9095 --read-retry-num 3 --read-timeout 1s\n\n  cluster-entrypoint.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    # wait for bootstrap node running\n    ok=false\n    for i in $(seq 100) ; do\n        if nc -z ${BOOTSTRAP_SVC_NAME} 9096 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect bootstrap node\"\n      exit 1\n    fi\n\n    mkdir -p /data/ipfs-cluster\n    if ! [ -z \"$(ls -A /data/ipfs-cluster)\" ]; then\n      echo \"IPFS cluster already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs-cluster/*\n    fi\n    ipfs-cluster-service init\n    cat /data/ipfs-cluster/service.json | sed 's|/ip4/127.0.0.1/tcp/9095|/ip4/0.0.0.0/tcp/9095|' > /tmp/tmp.json\n    mv /tmp/tmp.json /data/ipfs-cluster/service.json\n\n    BOOTSTRAP_ADDR=/dns4/${BOOTSTRAP_SVC_NAME}/tcp/9096/ipfs/${CLUSTER_BOOTSTRAP_PEER_ID}\n    exec ipfs-cluster-service daemon --upgrade --bootstrap $BOOTSTRAP_ADDR --leave\n\n  configure-ipfs.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    # wait for bootstrap node running\n    ok=false\n    for i in $(seq 100) ; do\n        if nc -z ${BOOTSTRAP_SVC_NAME} 4001 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect bootstrap node\"\n      exit 1\n    fi\n\n    BOOTSTRAP_ID=$(wget -O - ${BOOTSTRAP_SVC_NAME}:8000/id)\n    if [ \"${BOOTSTRAP_ID}\" == \"\" ] ; then\n      echo \"failed to get bootstrap peer id\"\n      exit 1\n    fi\n    if [ \"${IPFS_SWARM_KEY}\" == \"\" ] || [ \"${LIBP2P_FORCE_PNET}\" != \"1\" ] ; then\n      echo \"must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})\"\n      exit 1\n    fi\n\n    mkdir -p /data/ipfs\n    if ! [ -z \"$(ls -A /data/ipfs)\" ]; then\n      echo \"IPFS already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs/*\n    fi\n\n    ipfs init --profile=server\n    ipfs bootstrap rm --all\n    ipfs bootstrap add /dns4/${BOOTSTRAP_SVC_NAME}/tcp/4001/ipfs/${BOOTSTRAP_ID}\n    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\n    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\n    ipfs config Datastore.StorageMax 100GB\n    ipfs config Addresses.NoAnnounce --json '[]'\n    ipfs config Swarm.AddrFilters --json '[]'\n    echo -n \"${IPFS_SWARM_KEY}\" > /data/ipfs/swarm.key\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-bootstrap-conf\ndata:\n  id-server.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    if [ ! -f /doc/id ]; then\n      mkdir /doc\n      ipfs config show | grep \"PeerID\" | sed -E 's/.*\"PeerID\": \"([a-zA-Z0-9]*)\".*/\\1/' > /doc/id\n    fi\n    exec httpd -f -p 8000 -h /doc\n\n  cluster-entrypoint.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    mkdir -p /data/ipfs-cluster\n    if ! [ -z \"$(ls -A /data/ipfs-cluster)\" ]; then\n      echo \"IPFS cluster already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs-cluster/*\n    fi\n    ipfs-cluster-service init\n\n    CLUSTER_ID=${CLUSTER_BOOTSTRAP_PEER_ID} \\\n    CLUSTER_PRIVATEKEY=${CLUSTER_BOOTSTRAP_PEER_PRIV_KEY} \\\n    exec ipfs-cluster-service daemon --upgrade\n\n  configure-ipfs.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    if [ \"${IPFS_SWARM_KEY}\" == \"\" ] || [ \"${LIBP2P_FORCE_PNET}\" != \"1\" ] ; then\n      echo \"must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})\"\n    fi\n\n    mkdir -p /data/ipfs\n    if ! [ -z \"$(ls -A /data/ipfs)\" ]; then\n      echo \"IPFS already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs/*\n    fi\n\n    ipfs init --profile=server\n    ipfs bootstrap rm --all\n    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\n    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\n    ipfs config Addresses.NoAnnounce --json '[]'\n    ipfs config Swarm.AddrFilters --json '[]'\n    ipfs config Datastore.StorageMax 1GB\n    echo -n \"${IPFS_SWARM_KEY}\" > /data/ipfs/swarm.key\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs-stargz-snapshotter/README.md",
    "content": "# Example: Node-to-Node image sharing on Kubernetes with lazy pulling using `nerdctl ipfs registry` with eStargz and Stargz Snapshotter\n\nThis directory contains an example Kubernetes setup for node-to-node image sharing with lazy pulling (eStargz).\n\nUsage:\n- Generate `bootstrap.yaml` by executing `bootstrap.yaml.sh` (e.g. `./bootstrap.yaml.sh > ${DIR_LOCATION}/bootstrap.yaml`)\n  - [`ipfs-swarm-key-gen`](https://github.com/Kubuxu/go-ipfs-swarm-key-gen) is required (see https://github.com/ipfs/kubo/blob/v0.15.0/docs/experimental-features.md#private-networks)\n- Deploy `bootstrap.yaml` and `nerdctl-ipfs-registry.yaml` (e.g. using `kubectl apply`)\n- Make sure nodes contain containerd >= v1.5.8 and stargz-snapshotter.\n  - Here we use `ghcr.io/containerd/stargz-snapshotter:0.12.1-kind` that contains both of them. (This image requires kind >= 0.16.0)\n- You might want to change some configuration written in `nerdctl-ipfs-registry.yaml` (e.g. [chaning profile based on your node's resouce requirements](https://docs.ipfs.tech/how-to/default-profile/#available-profiles))\n\n## About eStargz and Stargz Snapshotter\n\neStargz is an OCI-compatible image format to enable to startup a container before the entire image contents become locally available.\nThis allows fast cold startup of containers.\nNecessary chunks of image contents are fetched to the node on-demand.\nThis technique is called *lazy pulling*.\n[Stargz Snapshotter](https://github.com/containerd/stargz-snapshotter) is the plugin of containerd that enables lazy pulling of eStargz on containerd.\n\nThis example runs stargz snapshotter on each node as a systemd service and plugs it into containerd on the same node.\ncontainerd on each node performs lazy pulling of eStargz images from IPFS via `nerdct ipfs registry`.\nThus, the eStargz image starts up without waiting for the entire contents becoming locally available so faster cold start can be expected.\n\nFor more details about eStargz and lazy pulling, please refer to [Stargz Snapshotter](https://github.com/containerd/stargz-snapshotter) repository.\n\n## Example on kind\n\nPrepare cluster (make sure kind nodes contain containerd >= v1.5.8).\n\n```console\n$ cat <<EOF > /tmp/kindconfig.yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\nEOF\n$ kind create cluster --image=ghcr.io/containerd/stargz-snapshotter:0.12.1-kind --config=/tmp/kindconfig.yaml\n$ ./bootstrap.yaml.sh > ./bootstrap.yaml\n$ kubectl apply -f .\n```\n\nPrepare `kind-worker` (1st node) for importing an image to IPFS\n\n(in `kind-worker`)\n\n```console\n$ docker exec -it kind-worker /bin/bash\n(kind-worker)# NERDCTL_VERSION=0.23.0\n(kind-worker)# curl -o /tmp/nerdctl.tgz -fsSL --proto '=https' --tlsv1.2 https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz\n(kind-worker)# tar zxvf /tmp/nerdctl.tgz -C /usr/local/bin/\n```\n\nAdd an image to `kind-worker`.\n\n```console\n$ docker exec -it kind-worker /bin/bash\n(kind-worker)# mkdir -p /tmp/ipfsapi ; echo -n /ip4/127.0.0.1/tcp/5001 >  /tmp/ipfsapi/api\n(kind-worker)# export IPFS_PATH=/tmp/ipfsapi\n(kind-worker)# nerdctl pull ghcr.io/stargz-containers/jenkins:2.60.3-esgz\n(kind-worker)# nerdctl push ipfs://ghcr.io/stargz-containers/jenkins:2.60.3-esgz\n(kind-worker)# nerdctl rmi ghcr.io/stargz-containers/jenkins:2.60.3-esgz\n```\n\n> NOTE: This example copies a pre-converted eStargz image (`ghcr.io/stargz-containers/jenkins:2.60.3-esgz`) from the registry to IPFS but you can push non-eStargz image to IPFS with converting it to eStargz using `--estargz` flag of `nerdctl push`. This flag automatically performs convertion of the image to eStargz.\n\nThe eStargz image added to `kind-worker` is shared to `kind-worker2` via IPFS.\nYou can perform lazy pulling of this eStargz image among nodes using the following manifest.\nCID of the pushed image is printed when `nerdctl push` is succeeded (we assume that the image is added to IPFS as CID `bafkreidqrxutnnuc3oilje27px5o3gggzrfyomumrprcavr7nquoy3cdje`).\n\n\n```console\n$ cat <<EOF | kubectl apply -f -\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: jenkins\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: jenkins\n  template:\n    metadata:\n      labels:\n        app: jenkins\n    spec:\n      containers:\n      - name: jenkins\n        image: localhost:5050/ipfs/bafkreidqrxutnnuc3oilje27px5o3gggzrfyomumrprcavr7nquoy3cdje\n        resources:\n          requests:\n            cpu: 1\nEOF\n```\n\n> NOTE1: Kubernetes doesn't support `ipfs://CID` URL on YAML as of now so we need to use `localhost:5050/ipfs/CID` form instead. In the future, this limitation should be eliminated.\n\n> NOTE2: stargz-snapshotter currently perfoms lazy pulling via `nerdctl ipfs registry` running on localhost instead of leveraging its [native support for fetching contents via ipfs daemon](https://github.com/containerd/stargz-snapshotter/blob/v0.12.0/docs/ipfs.md). This is because of the limitation described in NOTE1 and expected to be fixed once NOTE1 is solved.\n\nThe image runs on all nodes.\nYou may observe faster pulling of the image by eStargz.\n\n```console\n$ kubectl get pods -owide | grep jenkins\njenkins-959bc9548-6hcwc           1/1     Running   0          8s     10.244.2.4   kind-worker    <none>           <none>\njenkins-959bc9548-rfsxm           1/1     Running   0          8s     10.244.1.3   kind-worker2   <none>           <none>\n$ kubectl get pods -o name | grep jenkins | xargs -I{} kubectl describe {} | grep Pulled\n  Normal  Pulled     2m13s  kubelet            Successfully pulled image \"localhost:5050/ipfs/bafkreidqrxutnnuc3oilje27px5o3gggzrfyomumrprcavr7nquoy3cdje\" in 1.339830318s\n  Normal  Pulled     2m13s  kubelet            Successfully pulled image \"localhost:5050/ipfs/bafkreidqrxutnnuc3oilje27px5o3gggzrfyomumrprcavr7nquoy3cdje\" in 1.585882041s\n```\n\neStargz filesystem is used as the rootfs of the container.\nThe file contents are lazily downloaded to the node.\n\n```console\n$ docker exec -it kind-worker2 mount | grep \"stargz on\"\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/37/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/38/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/39/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/40/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/41/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/42/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/43/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/44/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/45/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/46/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/47/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/48/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/49/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/50/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/51/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/52/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/53/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/54/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/55/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\nstargz on /var/lib/containerd-stargz-grpc/snapshotter/snapshots/56/fs type fuse.rawBridge (rw,nodev,relatime,user_id=0,group_id=0,allow_other)\n```\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs-stargz-snapshotter/bootstrap.yaml.sh",
    "content": "#!/bin/bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# Example script to prepare swarm key secret for IPFS bootstrap,\n# Example: ./bootstrap.yaml.sh > ./bootstrap.yaml\n\nset -eu -o pipefail\n\nif ! command -v ipfs-swarm-key-gen >/dev/null 2>&1 ; then\n    echo \"ipfs-swarm-key-gen not found\"\n    exit 1\nfi\n\nSWARM_KEY=$(ipfs-swarm-key-gen | base64 | tr -d '\\n')\n\ncat <<EOF\napiVersion: v1\nkind: Secret\nmetadata:\n  name: secret-config\ntype: Opaque\ndata:\n  ipfs-swarm-key: $SWARM_KEY\nEOF\n"
  },
  {
    "path": "examples/nerdctl-ipfs-registry-kubernetes/ipfs-stargz-snapshotter/nerdctl-ipfs-registry.yaml",
    "content": "# Example YAML of IPFS-based node-to-node image sharing\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ipfs-bootstrap\nspec:\n  selector:\n    matchLabels:\n      app: ipfs-bootstrap\n  template:\n    metadata:\n      labels:\n        app: ipfs-bootstrap\n    spec:\n      initContainers:\n        - name: configure-ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/configure-ipfs.sh\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n            - name: IPFS_SWARM_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: ipfs-swarm-key\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      containers:\n        - name: id\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/id-server.sh\"]\n          ports:\n            - name: id\n              protocol: TCP\n              containerPort: 8000\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n        - name: ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"ipfs\", \"daemon\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n          ports:\n            - name: swarm\n              protocol: TCP\n              containerPort: 4001\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: swarm\n            initialDelaySeconds: 30\n            timeoutSeconds: 5\n            periodSeconds: 15\n      volumes:\n        - name: configure-script\n          configMap:\n            name: ipfs-bootstrap-conf\n        - name: ipfs-storage\n          emptyDir: {}\n\n---\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: ipfs-bootstrap\n  labels:\n    app: ipfs-bootstrap\nspec:\n  type: ClusterIP\n  ports:\n    - name: id\n      targetPort: id\n      port: 8000\n    - name: swarm\n      targetPort: swarm\n      port: 4001\n  selector:\n    app: ipfs-bootstrap\n\n---\n\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: ipfs\nspec:\n  selector:\n    matchLabels:\n      app: ipfs\n  template:\n    metadata:\n      labels:\n        app: ipfs\n    spec:\n      initContainers:\n        - name: configure-ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"sh\", \"/custom/configure-ipfs.sh\"]\n          env:\n            - name: BOOTSTRAP_SVC_NAME\n              value: \"ipfs-bootstrap\"\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n            - name: IPFS_SWARM_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: secret-config\n                  key: ipfs-swarm-key\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      containers:\n        - name: ipfs\n          image: \"ghcr.io/stargz-containers/ipfs/kubo:v0.16.0\"\n          command: [\"ipfs\", \"daemon\"]\n          env:\n            - name: LIBP2P_FORCE_PNET\n              value: \"1\"\n          ports:\n            - name: swarm\n              protocol: TCP\n              containerPort: 4001\n            - name: api\n              protocol: TCP\n              containerPort: 5001\n              hostPort: 5001\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n          livenessProbe:\n            tcpSocket:\n              port: swarm\n            initialDelaySeconds: 30\n            timeoutSeconds: 5\n            periodSeconds: 15\n        - name: nerdctl-ipfs-registry\n          image: \"ghcr.io/stargz-containers/nerdctl-ipfs-registry:v0.23.0\"\n          command: [\"sh\", \"/custom/nerdctl-ipfs-registry-entrypoint.sh\"]\n          env:\n            - name: IPFS_PATH\n              value: \"/data/ipfs\"\n          ports:\n            - containerPort: 5050\n              hostPort: 5050\n          volumeMounts:\n            - name: ipfs-storage\n              mountPath: /data/ipfs\n            - name: configure-script\n              mountPath: /custom\n      volumes:\n        - name: configure-script\n          configMap:\n            name: ipfs-peer-conf\n        - name: ipfs-storage\n          hostPath:\n            path: /var/ipfs/\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-peer-conf\ndata:\n  nerdctl-ipfs-registry-entrypoint.sh: |\n    #!/bin/sh\n    set -eu\n\n    if ! command -v curl &> /dev/null\n    then\n        echo \"curl not found. installing...\"\n        apt-get update -y && apt-get install -y curl\n    fi\n\n    # wait for ipfs daemon\n    ok=false\n    for i in $(seq 100) ; do\n        if curl -fsSL localhost:5001/api/v0/id >/dev/null 2>&1 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect ipfs api\"\n      exit 1\n    fi\n\n    exec /usr/local/bin/nerdctl ipfs registry serve --listen-registry 0.0.0.0:5050 --ipfs-address /ip4/127.0.0.1/tcp/5001 --read-retry-num 3 --read-timeout 500ms\n\n  configure-ipfs.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    # wait for bootstrap node running\n    ok=false\n    for i in $(seq 100) ; do\n        if nc -z ${BOOTSTRAP_SVC_NAME} 4001 ; then\n            ok=true\n            break\n        fi\n        echo \"Fail(${i}). Retrying...\"\n        sleep 3\n    done\n    if [ \"$ok\" != \"true\" ] ; then\n      echo \"failed to detect bootstrap node\"\n      exit 1\n    fi\n\n    BOOTSTRAP_ID=$(wget -O - ${BOOTSTRAP_SVC_NAME}:8000/id)\n    if [ \"${BOOTSTRAP_ID}\" == \"\" ] ; then\n      echo \"failed to get bootstrap peer id\"\n      exit 1\n    fi\n    if [ \"${IPFS_SWARM_KEY}\" == \"\" ] || [ \"${LIBP2P_FORCE_PNET}\" != \"1\" ] ; then\n      echo \"must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})\"\n      exit 1\n    fi\n\n    mkdir -p /data/ipfs\n    if ! [ -z \"$(ls -A /data/ipfs)\" ]; then\n      echo \"IPFS already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs/*\n    fi\n\n    ipfs init --profile=server\n    ipfs bootstrap rm --all\n    ipfs bootstrap add /dns4/${BOOTSTRAP_SVC_NAME}/tcp/4001/ipfs/${BOOTSTRAP_ID}\n    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\n    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\n    ipfs config Datastore.StorageMax 100GB\n    ipfs config Addresses.NoAnnounce --json '[]'\n    ipfs config Swarm.AddrFilters --json '[]'\n    echo -n \"${IPFS_SWARM_KEY}\" > /data/ipfs/swarm.key\n\n---\n\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ipfs-bootstrap-conf\ndata:\n  id-server.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    if [ ! -f /doc/id ]; then\n      mkdir /doc\n      ipfs config show | grep \"PeerID\" | sed -E 's/.*\"PeerID\": \"([a-zA-Z0-9]*)\".*/\\1/' > /doc/id\n    fi\n    exec httpd -f -p 8000 -h /doc\n\n  configure-ipfs.sh: |\n    #!/bin/sh\n    set -eu -o pipefail\n\n    if [ \"${IPFS_SWARM_KEY}\" == \"\" ] || [ \"${LIBP2P_FORCE_PNET}\" != \"1\" ] ; then\n      echo \"must be forced to private ipfs network (got LIBP2P_FORCE_PNET=${LIBP2P_FORCE_PNET})\"\n    fi\n\n    mkdir -p /data/ipfs\n    if ! [ -z \"$(ls -A /data/ipfs)\" ]; then\n      echo \"IPFS already configured on this node; destroying the current repo and refreshing...\"\n      rm -rf /data/ipfs/*\n    fi\n\n    ipfs init --profile=server\n    ipfs bootstrap rm --all\n    ipfs config Addresses.API /ip4/0.0.0.0/tcp/5001\n    ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080\n    ipfs config Addresses.NoAnnounce --json '[]'\n    ipfs config Swarm.AddrFilters --json '[]'\n    ipfs config Datastore.StorageMax 1GB\n    echo -n \"${IPFS_SWARM_KEY}\" > /data/ipfs/swarm.key\n"
  },
  {
    "path": "extras/rootless/containerd-rootless-setuptool.sh",
    "content": "#!/bin/sh\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# -----------------------------------------------------------------------------\n# Forked from https://github.com/moby/moby/blob/v20.10.3/contrib/dockerd-rootless-setuptool.sh\n# Copyright The Moby Authors.\n# Licensed under the Apache License, Version 2.0\n# NOTICE: https://github.com/moby/moby/blob/v20.10.3/NOTICE\n# -----------------------------------------------------------------------------\n\n# containerd-rootless-setuptool.sh: setup tool for containerd-rootless.sh\n# Needs to be executed as a non-root user.\n#\n# Typical usage: containerd-rootless-setuptool.sh install\nset -eu\n\n# utility functions\nINFO() {\n\tprintf \"\\e[104m\\e[97m[INFO]\\e[49m\\e[39m %s\\n\" \"$*\"\n}\n\nWARNING() {\n\t>&2 printf \"\\e[101m\\e[97m[WARNING]\\e[49m\\e[39m %s\\n\" \"$*\"\n}\n\nERROR() {\n\t>&2 printf \"\\e[101m\\e[97m[ERROR]\\e[49m\\e[39m %s\\n\" \"$*\"\n}\n\n# constants\nCONTAINERD_ROOTLESS_SH=\"containerd-rootless.sh\"\nSYSTEMD_CONTAINERD_UNIT=\"containerd.service\"\nSYSTEMD_BUILDKIT_UNIT=\"buildkit.service\"\nSYSTEMD_FUSE_OVERLAYFS_UNIT=\"containerd-fuse-overlayfs.service\"\nSYSTEMD_STARGZ_UNIT=\"stargz-snapshotter.service\"\nSYSTEMD_IPFS_UNIT=\"ipfs-daemon.service\"\nSYSTEMD_BYPASS4NETNSD_UNIT=\"bypass4netnsd.service\"\n\n# global vars\nARG0=\"$0\"\nREALPATH0=\"$(realpath \"$ARG0\")\"\nBIN=\"\"\nXDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:-$HOME/.config}\"\nXDG_DATA_HOME=\"${XDG_DATA_HOME:-$HOME/.local/share}\"\n\n# run checks and also initialize global vars (BIN)\ninit() {\n\tid=\"$(id -u)\"\n\t# User verification: deny running as root\n\tif [ \"$id\" = \"0\" ]; then\n\t\tERROR \"Refusing to install rootless containerd as the root user\"\n\t\texit 1\n\tfi\n\n\t# set BIN\n\tif ! BIN=\"$(command -v \"$CONTAINERD_ROOTLESS_SH\" 2>/dev/null)\"; then\n\t\tERROR \"$CONTAINERD_ROOTLESS_SH needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tBIN=$(dirname \"$BIN\")\n\n\t# detect systemd\n\tif ! systemctl --user show-environment >/dev/null 2>&1; then\n\t\tERROR \"Needs systemd (systemctl --user)\"\n\t\texit 1\n\tfi\n\n\t# HOME verification\n\tif [ -z \"${HOME:-}\" ] || [ ! -d \"$HOME\" ]; then\n\t\tERROR \"HOME needs to be set\"\n\t\texit 1\n\tfi\n\tif [ ! -w \"$HOME\" ]; then\n\t\tERROR \"HOME needs to be writable\"\n\t\texit 1\n\tfi\n\n\t# Validate XDG_RUNTIME_DIR\n\tif [ -z \"${XDG_RUNTIME_DIR:-}\" ] || [ ! -w \"$XDG_RUNTIME_DIR\" ]; then\n\t\tERROR \"Aborting because but XDG_RUNTIME_DIR (\\\"$XDG_RUNTIME_DIR\\\") is not set, does not exist, or is not writable\"\n\t\tERROR \"Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:\"\n\t\tERROR \"- try again by first running with root privileges 'loginctl enable-linger <user>' where <user> is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user <user>'\"\n\t\tERROR \"- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)\"\n\t\tERROR \"See also https://rootlesscontaine.rs/getting-started/common/login/ .\"\n\t\texit 1\n\tfi\n}\n\n# CLI subcommand: \"check\"\ncmd_entrypoint_check() {\n\tinit\n\tINFO \"Checking RootlessKit functionality\"\n\tif ! rootlesskit \\\n\t\t--net=slirp4netns \\\n\t\t--disable-host-loopback \\\n\t\t--copy-up=/etc --copy-up=/run --copy-up=/var/lib \\\n\t\ttrue; then\n\t\tERROR \"RootlessKit failed, see the error messages and https://rootlesscontaine.rs/getting-started/common/ .\"\n\t\texit 1\n\tfi\n\n\tINFO \"Checking cgroup v2\"\n\tcontrollers=\"/sys/fs/cgroup/user.slice/user-${id}.slice/user@${id}.service/cgroup.controllers\"\n\tif [ ! -f \"${controllers}\" ]; then\n\t\tWARNING \"Enabling cgroup v2 is highly recommended, see https://rootlesscontaine.rs/getting-started/common/cgroup2/ \"\n\telse\n\t\tfor f in cpu memory pids; do\n\t\t\tif ! grep -qw \"$f\" \"$controllers\"; then\n\t\t\t\tWARNING \"The cgroup v2 controller \\\"$f\\\" is not delegated for the current user (\\\"$controllers\\\"), see https://rootlesscontaine.rs/getting-started/common/cgroup2/\"\n\t\t\tfi\n\t\tdone\n\tfi\n\n\tINFO \"Checking overlayfs\"\n\ttmp=$(mktemp -d)\n\tmkdir -p \"${tmp}/l\" \"${tmp}/u\" \"${tmp}/w\" \"${tmp}/m\"\n\tif ! rootlesskit mount -t overlay -o lowerdir=\"${tmp}/l,upperdir=${tmp}/u,workdir=${tmp}/w\" overlay \"${tmp}/m\"; then\n\t\tWARNING \"Overlayfs is not enabled, consider installing fuse-overlayfs snapshotter (\\`$0 install-fuse-overlayfs\\`), \" \\\n\t\t\t\"or see https://rootlesscontaine.rs/how-it-works/overlayfs/ to enable overlayfs.\"\n\tfi\n\trm -rf \"${tmp}\"\n\tINFO \"Requirements are satisfied\"\n}\n\npropagate_env_from() {\n\tpid=\"$1\"\n\tenv=\"$(sed -e \"s/\\x0/'\\n/g\" <\"/proc/${pid}/environ\" | sed -Ee \"s/^[^=]*=/export \\0'/g\")\"\n\tshift\n\tfor key in \"$@\"; do\n\t\teval \"$(echo \"$env\" | grep \"^export ${key=}\")\"\n\tdone\n}\n\n# CLI subcommand: \"nsenter\"\ncmd_entrypoint_nsenter() {\n\t# No need to call init()\n\tpid=$(cat \"$XDG_RUNTIME_DIR/containerd-rootless/child_pid\")\n\tn=\"\"\n\t# If RootlessKit is running with `--detach-netns` mode, we do NOT enter the detached netns here\n\tif [ ! -e \"$XDG_RUNTIME_DIR/containerd-rootless/netns\" ]; then\n\t\tn=\"-n\"\n\tfi\n\tpropagate_env_from \"$pid\" ROOTLESSKIT_STATE_DIR ROOTLESSKIT_PARENT_EUID ROOTLESSKIT_PARENT_EGID\n\texec nsenter --no-fork --wd=\"$(pwd)\" --preserve-credentials -m $n -U -t \"$pid\" -- \"$@\"\n}\n\nshow_systemd_error() {\n\tunit=\"$1\"\n\tn=\"20\"\n\tERROR \"Failed to start ${unit}. Run \\`journalctl -n ${n} --no-pager --user --unit ${unit}\\` to show the error log.\"\n\tERROR \"Before retrying installation, you might need to uninstall the current setup: \\`$0 uninstall; ${BIN}/rootlesskit rm -rf ${HOME}/.local/share/containerd\\`\"\n}\n\ninstall_systemd_unit() {\n\tunit=\"$1\"\n\tunit_file=\"${XDG_CONFIG_HOME}/systemd/user/${unit}\"\n\tif [ -f \"${unit_file}\" ]; then\n\t\tWARNING \"File already exists, skipping: ${unit_file}\"\n\telse\n\t\tINFO \"Creating \\\"${unit_file}\\\"\"\n\t\tmkdir -p \"${XDG_CONFIG_HOME}/systemd/user\"\n\t\tcat >\"${unit_file}\"\n\t\tsystemctl --user daemon-reload\n\tfi\n\tif ! systemctl --user --no-pager status \"${unit}\" >/dev/null 2>&1; then\n\t\tINFO \"Starting systemd unit \\\"${unit}\\\"\"\n\t\t(\n\t\t\tset -x\n\t\t\tif ! systemctl --user start \"${unit}\"; then\n\t\t\t\tset +x\n\t\t\t\tshow_systemd_error \"${unit}\"\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tsleep 3\n\t\t)\n\tfi\n\t(\n\t\tset -x\n\t\tif ! systemctl --user --no-pager --full status \"${unit}\"; then\n\t\t\tset +x\n\t\t\tshow_systemd_error \"${unit}\"\n\t\t\texit 1\n\t\tfi\n\t\tsystemctl --user enable \"${unit}\"\n\t)\n\tINFO \"Installed \\\"${unit}\\\" successfully.\"\n\tINFO \"To control \\\"${unit}\\\", run: \\`systemctl --user (start|stop|restart) ${unit}\\`\"\n}\n\nuninstall_systemd_unit() {\n\tunit=\"$1\"\n\tunit_file=\"${XDG_CONFIG_HOME}/systemd/user/${unit}\"\n\tif [ ! -f \"${unit_file}\" ]; then\n\t\tINFO \"Unit ${unit} is not installed\"\n\t\treturn\n\tfi\n\t(\n\t\tset -x\n\t\tsystemctl --user stop \"${unit}\"\n\t) || :\n\t(\n\t\tset -x\n\t\tsystemctl --user disable \"${unit}\"\n\t) || :\n\trm -f \"${unit_file}\"\n\tINFO \"Uninstalled \\\"${unit}\\\"\"\n}\n\n# CLI subcommand: \"install\"\ncmd_entrypoint_install() {\n\tinit\n\tcmd_entrypoint_check\n\tcat <<-EOT | install_systemd_unit \"${SYSTEMD_CONTAINERD_UNIT}\"\n\t\t[Unit]\n\t\tDescription=containerd (Rootless)\n\t\tRequires=dbus.socket\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tEnvironment=CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS=${CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS:-}\n\t\tExecStart=$BIN/${CONTAINERD_ROOTLESS_SH}\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tTimeoutSec=0\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tStartLimitBurst=3\n\t\tStartLimitInterval=60s\n\t\tLimitNOFILE=infinity\n\t\tLimitNPROC=infinity\n\t\tLimitCORE=infinity\n\t\tTasksMax=infinity\n\t\tDelegate=yes\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n\tsystemctl --user daemon-reload\n\tINFO \"To run \\\"${SYSTEMD_CONTAINERD_UNIT}\\\" on system startup automatically, run: \\`sudo loginctl enable-linger $(id -un)\\`\"\n\tINFO \"------------------------------------------------------------------------------------------\"\n\tINFO \"Use \\`nerdctl\\` to connect to the rootless containerd.\"\n\tINFO \"You do NOT need to specify \\$CONTAINERD_ADDRESS explicitly.\"\n}\n\n# CLI subcommand: \"install-buildkit\"\ncmd_entrypoint_install_buildkit() {\n\tinit\n\tif ! command -v \"buildkitd\" >/dev/null 2>&1; then\n\t\tERROR \"buildkitd (https://github.com/moby/buildkit) needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tif ! systemctl --user --no-pager status \"${SYSTEMD_CONTAINERD_UNIT}\" >/dev/null 2>&1; then\n\t\tERROR \"Install containerd first (\\`$ARG0 install\\`)\"\n\t\texit 1\n\tfi\n\tBUILDKITD_FLAG=\"--oci-worker=true --oci-worker-rootless=true --containerd-worker=false\"\n\tif buildkitd --help | grep -q bridge; then\n\t\t# Available since BuildKit v0.13\n\t\tBUILDKITD_FLAG=\"${BUILDKITD_FLAG} --oci-worker-net=bridge\"\n\tfi\n\tcat <<-EOT | install_systemd_unit \"${SYSTEMD_BUILDKIT_UNIT}\"\n\t\t[Unit]\n\t\tDescription=BuildKit (Rootless)\n\t\tPartOf=${SYSTEMD_CONTAINERD_UNIT}\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tExecStart=\"$REALPATH0\" nsenter -- buildkitd ${BUILDKITD_FLAG}\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n}\n\n# CLI subcommand: \"install-buildkit-containerd\"\ncmd_entrypoint_install_buildkit_containerd() {\n\tinit\n\tif ! command -v \"buildkitd\" >/dev/null 2>&1; then\n\t\tERROR \"buildkitd (https://github.com/moby/buildkit) needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tif ! systemctl --user --no-pager status \"${SYSTEMD_CONTAINERD_UNIT}\" >/dev/null 2>&1; then\n\t\tERROR \"Install containerd first (\\`$ARG0 install\\`)\"\n\t\texit 1\n\tfi\n\tUNIT_NAME=${SYSTEMD_BUILDKIT_UNIT}\n\tBUILDKITD_FLAG=\"--oci-worker=false --containerd-worker=true --containerd-worker-rootless=true\"\n\tif [ -n \"${CONTAINERD_NAMESPACE:-}\" ]; then\n\t\tUNIT_NAME=\"${CONTAINERD_NAMESPACE}-${SYSTEMD_BUILDKIT_UNIT}\"\n\t\tBUILDKITD_FLAG=\"${BUILDKITD_FLAG} --addr=unix://${XDG_RUNTIME_DIR}/buildkit-${CONTAINERD_NAMESPACE}/buildkitd.sock --root=${XDG_DATA_HOME}/buildkit-${CONTAINERD_NAMESPACE} --containerd-worker-namespace=${CONTAINERD_NAMESPACE}\"\n\telse\n\t\tWARNING \"buildkitd has access to images in \\\"buildkit\\\" namespace by default. If you want to give buildkitd access to the images in \\\"default\\\" namespace, run this command with CONTAINERD_NAMESPACE=default\"\n\tfi\n\tif [ -n \"${CONTAINERD_SNAPSHOTTER:-}\" ]; then\n\t\tBUILDKITD_FLAG=\"${BUILDKITD_FLAG} --containerd-worker-snapshotter=${CONTAINERD_SNAPSHOTTER}\"\n\tfi\n\tif buildkitd --help | grep -q bridge; then\n\t\t# Available since BuildKit v0.13\n\t\tBUILDKITD_FLAG=\"${BUILDKITD_FLAG} --containerd-worker-net=bridge\"\n\tfi\n\tcat <<-EOT | install_systemd_unit \"${UNIT_NAME}\"\n\t\t[Unit]\n\t\tDescription=BuildKit (Rootless)\n\t\tPartOf=${SYSTEMD_CONTAINERD_UNIT}\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tExecStart=\"$REALPATH0\" nsenter -- buildkitd ${BUILDKITD_FLAG}\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n}\n\n# CLI subcommand: \"install-bypass4netnsd\"\ncmd_entrypoint_install_bypass4netnsd() {\n\tinit\n\tif ! command -v \"bypass4netnsd\" >/dev/null 2>&1; then\n\t\tERROR \"bypass4netnsd (https://github.com/rootless-containers/bypass4netns) needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tcommand_v_bypass4netnsd=\"$(command -v bypass4netnsd)\"\n\t# FIXME: bail if bypass4netnsd is an alias\n\tcat <<-EOT | install_systemd_unit \"${SYSTEMD_BYPASS4NETNSD_UNIT}\"\n\t\t[Unit]\n\t\tDescription=bypass4netnsd (daemon for bypass4netns, accelerator for rootless containers)\n\t\t# Not PartOf=${SYSTEMD_CONTAINERD_UNIT}\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tExecStart=\"${command_v_bypass4netnsd}\"\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n\tINFO \"To use bypass4netnsd, set the \\\"nerdctl/bypass4netns=true\\\" annotation on containers, e.g., \\`nerdctl run --annotation nerdctl/bypass4netns=true\\`\"\n}\n\n# CLI subcommand: \"install-fuse-overlayfs\"\ncmd_entrypoint_install_fuse_overlayfs() {\n\tinit\n\tif ! command -v \"containerd-fuse-overlayfs-grpc\" >/dev/null 2>&1; then\n\t\tERROR \"containerd-fuse-overlayfs-grpc (https://github.com/containerd/fuse-overlayfs-snapshotter) needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tif ! command -v \"fuse-overlayfs\" >/dev/null 2>&1; then\n\t\tERROR \"fuse-overlayfs (https://github.com/containers/fuse-overlayfs) needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tif ! systemctl --user --no-pager status \"${SYSTEMD_CONTAINERD_UNIT}\" >/dev/null 2>&1; then\n\t\tERROR \"Install containerd first (\\`$ARG0 install\\`)\"\n\t\texit 1\n\tfi\n\tcat <<-EOT | install_systemd_unit \"${SYSTEMD_FUSE_OVERLAYFS_UNIT}\"\n\t\t[Unit]\n\t\tDescription=containerd-fuse-overlayfs (Rootless)\n\t\tPartOf=${SYSTEMD_CONTAINERD_UNIT}\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tExecStart=\"$REALPATH0\" nsenter containerd-fuse-overlayfs-grpc \"${XDG_RUNTIME_DIR}/containerd-fuse-overlayfs.sock\" \"${XDG_DATA_HOME}/containerd-fuse-overlayfs\"\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n\tINFO \"Add the following lines to \\\"${XDG_CONFIG_HOME}/containerd/config.toml\\\" manually, and then run \\`systemctl --user restart ${SYSTEMD_CONTAINERD_UNIT}\\`:\"\n\tcat <<-EOT\n\t\t### BEGIN ###\n\t\t[proxy_plugins]\n\t\t  [proxy_plugins.\"fuse-overlayfs\"]\n\t\t    type = \"snapshot\"\n\t\t    address = \"${XDG_RUNTIME_DIR}/containerd-fuse-overlayfs.sock\"\n\t\t  [proxy_plugins.\"fuse-overlayfs\".exports]\n\t\t    root = \"${XDG_DATA_HOME}/containerd-fuse-overlayfs/\"\n\t\t\tenable_remote_snapshot_annotations = \"true\"\n\t\t[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n\t\t\tplatform = \"linux\"\n\t\t\tsnapshotter = \"fuse-overlayfs\"\n\t\t[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n\t\t\tplatform = \"linux\"\n\t\t\tsnapshotter = \"overlayfs\"\n\t\t###  END  ###\n\tEOT\n\tINFO \"Set \\`export CONTAINERD_SNAPSHOTTER=\\\"fuse-overlayfs\\\"\\` to use the fuse-overlayfs snapshotter.\"\n}\n\n# CLI subcommand: \"install-stargz\"\ncmd_entrypoint_install_stargz() {\n\tinit\n\tif ! command -v \"containerd-stargz-grpc\" >/dev/null 2>&1; then\n\t\tERROR \"containerd-stargz-grpc (https://github.com/containerd/stargz-snapshotter) needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tif ! systemctl --user --no-pager status \"${SYSTEMD_CONTAINERD_UNIT}\" >/dev/null 2>&1; then\n\t\tERROR \"Install containerd first (\\`$ARG0 install\\`)\"\n\t\texit 1\n\tfi\n\tif [ ! -f \"${XDG_CONFIG_HOME}/containerd-stargz-grpc/config.toml\" ]; then\n\t\tmkdir -p \"${XDG_CONFIG_HOME}/containerd-stargz-grpc\"\n\t\ttouch \"${XDG_CONFIG_HOME}/containerd-stargz-grpc/config.toml\"\n\tfi\n\tcat <<-EOT | install_systemd_unit \"${SYSTEMD_STARGZ_UNIT}\"\n\t\t[Unit]\n\t\tDescription=stargz snapshotter (Rootless)\n\t\tPartOf=${SYSTEMD_CONTAINERD_UNIT}\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tEnvironment=IPFS_PATH=${XDG_DATA_HOME}/ipfs\n\t\tExecStart=\"$REALPATH0\" nsenter -- containerd-stargz-grpc -address \"${XDG_RUNTIME_DIR}/containerd-stargz-grpc/containerd-stargz-grpc.sock\" -root \"${XDG_DATA_HOME}/containerd-stargz-grpc\" -config \"${XDG_CONFIG_HOME}/containerd-stargz-grpc/config.toml\"\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n\tINFO \"Add the following lines to \\\"${XDG_CONFIG_HOME}/containerd/config.toml\\\" manually, and then run \\`systemctl --user restart ${SYSTEMD_CONTAINERD_UNIT}\\`:\"\n\tcat <<-EOT\n\t\t### BEGIN ###\n\t\t[proxy_plugins]\n\t\t  [proxy_plugins.\"stargz\"]\n\t\t    type = \"snapshot\"\n\t\t    address = \"${XDG_RUNTIME_DIR}/containerd-stargz-grpc/containerd-stargz-grpc.sock\"\n\t\t  [proxy_plugins.stargz.exports]\n\t\t    root = \"${XDG_DATA_HOME}/containerd-stargz-grpc/\"\n\t\t    enable_remote_snapshot_annotations = \"true\"\n\t\t[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n\t\t\tplatform = \"linux\"\n\t\t\tsnapshotter = \"stargz\"\n\t\t[[plugins.\"io.containerd.transfer.v1.local\".unpack_config]]\n\t\t\tplatform = \"linux\"\n\t\t\tsnapshotter = \"overlayfs\"\n\t\t###  END  ###\n\tEOT\n\tINFO \"Set \\`export CONTAINERD_SNAPSHOTTER=\\\"stargz\\\"\\` to use the stargz snapshotter.\"\n}\n\n# CLI subcommand: \"install-ipfs\"\ncmd_entrypoint_install_ipfs() {\n\tinit\n\tif ! command -v \"ipfs\" >/dev/null 2>&1; then\n\t\tERROR \"ipfs needs to be present under \\$PATH\"\n\t\texit 1\n\tfi\n\tif ! systemctl --user --no-pager status \"${SYSTEMD_CONTAINERD_UNIT}\" >/dev/null 2>&1; then\n\t\tERROR \"Install containerd first (\\`$ARG0 install\\`)\"\n\t\texit 1\n\tfi\n\tIPFS_PATH=\"${XDG_DATA_HOME}/ipfs\"\n\tmkdir -p \"${IPFS_PATH}\"\n\tcat <<-EOT | install_systemd_unit \"${SYSTEMD_IPFS_UNIT}\"\n\t\t[Unit]\n\t\tDescription=ipfs daemon for rootless nerdctl\n\t\tPartOf=${SYSTEMD_CONTAINERD_UNIT}\n\n\t\t[Service]\n\t\tEnvironment=PATH=$BIN:/sbin:/usr/sbin:$PATH\n\t\tEnvironment=IPFS_PATH=${IPFS_PATH}\n\t\tExecStart=\"$REALPATH0\" nsenter -- ipfs daemon $@\n\t\tExecReload=/bin/kill -s HUP \\$MAINPID\n\t\tRestartSec=2\n\t\tRestart=always\n\t\tType=simple\n\t\tKillMode=mixed\n\n\t\t[Install]\n\t\tWantedBy=default.target\n\tEOT\n\n\t# Avoid using 5001(api)/8080(gateway) which are reserved by tests.\n\t# TODO: support unix socket\n\tsystemctl --user stop \"${SYSTEMD_IPFS_UNIT}\"\n\tsleep 3\n\tIPFS_PATH=${IPFS_PATH} ipfs config Addresses.API \"/ip4/127.0.0.1/tcp/5888\"\n\tIPFS_PATH=${IPFS_PATH} ipfs config Addresses.Gateway \"/ip4/127.0.0.1/tcp/5889\"\n\tsystemctl --user restart \"${SYSTEMD_IPFS_UNIT}\"\n\tsleep 3\n\n\tINFO \"If you use stargz-snapshotter, add the following line to \\\"${XDG_CONFIG_HOME}/containerd-stargz-grpc/config.toml\\\" manually, and then run \\`systemctl --user restart ${SYSTEMD_STARGZ_UNIT}\\`:\"\n\tcat <<-EOT\n\t\t### BEGIN ###\n\t\tipfs = true\n\t\t###  END  ###\n\tEOT\n\tINFO \"If you want to expose the port 4001 of ipfs daemon, re-install rootless containerd with CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS=\\\"--publish=0.0.0.0:4001:4001/tcp\\\" environment variable.\"\n\tINFO \"Set \\`export IPFS_PATH=\\\"${IPFS_PATH}\\\"\\` to use ipfs.\"\n}\n\n# CLI subcommand: \"uninstall\"\ncmd_entrypoint_uninstall() {\n\tinit\n\tuninstall_systemd_unit \"${SYSTEMD_BUILDKIT_UNIT}\"\n\tif [ -n \"${CONTAINERD_NAMESPACE:-}\" ]; then\n\t\tuninstall_systemd_unit \"${CONTAINERD_NAMESPACE}-${SYSTEMD_BUILDKIT_UNIT}\"\n\tfi\n\tuninstall_systemd_unit \"${SYSTEMD_FUSE_OVERLAYFS_UNIT}\"\n\tuninstall_systemd_unit \"${SYSTEMD_CONTAINERD_UNIT}\"\n\tuninstall_systemd_unit \"${SYSTEMD_STARGZ_UNIT}\"\n\tuninstall_systemd_unit \"${SYSTEMD_IPFS_UNIT}\"\n\tuninstall_systemd_unit \"${SYSTEMD_BYPASS4NETNSD_UNIT}\"\n\n\tINFO \"This uninstallation tool does NOT remove containerd binaries and data.\"\n\tINFO \"To remove data, run: \\`$BIN/rootlesskit rm -rf ${XDG_DATA_HOME}/containerd\\`\"\n}\n\n# CLI subcommand: \"uninstall-buildkit\"\ncmd_entrypoint_uninstall_buildkit() {\n\tinit\n\tuninstall_systemd_unit \"${SYSTEMD_BUILDKIT_UNIT}\"\n\tINFO \"This uninstallation tool does NOT remove data.\"\n\tINFO \"To remove data, run: \\`$BIN/rootlesskit rm -rf ${XDG_DATA_HOME}/buildkit\\`\"\n\tif [ -e \"${XDG_CONFIG_HOME}/buildkit/buildkitd.toml\" ]; then\n\t\tINFO \"You may also want to remove the daemon config: \\`rm -f ${XDG_CONFIG_HOME}/buildkit/buildkitd.toml\\`\"\n\tfi\n}\n\n# CLI subcommand: \"uninstall-buildkit-containerd\"\ncmd_entrypoint_uninstall_buildkit_containerd() {\n\tinit\n\tUNIT_NAME=${SYSTEMD_BUILDKIT_UNIT}\n\tBUILDKIT_ROOT=\"${XDG_DATA_HOME}/buildkit\"\n\tif [ -n \"${CONTAINERD_NAMESPACE:-}\" ]; then\n\t\tUNIT_NAME=\"${CONTAINERD_NAMESPACE}-${SYSTEMD_BUILDKIT_UNIT}\"\n\t\tBUILDKIT_ROOT=\"${XDG_DATA_HOME}/buildkit-${CONTAINERD_NAMESPACE}\"\n\tfi\n\tuninstall_systemd_unit \"${UNIT_NAME}\"\n\tINFO \"This uninstallation tool does NOT remove data.\"\n\tINFO \"To remove data, run: \\`$BIN/rootlesskit rm -rf ${BUILDKIT_ROOT}\\`\"\n}\n\n# CLI subcommand: \"uninstall-bypass4netnsd\"\ncmd_entrypoint_uninstall_bypass4netnsd() {\n\tinit\n\tuninstall_systemd_unit \"${SYSTEMD_BYPASS4NETNSD_UNIT}\"\n}\n\n# CLI subcommand: \"uninstall-fuse-overlayfs\"\ncmd_entrypoint_uninstall_fuse_overlayfs() {\n\tinit\n\tuninstall_systemd_unit \"${SYSTEMD_FUSE_OVERLAYFS_UNIT}\"\n\tINFO \"This uninstallation tool does NOT remove data.\"\n\tINFO \"To remove data, run: \\`$BIN/rootlesskit rm -rf ${XDG_DATA_HOME}/containerd-fuse-overlayfs\"\n}\n\n# CLI subcommand: \"uninstall-stargz\"\ncmd_entrypoint_uninstall_stargz() {\n\tinit\n\tuninstall_systemd_unit \"${SYSTEMD_STARGZ_UNIT}\"\n\tINFO \"This uninstallation tool does NOT remove data.\"\n\tINFO \"To remove data, run: \\`$BIN/rootlesskit rm -rf ${XDG_DATA_HOME}/containerd-stargz-grpc\"\n}\n\n# CLI subcommand: \"uninstall-ipfs\"\ncmd_entrypoint_uninstall_ipfs() {\n\tinit\n\tuninstall_systemd_unit \"${SYSTEMD_IPFS_UNIT}\"\n\tINFO \"This uninstallation tool does NOT remove data.\"\n\tINFO \"To remove data, run: \\`$BIN/rootlesskit rm -rf ${XDG_DATA_HOME}/ipfs\"\n}\n\n# text for --help\nusage() {\n\techo \"Usage: ${ARG0} [OPTIONS] COMMAND\"\n\techo\n\techo \"A setup tool for Rootless containerd (${CONTAINERD_ROOTLESS_SH}).\"\n\techo\n\techo \"Commands:\"\n\techo \"  check        Check prerequisites\"\n\techo \"  nsenter      Enter into RootlessKit namespaces (mostly for debugging)\"\n\techo \"  install      Install systemd unit and show how to manage the service\"\n\techo \"  uninstall    Uninstall systemd unit\"\n\techo\n\techo \"Add-on commands (BuildKit):\"\n\techo \"  install-buildkit            Install the systemd unit for BuildKit\"\n\techo \"  uninstall-buildkit          Uninstall the systemd unit for BuildKit\"\n\techo\n\techo \"Add-on commands (bypass4netnsd):\"\n\techo \"  install-bypass4netnsd       Install the systemd unit for bypass4netnsd\"\n\techo \"  uninstall-bypass4netnsd     Uninstall the systemd unit for bypass4netnsd\"\n\techo\n\techo \"Add-on commands (fuse-overlayfs):\"\n\techo \"  install-fuse-overlayfs      Install the systemd unit for fuse-overlayfs snapshotter\"\n\techo \"  uninstall-fuse-overlayfs    Uninstall the systemd unit for fuse-overlayfs snapshotter\"\n\techo\n\techo \"Add-on commands (stargz):\"\n\techo \"  install-stargz              Install the systemd unit for stargz snapshotter\"\n\techo \"  uninstall-stargz            Uninstall the systemd unit for stargz snapshotter\"\n\techo\n\techo \"Add-on commands (ipfs):\"\n\techo \"  install-ipfs [ipfs-daemon-flags...]  Install the systemd unit for ipfs daemon. Specify \\\"--offline\\\" if run the daemon in offline mode. Specify \\\"--init\\\" to initialize IPFS repository as well.\"\n\techo \"  uninstall-ipfs                       Uninstall the systemd unit for ipfs daemon\"\n\techo\n\techo \"Add-on commands (BuildKit containerd worker):\"\n\techo \"  install-buildkit-containerd   Install the systemd unit for BuildKit with CONTAINERD_NAMESPACE=${CONTAINERD_NAMESPACE:-} and CONTAINERD_SNAPSHOTTER=${CONTAINERD_SNAPSHOTTER:-}\"\n\techo \"  uninstall-buildkit-containerd Uninstall the systemd unit for BuildKit with CONTAINERD_NAMESPACE=${CONTAINERD_NAMESPACE:-} and CONTAINERD_SNAPSHOTTER=${CONTAINERD_SNAPSHOTTER:-}\"\n}\n\n# parse CLI args\nif ! args=\"$(getopt -o h --long help -n \"$ARG0\" -- \"$@\")\"; then\n\tusage\n\texit 1\nfi\neval set -- \"$args\"\nwhile [ \"$#\" -gt 0 ]; do\n\targ=\"$1\"\n\tshift\n\tcase \"$arg\" in\n\t-h | --help)\n\t\tusage\n\t\texit 0\n\t\t;;\n\t--)\n\t\tbreak\n\t\t;;\n\t*)\n\t\t# XXX this means we missed something in our \"getopt\" arguments above!\n\t\tERROR \"Scripting error, unknown argument '$arg' when parsing script arguments.\"\n\t\texit 1\n\t\t;;\n\tesac\ndone\n\ncommand=$(echo \"${1:-}\" | sed -e \"s/-/_/g\")\nif [ -z \"$command\" ]; then\n\tERROR \"No command was specified. Run with --help to see the usage. Maybe you want to run \\`$ARG0 install\\`?\"\n\texit 1\nfi\n\nif ! command -v \"cmd_entrypoint_${command}\" >/dev/null 2>&1; then\n\tERROR \"Unknown command: ${command}. Run with --help to see the usage.\"\n\texit 1\nfi\n\n# main\nshift\n\"cmd_entrypoint_${command}\" \"$@\"\n"
  },
  {
    "path": "extras/rootless/containerd-rootless.sh",
    "content": "#!/bin/sh\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# -----------------------------------------------------------------------------\n# Forked from https://github.com/moby/moby/blob/v20.10.3/contrib/dockerd-rootless.sh\n# Copyright The Moby Authors.\n# Licensed under the Apache License, Version 2.0\n# NOTICE: https://github.com/moby/moby/blob/v20.10.3/NOTICE\n# -----------------------------------------------------------------------------\n\n# containerd-rootless.sh executes containerd in rootless mode.\n#\n# Usage: containerd-rootless.sh [CONTAINERD_OPTIONS]\n#\n# External dependencies:\n# * newuidmap and newgidmap needs to be installed.\n# * /etc/subuid and /etc/subgid needs to be configured for the current user.\n# * RootlessKit (>= v0.10.0) needs to be installed. RootlessKit >= v2.0.0 is recommended.\n# * Either one of slirp4netns (>= v0.4.0), VPNKit, lxc-user-nic needs to be installed. slirp4netns >= v1.1.7 is recommended.\n#\n# Recognized environment variables:\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_STATE_DIR=DIR: the rootlesskit state dir. Defaults to \"$XDG_RUNTIME_DIR/containerd-rootless\".\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_NET=(slirp4netns|vpnkit|lxc-user-nic): the rootlesskit network driver. Defaults to \"slirp4netns\" if slirp4netns (>= v0.4.0) is installed. Otherwise defaults to \"vpnkit\".\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_MTU=NUM: the MTU value for the rootlesskit network driver. Defaults to 65520 for slirp4netns, 1500 for other drivers.\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=(builtin|slirp4netns): the rootlesskit port driver. Defaults to \"builtin\".\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX=(auto|true|false): whether to protect slirp4netns with a dedicated mount namespace. Defaults to \"auto\".\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP=(auto|true|false): whether to protect slirp4netns with seccomp. Defaults to \"auto\".\n# * CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS=(auto|true|false): whether to launch rootlesskit with the \"detach-netns\" mode.\n#   Defaults to \"auto\", which is resolved to \"true\" if RootlessKit >= 2.0 is installed.\n#   The \"detached-netns\" mode accelerates `nerdctl (pull|push|build)` and enables `nerdctl run --net=host`,\n#   however, there is a relatively minor drawback with BuildKit prior to v0.13:\n#   the host loopback IP address (127.0.0.1) and abstract sockets are exposed to Dockerfile's \"RUN\" instructions during `nerdctl build` (not `nerdctl run`).\n#   The drawback is fixed in BuildKit v0.13. Upgrading from a prior version of BuildKit needs removing the old systemd unit:\n#   `containerd-rootless-setuptool.sh uninstall-buildkit && rm -f ~/.config/buildkit/buildkitd.toml`\n\n# See also: https://github.com/containerd/nerdctl/blob/main/docs/rootless.md#configuring-rootlesskit\n\nset -e\nif ! [ -w \"$XDG_RUNTIME_DIR\" ]; then\n\techo \"XDG_RUNTIME_DIR needs to be set and writable\"\n\texit 1\nfi\nif ! [ -w \"$HOME\" ]; then\n\techo \"HOME needs to be set and writable\"\n\texit 1\nfi\n: \"${XDG_DATA_HOME:=$HOME/.local/share}\"\n: \"${XDG_CONFIG_HOME:=$HOME/.config}\"\n\nif [ -z \"$_CONTAINERD_ROOTLESS_CHILD\" ]; then\n\tif [ \"$(id -u)\" = \"0\" ]; then\n\t\techo \"Must not run as root\"\n\t\texit 1\n\tfi\n\tcase \"$1\" in\n\t\"check\" | \"install\" | \"uninstall\")\n\t\techo \"Did you mean 'containerd-rootless-setuptool.sh $*' ?\"\n\t\texit 1\n\t\t;;\n\tesac\n\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_STATE_DIR:=$XDG_RUNTIME_DIR/containerd-rootless}\"\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_NET:=}\"\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_MTU:=}\"\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER:=builtin}\"\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX:=auto}\"\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP:=auto}\"\n\t: \"${CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS:=auto}\"\n\tnet=$CONTAINERD_ROOTLESS_ROOTLESSKIT_NET\n\tmtu=$CONTAINERD_ROOTLESS_ROOTLESSKIT_MTU\n\tif [ -z \"$net\" ]; then\n\t\tif command -v slirp4netns >/dev/null 2>&1; then\n\t\t\t# If --netns-type is present in --help, slirp4netns is >= v0.4.0.\n\t\t\tif slirp4netns --help | grep -qw -- --netns-type; then\n\t\t\t\tnet=slirp4netns\n\t\t\t\tif [ -z \"$mtu\" ]; then\n\t\t\t\t\tmtu=65520\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\techo \"slirp4netns found but seems older than v0.4.0. Falling back to VPNKit.\"\n\t\t\tfi\n\t\tfi\n\t\tif [ -z \"$net\" ]; then\n\t\t\tif command -v vpnkit >/dev/null 2>&1; then\n\t\t\t\tnet=vpnkit\n\t\t\telse\n\t\t\t\techo \"Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed\"\n\t\t\t\texit 1\n\t\t\tfi\n\t\tfi\n\tfi\n\tif [ -z \"$mtu\" ]; then\n\t\tmtu=1500\n\tfi\n\n\t_CONTAINERD_ROOTLESS_CHILD=1\n\texport _CONTAINERD_ROOTLESS_CHILD\n\n\t# `selinuxenabled` always returns false in RootlessKit child, so we execute `selinuxenabled` in the parent.\n\t# https://github.com/rootless-containers/rootlesskit/issues/94\n\tif command -v selinuxenabled >/dev/null 2>&1; then\n\t\tif selinuxenabled; then\n\t\t\t_CONTAINERD_ROOTLESS_SELINUX=1\n\t\t\texport _CONTAINERD_ROOTLESS_SELINUX\n\t\tfi\n\tfi\n\n\tcase \"$CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS\" in\n\tauto)\n\t\tif  rootlesskit --help | grep -qw -- \"--detach-netns\"; then\n\t\t\tCONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS=\"--detach-netns $CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS\"\n\t\tfi\n\t\t;;\n\t1 | true)\n\t\tCONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS=\"--detach-netns $CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS\"\n\t\t;;\n\t0 | false)\n\t\t# NOP\n\t\t;;\n\t*)\n\t\techo \"Unknown CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS value: $CONTAINERD_ROOTLESS_ROOTLESSKIT_DETACH_NETNS\"\n\t\texit 1\n\t\t;;\n\tesac\n\n\t# Re-exec the script via RootlessKit, so as to create unprivileged {user,mount,network} namespaces.\n\t#\n\t# --copy-up allows removing/creating files in the directories by creating tmpfs and symlinks\n\t# * /etc:     copy-up is required so as to prevent `/etc/resolv.conf` in the\n\t#             namespace from being unexpectedly unmounted when `/etc/resolv.conf` is recreated on the host\n\t#             (by either systemd-networkd or NetworkManager)\n\t# * /run:     copy-up is required so that we can create /run/containerd (hardcoded) in our namespace\n\t# * /var/lib: copy-up is required so that we can create /var/lib/containerd in our namespace\n\t# shellcheck disable=SC2086\n\texec rootlesskit \\\n\t\t--state-dir=\"$CONTAINERD_ROOTLESS_ROOTLESSKIT_STATE_DIR\" \\\n\t\t--net=\"$net\" --mtu=\"$mtu\" \\\n\t\t--slirp4netns-sandbox=\"$CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SANDBOX\" \\\n\t\t--slirp4netns-seccomp=\"$CONTAINERD_ROOTLESS_ROOTLESSKIT_SLIRP4NETNS_SECCOMP\" \\\n\t\t--disable-host-loopback --port-driver=\"$CONTAINERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER\" \\\n\t\t--copy-up=/etc --copy-up=/run --copy-up=/var/lib \\\n\t\t--propagation=rslave \\\n\t\t$CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS \\\n\t\t\"$0\" \"$@\"\nelse\n\t[ \"$_CONTAINERD_ROOTLESS_CHILD\" = 1 ]\n\t# Remove the *symlinks* for the existing files in the parent namespace if any,\n\t# so that we can create our own files in our mount namespace.\n\t# The actual files in the parent namespace are *not removed* by this rm command.\n\trm -f /run/containerd /run/xtables.lock \\\n\t\t/var/lib/containerd /var/lib/cni /etc/containerd\n\n\t# Bind-mount /etc/ssl.\n\t# Workaround for \"x509: certificate signed by unknown authority\" on openSUSE Tumbleweed.\n\t# https://github.com/rootless-containers/rootlesskit/issues/225\n\trealpath_etc_ssl=$(realpath /etc/ssl)\n\trm -f /etc/ssl\n\tmkdir /etc/ssl\n\tmount --rbind \"${realpath_etc_ssl}\" /etc/ssl\n\n\t# Bind-mount /run/containerd\n\tmkdir -p \"${XDG_RUNTIME_DIR}/containerd\" \"/run/containerd\"\n\tmount --bind \"${XDG_RUNTIME_DIR}/containerd\" \"/run/containerd\"\n\n\t# Bind-mount /var/lib/containerd\n\tmkdir -p \"${XDG_DATA_HOME}/containerd\" \"/var/lib/containerd\"\n\tmount --bind \"${XDG_DATA_HOME}/containerd\" \"/var/lib/containerd\"\n\n\t# Bind-mount /var/lib/cni\n\tmkdir -p \"${XDG_DATA_HOME}/cni\" \"/var/lib/cni\"\n\tmount --bind \"${XDG_DATA_HOME}/cni\" \"/var/lib/cni\"\n\n\t# Bind-mount /etc/containerd\n\tmkdir -p \"${XDG_CONFIG_HOME}/containerd\" \"/etc/containerd\"\n\tmount --bind \"${XDG_CONFIG_HOME}/containerd\" \"/etc/containerd\"\n\n\tif [ -n \"$_CONTAINERD_ROOTLESS_SELINUX\" ]; then\n\t\t# iptables requires /run in the child to be relabeled. The actual /run in the parent is unaffected.\n\t\t# https://github.com/containers/podman/blob/e6fc34b71aa9d876b1218efe90e14f8b912b0603/libpod/networking_linux.go#L396-L401\n\t\t# https://github.com/moby/moby/issues/41230\n\t\tchcon system_u:object_r:iptables_var_run_t:s0 /run\n\tfi\n\n\texec containerd \"$@\"\nfi\n"
  },
  {
    "path": "go.mod",
    "content": "//gomodjail:confined\nmodule github.com/containerd/nerdctl/v2\n\ngo 1.24.3\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.4.0\n\tgithub.com/Microsoft/go-winio v0.6.2\n\tgithub.com/Microsoft/hcsshim v0.14.0-rc.1\n\tgithub.com/compose-spec/compose-go/v2 v2.10.1 //gomodjail:unconfined\n\tgithub.com/containerd/accelerated-container-image v1.4.1\n\tgithub.com/containerd/cgroups/v3 v3.1.3 //gomodjail:unconfined\n\tgithub.com/containerd/console v1.0.5 //gomodjail:unconfined\n\tgithub.com/containerd/containerd/api v1.10.0\n\tgithub.com/containerd/containerd/v2 v2.2.2 //gomodjail:unconfined\n\tgithub.com/containerd/continuity v0.4.5 //gomodjail:unconfined\n\tgithub.com/containerd/errdefs v1.0.0\n\tgithub.com/containerd/fifo v1.1.0 //gomodjail:unconfined\n\tgithub.com/containerd/go-cni v1.1.13 //gomodjail:unconfined\n\tgithub.com/containerd/imgcrypt/v2 v2.0.2 //gomodjail:unconfined\n\tgithub.com/containerd/log v0.1.0\n\tgithub.com/containerd/nerdctl/mod/tigron v0.0.0\n\tgithub.com/containerd/nydus-snapshotter v0.15.11 //gomodjail:unconfined\n\tgithub.com/containerd/platforms v1.0.0-rc.2 //gomodjail:unconfined\n\tgithub.com/containerd/stargz-snapshotter v0.18.1 //gomodjail:unconfined\n\tgithub.com/containerd/stargz-snapshotter/estargz v0.18.1 //gomodjail:unconfined\n\tgithub.com/containerd/stargz-snapshotter/ipfs v0.18.1 //gomodjail:unconfined\n\tgithub.com/containerd/typeurl/v2 v2.2.3\n\tgithub.com/containernetworking/cni v1.3.0 //gomodjail:unconfined\n\tgithub.com/containernetworking/plugins v1.9.0 //gomodjail:unconfined\n\tgithub.com/coreos/go-iptables v0.8.0 //gomodjail:unconfined\n\tgithub.com/coreos/go-systemd/v22 v22.7.0\n\tgithub.com/cyphar/filepath-securejoin v0.6.1 //gomodjail:unconfined\n\tgithub.com/distribution/reference v0.6.0\n\tgithub.com/docker/cli v29.2.1+incompatible //gomodjail:unconfined\n\tgithub.com/docker/docker v28.5.2+incompatible //gomodjail:unconfined\n\tgithub.com/docker/go-connections v0.6.0\n\tgithub.com/docker/go-units v0.5.0\n\tgithub.com/fahedouch/go-logrotate v0.3.0 //gomodjail:unconfined\n\tgithub.com/fatih/color v1.18.0 //gomodjail:unconfined\n\tgithub.com/fluent/fluent-logger-golang v1.10.1\n\tgithub.com/fsnotify/fsnotify v1.9.0 //gomodjail:unconfined\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0\n\tgithub.com/ipfs/go-cid v0.6.0\n\tgithub.com/klauspost/compress v1.18.4\n\tgithub.com/mattn/go-isatty v0.0.20 //gomodjail:unconfined\n\tgithub.com/moby/sys/mount v0.3.4\n\tgithub.com/moby/sys/signal v0.7.1\n\tgithub.com/moby/sys/user v0.4.0 //gomodjail:unconfined\n\tgithub.com/moby/sys/userns v0.1.0 //gomodjail:unconfined\n\tgithub.com/moby/term v0.5.2 //gomodjail:unconfined\n\tgithub.com/muesli/cancelreader v0.2.2 //gomodjail:unconfined\n\tgithub.com/opencontainers/go-digest v1.0.0\n\tgithub.com/opencontainers/image-spec v1.1.1\n\tgithub.com/opencontainers/runtime-spec v1.3.0\n\tgithub.com/pelletier/go-toml/v2 v2.2.4\n\tgithub.com/rootless-containers/bypass4netns v0.4.2 //gomodjail:unconfined\n\tgithub.com/rootless-containers/rootlesskit/v2 v2.3.6 //gomodjail:unconfined\n\tgithub.com/spf13/cobra v1.10.2 //gomodjail:unconfined\n\tgithub.com/spf13/pflag v1.0.10 //gomodjail:unconfined\n\tgithub.com/vishvananda/netlink v1.3.1 //gomodjail:unconfined\n\tgithub.com/vishvananda/netns v0.0.5 //gomodjail:unconfined\n\tgithub.com/yuchanns/srslog v1.1.0\n\tgo.uber.org/mock v0.6.0\n\tgo.yaml.in/yaml/v3 v3.0.4\n\tgolang.org/x/crypto v0.48.0\n\tgolang.org/x/net v0.50.0\n\tgolang.org/x/sync v0.19.0 //gomodjail:unconfined\n\tgolang.org/x/sys v0.41.0 //gomodjail:unconfined\n\tgolang.org/x/term v0.40.0 //gomodjail:unconfined\n\tgolang.org/x/text v0.34.0\n\tgotest.tools/v3 v3.5.2\n\ttags.cncf.io/container-device-interface v1.1.0 //gomodjail:unconfined\n)\n\nrequire (\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/cilium/ebpf v0.16.0 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/containerd/go-runc v1.1.0 // indirect\n\tgithub.com/containerd/plugin v1.0.0 // indirect\n\tgithub.com/containerd/ttrpc v1.2.7 // indirect\n\tgithub.com/containers/ocicrypt v1.2.1 // indirect\n\tgithub.com/creack/pty v1.1.24 // indirect\n\tgithub.com/djherbis/times v1.6.0 // indirect\n\tgithub.com/docker/docker-credential-helpers v0.8.2 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.1.3 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/godbus/dbus/v5 v5.1.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.8 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-shellwords v1.0.12 // indirect\n\tgithub.com/miekg/pkcs11 v1.1.1 // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/locker v1.0.1 // indirect\n\tgithub.com/moby/sys/mountinfo v0.7.2 // indirect\n\tgithub.com/moby/sys/sequential v0.6.0 // indirect\n\tgithub.com/moby/sys/symlink v0.3.0 // indirect\n\tgithub.com/mr-tron/base58 v1.2.0 // indirect\n\tgithub.com/multiformats/go-base32 v0.1.0 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/multiformats/go-multiaddr v0.16.1 // indirect\n\tgithub.com/multiformats/go-multibase v0.2.0 // indirect\n\tgithub.com/multiformats/go-multihash v0.2.3 // indirect\n\tgithub.com/multiformats/go-varint v0.1.0 // indirect\n\tgithub.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 // indirect\n\tgithub.com/opencontainers/selinux v1.13.1 // indirect\n\tgithub.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect\n\tgithub.com/philhofer/fwd v1.2.0 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect\n\tgithub.com/sasha-s/go-deadlock v0.3.5 // indirect\n\t//gomodjail:unconfined\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/smallstep/pkcs7 v0.1.1 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect\n\tgithub.com/tinylib/msgp v1.3.0 // indirect\n\tgithub.com/vbatts/tar-split v0.12.2 // indirect\n\tgithub.com/xhit/go-str2duration/v2 v2.1.0 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect\n\tgo.opentelemetry.io/otel v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.38.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.2 // indirect\n\tgolang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect\n\t//gomodjail:unconfined\n\tgoogle.golang.org/grpc v1.78.0 // indirect\n\t//gomodjail:unconfined\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tlukechampine.com/blake3 v1.3.0 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n\ttags.cncf.io/container-device-interface/specs-go v1.1.0 // indirect\n)\n\nrequire (\n\tcyphar.com/go-pathrs v0.2.1 // indirect\n\tgithub.com/moby/moby/api v1.52.0 // indirect\n\tgithub.com/moby/moby/client v0.1.0 // indirect\n\tgithub.com/moby/sys/capability v0.4.0 // indirect\n\tgo.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect\n)\n\nreplace github.com/containerd/nerdctl/mod/tigron v0.0.0 => ./mod/tigron\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=\ncyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=\ngithub.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ=\ngithub.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=\ngithub.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU=\ngithub.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg=\ngithub.com/containerd/accelerated-container-image v1.4.1 h1:jeZYAaq5pMCeyRZ0I916OjJsEb2TGjAQmfAZyQLi3ec=\ngithub.com/containerd/accelerated-container-image v1.4.1/go.mod h1:rhqPgQ63sgkYHY56pAVl0NBN+lDJYgzgZW9m781nnWg=\ngithub.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ=\ngithub.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=\ngithub.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=\ngithub.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=\ngithub.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o=\ngithub.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=\ngithub.com/containerd/containerd/v2 v2.2.2 h1:mjVQdtfryzT7lOqs5EYUFZm8ioPVjOpkSoG1GJPxEMY=\ngithub.com/containerd/containerd/v2 v2.2.2/go.mod h1:5Jhevmv6/2J+Iu/A2xXAdUIdI5Ah/hfyO7okJ4AFIdY=\ngithub.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=\ngithub.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=\ngithub.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=\ngithub.com/containerd/go-cni v1.1.13 h1:eFSGOKlhoYNxpJ51KRIMHZNlg5UgocXEIEBGkY7Hnis=\ngithub.com/containerd/go-cni v1.1.13/go.mod h1:nTieub0XDRmvCZ9VI/SBG6PyqT95N4FIhxsauF1vSBI=\ngithub.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gGleA=\ngithub.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U=\ngithub.com/containerd/imgcrypt/v2 v2.0.2 h1:WOEaE33CaSxzuRF8YLfAjHWuu1Xh27aPPQtqtALqfuM=\ngithub.com/containerd/imgcrypt/v2 v2.0.2/go.mod h1:8r4JW1b83jkDhaioOUZ7idxIYp+Wn1k4E4KXwy2oSNI=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/containerd/nydus-snapshotter v0.15.11 h1:YTdF4rsjFRsfyaIhnWVUSLz8FqJwOyRZ5FhvFjHh7Uc=\ngithub.com/containerd/nydus-snapshotter v0.15.11/go.mod h1:EWRd/QJ0b6UKHAqYgiV5gHlqLC2qq5cQiSlXEdVovrA=\ngithub.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=\ngithub.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=\ngithub.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=\ngithub.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=\ngithub.com/containerd/stargz-snapshotter v0.18.1 h1:eIkwsafohSWas5YmhxoumrI7elmb2EZJcW8eu7goyOY=\ngithub.com/containerd/stargz-snapshotter v0.18.1/go.mod h1:HPC+XHGIxkjWfAONMvXepQyOs8iGApP2e5A3fOv2TCU=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q=\ngithub.com/containerd/stargz-snapshotter/ipfs v0.18.1 h1:v0kozDNJCW1iVy/1MgD5uZImW87CvkHLzb9L9JwfOco=\ngithub.com/containerd/stargz-snapshotter/ipfs v0.18.1/go.mod h1:qgy0jrKhqtLxn6J5rb9BXZ1Xj1Xeimd0vOi25Zyhpds=\ngithub.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=\ngithub.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=\ngithub.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=\ngithub.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=\ngithub.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo=\ngithub.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4=\ngithub.com/containernetworking/plugins v1.9.0 h1:Mg3SXBdRGkdXyFC4lcwr6u2ZB2SDeL6LC3U+QrEANuQ=\ngithub.com/containernetworking/plugins v1.9.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c=\ngithub.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=\ngithub.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=\ngithub.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=\ngithub.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=\ngithub.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=\ngithub.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=\ngithub.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=\ngithub.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=\ngithub.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=\ngithub.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=\ngithub.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=\ngithub.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=\ngithub.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fahedouch/go-logrotate v0.3.0 h1:XP+dHIDgWZ1ckz43mG6gl5ASer3PZDVr755SVMyzaUQ=\ngithub.com/fahedouch/go-logrotate v0.3.0/go.mod h1:X49m0bvPLkk71MHNCQ1yEfVEw8W/u+qvHa/hOnhCYf4=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fluent/fluent-logger-golang v1.10.1 h1:wu54iN1O2afll5oQrtTjhgZRwWcfOeFFzwRsEkABfFQ=\ngithub.com/fluent/fluent-logger-golang v1.10.1/go.mod h1:qOuXG4ZMrXaSTk12ua+uAb21xfNYOzn0roAtp7mfGAE=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=\ngithub.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=\ngithub.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=\ngithub.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=\ngithub.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30=\ngithub.com/ipfs/go-cid v0.6.0/go.mod h1:NC4kS1LZjzfhK40UGmpXv5/qD2kcMzACYJNntCUiDhQ=\ngithub.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=\ngithub.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=\ngithub.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=\ngithub.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=\ngithub.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=\ngithub.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=\ngithub.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=\ngithub.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=\ngithub.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=\ngithub.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=\ngithub.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=\ngithub.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=\ngithub.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=\ngithub.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=\ngithub.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=\ngithub.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=\ngithub.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=\ngithub.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=\ngithub.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=\ngithub.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=\ngithub.com/moby/sys/mount v0.3.4 h1:yn5jq4STPztkkzSKpZkLcmjue+bZJ0u2AuQY1iNI1Ww=\ngithub.com/moby/sys/mount v0.3.4/go.mod h1:KcQJMbQdJHPlq5lcYT+/CjatWM4PuxKe+XLSVS4J6Os=\ngithub.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=\ngithub.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=\ngithub.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=\ngithub.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=\ngithub.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=\ngithub.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=\ngithub.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=\ngithub.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=\ngithub.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=\ngithub.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=\ngithub.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=\ngithub.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\ngithub.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=\ngithub.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multiaddr v0.16.1 h1:fgJ0Pitow+wWXzN9do+1b8Pyjmo8m5WhGfzpL82MpCw=\ngithub.com/multiformats/go-multiaddr v0.16.1/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=\ngithub.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=\ngithub.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI=\ngithub.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI=\ngithub.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY=\ngithub.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=\ngithub.com/onsi/gomega v1.38.1 h1:FaLA8GlcpXDwsb7m0h2A9ew2aTk3vnZMlzFgg5tz/pk=\ngithub.com/onsi/gomega v1.38.1/go.mod h1:LfcV8wZLvwcYRwPiJysphKAEsmcFnLMK/9c+PjvlX8g=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=\ngithub.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116 h1:tAKu3NkKWZYpqBSOJKwTxT1wIGueiF7gcmcNgr5pNTY=\ngithub.com/opencontainers/runtime-tools v0.9.1-0.20251114084447-edf4cb3d2116/go.mod h1:DKDEfzxvRkoQ6n9TGhxQgg2IM1lY4aM0eaQP4e3oElw=\ngithub.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=\ngithub.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=\ngithub.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=\ngithub.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=\ngithub.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rootless-containers/bypass4netns v0.4.2 h1:JUZcpX7VLRfDkLxBPC6fyNalJGv9MjnjECOilZIvKRc=\ngithub.com/rootless-containers/bypass4netns v0.4.2/go.mod h1:iOY28IeFVqFHnK0qkBCQ3eKzKQgSW5DtlXFQJyJMAQk=\ngithub.com/rootless-containers/rootlesskit/v2 v2.3.6 h1:m/26nAx0DbHZYaM46+uoQjfpu9G77QLzWj2jz25chO8=\ngithub.com/rootless-containers/rootlesskit/v2 v2.3.6/go.mod h1:pv+RESmjRmeUIOsEWOT1f8560CrdaQrDW0YsF4K5kAY=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=\ngithub.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=\ngithub.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=\ngithub.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=\ngithub.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=\ngithub.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=\ngithub.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=\ngithub.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=\ngithub.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=\ngithub.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=\ngithub.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=\ngithub.com/yuchanns/srslog v1.1.0 h1:CEm97Xxxd8XpJThE0gc/XsqUGgPufh5u5MUjC27/KOk=\ngithub.com/yuchanns/srslog v1.1.0/go.mod h1:HsLjdv3XV02C3kgBW2bTyW6i88OQE+VYJZIxrPKPPak=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=\ngo.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=\ngo.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=\ngo.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngo.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=\ngo.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=\ngolang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=\ngolang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=\ngolang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nlukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=\nlukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\npgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=\npgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\ntags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY=\ntags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q=\ntags.cncf.io/container-device-interface/specs-go v1.1.0 h1:QRZVeAceQM+zTZe12eyfuJuuzp524EKYwhmvLd+h+yQ=\ntags.cncf.io/container-device-interface/specs-go v1.1.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ=\n"
  },
  {
    "path": "hack/build-integration-canary.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# shellcheck disable=SC2034,SC2015\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\nroot=\"$(cd \"$(dirname \"${BASH_SOURCE[0]:-$PWD}\")\" 2>/dev/null 1>&2 && pwd)\"\nreadonly root\n# shellcheck source=/dev/null\n. \"$root/scripts/lib.sh\"\n\n######################\n# Definitions\n######################\n\n# \"Blacklisting\" here means that any dependency which name is blacklisted will be left untouched, at the version\n# currently pinned in the Dockerfile.\n# This is convenient so that currently broken alpha/beta/RC can be held back temporarily to keep the build green\n# TODO: Blacklisting gotestsum until a new version compatible with golang v1.25rc1 is released\n# Issue: https://github.com/google/go-licenses/issues/312\nblacklist=(gotestsum)\n\n# List all the repositories we depend on to build and run integration tests\ndependencies=(\n  ktock/buildg\n  moby/buildkit\n  containerd/containerd\n  distribution/distribution\n  containers/fuse-overlayfs\n  containerd/fuse-overlayfs-snapshotter\n  gotestyourself/gotestsum\n  ipfs/kubo\n  containerd/nydus-snapshotter\n  containernetworking/plugins\n  rootless-containers/rootlesskit\n  opencontainers/runc\n  rootless-containers/slirp4netns\n  awslabs/soci-snapshotter\n  containerd/stargz-snapshotter\n  krallin/tini\n)\n\n# Certain dependencies do issue multiple unrelated releaes on their repo - use these below to ignore certain releases\nBUILDKIT_EXCLUDE=\"dockerfile/\"\nCONTAINERD_EXCLUDE=\"containerd API\"\n\n# Some dependencies will be checksum-matched. Setting the variables below will trigger us to download and generate shasums\n# The value you set the variable to also decides which artifacts you are interested in.\nBUILDKIT_CHECKSUM=linux\nCNI_PLUGINS_CHECKSUM=linux\nCONTAINERD_FUSE_OVERLAYFS_CHECKSUM=linux\nFUSE_OVERLAYFS_CHECKSUM=linux\n# Avoids the full build\nBUILDG_CHECKSUM=buildg-v\nROOTLESSKIT_CHECKSUM=linux\nSLIRP4NETNS_CHECKSUM=linux\nSTARGZ_SNAPSHOTTER_CHECKSUM=linux\n# We specifically want the static ones\nTINI_CHECKSUM=static\n\nversion::compare(){\n  local raw_version_fd=\"$1\"\n  local parsed\n  local line\n  while read -r line; do\n    parsed+=(\"$line\")\n  done < <(sed -E 's/^(.* )?v?([0-9]+)[.]([0-9]+)([.]([0-9]+))?(-?([a-z]+)[.]?([0-9]+))?.*/\\2\\n\\3\\n\\5\\n\\7\\n\\8\\n/i' < \"$raw_version_fd\")\n\n  local maj=\"${higher[0]}\"\n  local min=\"${higher[1]}\"\n  local patch=\"${higher[2]}\"\n  local sub=\"${higher[3]}\"\n  local subv=\"${higher[4]}\"\n\n  log::debug \"parsed version: ${parsed[*]}\"\n  log::debug \" > current higher version: ${higher[*]}\"\n\n  if [ \"${parsed[0]}\" -gt \"$maj\" ]; then\n    log::debug \" > new higher\"\n    higher=(\"${parsed[@]}\")\n    return\n  elif [ \"${parsed[0]}\" -lt \"$maj\" ]; then\n    return 1\n  fi\n  if [ \"${parsed[1]}\" -gt \"$min\" ]; then\n    log::debug \" > new higher\"\n    higher=(\"${parsed[@]}\")\n    return\n  elif [ \"${parsed[1]}\" -lt \"$min\" ]; then\n    return 1\n  fi\n  if [ \"${parsed[2]}\" -gt \"$patch\" ]; then\n    log::debug \" > new higher\"\n    higher=(\"${parsed[@]}\")\n    return\n  elif [ \"${parsed[2]}\" -lt \"$patch\" ]; then\n    return 1\n  fi\n  # If the current latest does not have a sub, then it is more recent\n  if [ \"$sub\" == \"\" ]; then\n      return 1\n  fi\n  # If it has a sub, and the parsed one does not, then the parsed one is more recent\n  if [ \"${parsed[3]}\" == \"\" ]; then\n    log::debug \" > new higher\"\n    higher=(\"${parsed[@]}\")\n    return\n  fi\n  # Otherwise, we have two subs. Normalize, then compare\n  # alpha < beta < rc\n  [ \"$sub\" == \"rc\" ] && sub=2 || { [ \"$sub\" == \"beta\" ] && sub=1; } || { [ \"$sub\" == \"alpha\" ] && sub=0; } || {\n    log::error \"Unrecognized sub pattern: $sub\"\n    exit 42\n  }\n  [ \"${parsed[3]}\" == \"rc\" ] && parsed[3]=2 || { [ \"${parsed[3]}\" == \"beta\" ] && parsed[3]=1; } || { [ \"${parsed[3]}\" == \"alpha\" ] && parsed[3]=0; } || {\n    log::error \"Unrecognized sub pattern: ${parsed[3]}\"\n    exit 42\n  }\n  if [ \"${parsed[3]}\" -gt \"$sub\" ]; then\n    log::debug \" > new higher\"\n    higher=(\"${parsed[@]}\")\n    return\n  elif [ \"${parsed[3]}\" -lt \"$sub\" ]; then\n    return 1\n  fi\n  # Ok... we are left with just the sub version\n  if [ \"${parsed[4]}\" -gt \"$subv\" ]; then\n    log::debug \" > new higher\"\n    higher=(\"${parsed[@]}\")\n    return\n  elif [ \"${parsed[4]}\" -lt \"$subv\" ]; then\n    return 1\n  fi\n}\n\n# Retrieves the \"highest version\" release for a given repo\n# Optional argument 2 allows to filter out unwanted release which name matches the argument\n# This is useful for repo that do independent releases for assets (like buildkit dockerfiles)\nlatest::release(){\n  local repo=\"$1\"\n  local ignore=\"${2:-}\"\n  local line\n  local name\n\n  higher=(0 0 0 \"alpha\" 0)\n  higher_data=\n  higher_readable=\n\n  log::info \"Analyzing releases for $repo\"\n\n  while read -r line; do\n    [ ! \"$ignore\" ] || ! grep -q \"$ignore\" <<<\"$line\" || continue\n    # Use tag_name as the canonical version identifier (name is an optional display label and may be empty)\n    name=\"$(echo \"$line\" | jq -rc 'if .name != \"\" then .name else .tag_name end')\"\n    if [ \"$name\" == \"\" ] || [ \"$name\" == null ] ; then\n      log::debug \" > bogus release name ($name) ignored\"\n      continue\n    fi\n    log::debug \" > found release: $name\"\n    if version::compare <(echo \"$name\"); then\n      higher_data=\"$line\"\n      higher_readable=\"$(echo \"$name\" | sed -E 's/(.*[ ])?(v?[0-9][0-9.a-z-]+).*/\\2/')\"\n    fi\n  done < <(github::releases \"$repo\")\n\n  log::info \" >>> latest release detected: $higher_readable\"\n}\n\n# Retrieve the latest git tag for a given repo\nlatest::tag(){\n  local repo=\"$1\"\n\n  log::info \"Analyzing tags for $repo\"\n  github::tags::latest \"$repo\"\n}\n\n# Once a latest release has been retrieved for a given project, you can get the url to the asset matching OS and ARCH\nassets::get(){\n  local os=\"$1\"\n  local arch=\"$2\"\n  local name=\n  local found=\n\n  while read -r line; do\n    name=\"$(echo \"$line\" | jq -rc .name)\"\n    log::debug \" >>> candidate $name\"\n    ! grep -qi \"$os\" <<<\"$name\" || ! grep -qi \"$arch\" <<<\"$name\" || (\n      ! grep -Eqi \"[.]t?g?x?z$\" <<<\"$name\" && grep -Eqi \"[.][a-z]+$\" <<<\"$name\"\n    ) || {\n      found=\"$line\"\n      break\n    }\n  done < <(echo \"$higher_data\" | jq -rc .assets.[])\n  [ \"$found\" == \"\" ] && {\n    log::warning \" >>> no asset found for $os/$arch\"\n  } || {\n    log::info \" >>> found asset for $os/$arch: $(echo \"$found\" | jq -rc .browser_download_url)\"\n    printf \"%s\\n\" \"$(echo \"$found\" | jq -rc .browser_download_url)\"\n  }\n}\n\n######################\n# Script\n######################\n\ncanary::build::integration(){\n  docker_args=(docker build -t test-integration --target test-integration)\n\n  for dep in \"${dependencies[@]}\"; do\n    local bl=\"\"\n    shortname=\"${dep##*/}\"\n    [ \"$shortname\" != \"plugins\" ] || shortname=\"cni-plugins\"\n    [ \"$shortname\" != \"fuse-overlayfs-snapshotter\" ] || shortname=\"containerd-fuse-overlayfs\"\n    for bl in \"${blacklist[@]}\"; do\n      if [ \"$bl\" == \"$shortname\" ]; then\n        log::warning \"Dependency $shortname is blacklisted and will be left to its currently pinned version\"\n        break\n      fi\n    done\n    [ \"$bl\" != \"$shortname\" ] || continue\n\n    shortsafename=\"$(printf \"%s\" \"$shortname\" | tr '[:lower:]' '[:upper:]' | tr '-' '_')\"\n\n    exclusion=\"${shortsafename}_EXCLUDE\"\n    latest::release \"$dep\" \"${!exclusion:-}\"\n\n    # XXX containerd does not display \"v\" in its released versions\n    [ \"${higher_readable:0:1}\" == v ] || higher_readable=\"v$higher_readable\"\n\n    checksum=\"${shortsafename}_CHECKSUM\"\n    if [ \"${!checksum:-}\" != \"\" ]; then\n      # Checksum file\n      checksum_file=./Dockerfile.d/SHA256SUMS.d/\"${shortname}-${higher_readable}\"\n      if [ ! -e \"$checksum_file\" ]; then\n        # Get assets - try first os/arch - fallback on gnu style arch otherwise\n        assets=()\n\n        # Most well behaved go projects will tag with a go os and arch\n        candidate=\"$(assets::get \"${!checksum:-}\" \"amd64\")\"\n        # Then non go projects tend to use gnu style\n        [ \"$candidate\" != \"\" ] || candidate=\"$(assets::get \"\" \"x86_64\")\"\n        # And then some projects which are linux only do not specify the OS\n        [ \"$candidate\" != \"\" ] || candidate=\"$(assets::get \"\" \"amd64\")\"\n        [ \"$candidate\" == \"\" ] || assets+=(\"$candidate\")\n\n        candidate=\"$(assets::get \"${!checksum:-}\" \"arm64\")\"\n        [ \"$candidate\" != \"\" ] || candidate=\"$(assets::get \"\" \"aarch64\")\"\n        [ \"$candidate\" != \"\" ] || candidate=\"$(assets::get \"\" \"arm64\")\"\n        [ \"$candidate\" == \"\" ] || assets+=(\"$candidate\")\n        # Fallback to source if there is nothing else\n\n        [ \"${#assets[@]}\" != 0 ] || candidate=\"$(assets::get \"\" \"source\")\"\n        [ \"$candidate\" == \"\" ] || assets+=(\"$candidate\")\n\n        # XXX very special...\n        if [ \"$shortsafename\" == \"STARGZ_SNAPSHOTTER\" ]; then\n          assets+=(\"https://raw.githubusercontent.com/containerd/stargz-snapshotter/${higher_readable}/script/config/etc/systemd/system/stargz-snapshotter.service\")\n        fi\n\n        # Write the checksum for what we found\n        if [ \"${#assets[@]}\" == 0 ]; then\n          log::error \"No asset found for this checksum-able dependency. Dropping off.\"\n          exit 1\n        fi\n        http::checksum \"${assets[@]}\" > \"$checksum_file\"\n      fi\n    fi\n\n    while read -r line; do\n      # Extract value after \"=\" from a possible dockerfile `ARG XXX_VERSION`, stripping out @ suffixes\n      old_version=$(echo \"$line\" | grep \"ARG ${shortsafename}_VERSION=\") || true\n      old_version=\"${old_version##*=}\"\n      old_version=\"${old_version%%@*}\"\n      [ \"$old_version\" != \"\" ] || continue\n      # If the Dockerfile version does NOT start with a v, adapt to that\n      [ \"${old_version:0:1}\" == \"v\" ] || higher_readable=\"${higher_readable:1}\"\n\n      if [ \"$old_version\" != \"$higher_readable\" ]; then\n        log::warning \"Dependency ${shortsafename} is going to use an updated version $higher_readable (currently: $old_version)\"\n      fi\n    done < ./Dockerfile\n\n    docker_args+=(--build-arg \"${shortsafename}_VERSION=$higher_readable\")\n  done\n\n  hub_available_go_version=\"$(canary::golang::hublatest)\"\n  if [ \"$hub_available_go_version\" != \"\" ]; then\n    docker_args+=(--build-arg \"GO_VERSION=$hub_available_go_version\")\n  fi\n\n  log::debug \"${docker_args[*]} .\"\n  \"${docker_args[@]}\" \".\"\n}\n\n# Hub usually has a delay before available golang version show-up. This method will find the latest available one.\n# See\n# - https://github.com/containerd/nerdctl/issues/3224\n# - https://github.com/containerd/nerdctl/issues/3306\ncanary::golang::hublatest(){\n  local hub_tags\n  local go_version\n  local available_version=\"\"\n  local index\n\n  hub_tags=\"$(http::get /dev/stdout \"https://registry-1.docker.io/v2/library/golang/tags/list\" -H \"Authorization: Bearer $(http::get /dev/stdout \"https://auth.docker.io/token?service=registry.docker.io&scope=repository%3Alibrary%2Fgolang%3Apull\" | jq  -rc .access_token)\")\"\n\n  index=0\n  while [ \"$available_version\" == \"\" ] && [ \"$index\" -lt 5 ]; do\n    go_version=\"$(http::get /dev/stdout \"https://go.dev/dl/?mode=json&include=all\" | jq -rc .[$index].version)\"\n    go_version=\"${go_version##*go}\"\n    available_version=\"$(printf \"%s\" \"$hub_tags\" | jq -rc \".tags[] | select(.==\\\"$go_version\\\")\")\"\n    ((index++))\n  done || true\n\n  printf \"%s\" \"$available_version\"\n}\n"
  },
  {
    "path": "hack/generate-release-note.sh",
    "content": "#!/bin/bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nminimal_amd64tgz=\"$(find _output -name '*linux-amd64.tar.gz*' -and ! -name '*full*')\"\nfull_amd64tgz=\"$(find _output -name '*linux-amd64.tar.gz*' -and -name '*full*')\"\n\nminimal_amd64tgz_basename=\"$(basename \"${minimal_amd64tgz}\")\"\nfull_amd64tgz_basename=\"$(basename \"${full_amd64tgz}\")\"\n\ncat <<-EOX\n## Changes\n(To be documented)\n\n## Compatible containerd versions\nThis release of nerdctl is expected to be used with containerd v1.7, v2.0, v2.1, or v2.2.\nSome features may not work with other releases of containerd.\n\n## About the binaries\n- Minimal (\\`${minimal_amd64tgz_basename}\\`): nerdctl only\n- Full (\\`${full_amd64tgz_basename}\\`):    Includes dependencies such as containerd, runc, and CNI\n\n### Minimal\nExtract the archive to a path like \\`/usr/local/bin\\` or \\`~/bin\\` .\n<details><summary>tar Cxzvvf /usr/local/bin ${minimal_amd64tgz_basename}</summary>\n<p>\n\n\\`\\`\\`\n$(tar tzvf \"${minimal_amd64tgz}\")\n\\`\\`\\`\n</p>\n</details>\n\n### Full\nExtract the archive to a path like \\`/usr/local\\` or \\`~/.local\\` .\n\n<details><summary>tar Cxzvvf /usr/local ${full_amd64tgz_basename}</summary>\n<p>\n\n\\`\\`\\`\n$(tar tzvf \"${full_amd64tgz}\")\n\\`\\`\\`\n</p>\n</details>\n\n<details><summary>Included components</summary>\n<p>\n\nSee \\`share/doc/nerdctl-full/README.md\\`:\n\\`\\`\\`markdown\n$(tar xOzf \"${full_amd64tgz}\" share/doc/nerdctl-full/README.md)\n\\`\\`\\`\n</p>\n</details>\n\n## Quick start\n### Rootful\n\\`\\`\\`console\n$ sudo systemctl enable --now containerd\n$ sudo nerdctl run -d --name nginx -p 80:80 nginx:alpine\n\\`\\`\\`\n\n### Rootless\n\\`\\`\\`console\n$ containerd-rootless-setuptool.sh install\n$ nerdctl run -d --name nginx -p 8080:80 nginx:alpine\n\\`\\`\\`\n\nEnabling cgroup v2 is highly recommended for rootless mode, see https://rootlesscontaine.rs/getting-started/common/cgroup2/ .\nEOX\n"
  },
  {
    "path": "hack/git-checkout-tag-with-hash.sh",
    "content": "#!/bin/sh\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# This script is intended to be usable in other projects too.\n# * Must not have project-specific logics\n# * Must be compatible with bash, dash, and busybox\n\nset -eu\nif [ \"$#\" -ne 1 ]; then\n\techo \"$0: checkout TAG with HASH, and validate it\"\n\techo \"Usage: $0 TAG[@HASH]\"\n\texit 0\nfi\n\n: \"${GIT:=git}\"\nTAG=\"$(echo \"$1\" | cut -d@ -f1)\"\nHASH=\"\"\ncase \"$1\" in\n*@*)\n\tHASH=\"$(echo \"$1\" | cut -d@ -f2)\"\n\t;;\nesac\n\n\"$GIT\" checkout \"$TAG\"\nHEAD=\"$(\"$GIT\" rev-parse HEAD)\"\nif [ -z \"$HASH\" ]; then\n\techo >&2 \"WARNING: ${TAG}: commit hash was not specified (got ${HEAD})\"\nelse\n\tif [ \"$HEAD\" != \"$HASH\" ]; then\n\t\techo >&2 \"ERROR: ${TAG}: expected ${HASH}, got ${HEAD}\"\n\t\texit 1\n\tfi\nfi\n"
  },
  {
    "path": "hack/github/action-helpers.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# shellcheck disable=SC2034\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\n\nreadonly decorator_success=\"✅\"\nreadonly decorator_failure=\"❌\"\n\ngithub::md::h1(){\n  printf \"# %s\\n\" \"$1\"\n}\n\ngithub::md::h2(){\n  printf \"## %s\\n\" \"$1\"\n}\n\ngithub::md::h3(){\n  printf \"### %s\\n\" \"$1\"\n}\n\ngithub::md::bq(){\n  local x\n  for x in \"$@\"; do\n    printf \"> %s\\n\" \"$1\"\n  done\n}\n\ngithub::md::table::header(){\n  printf \"|\"\n  for x in \"$@\"; do\n    printf \" %s |\" \"$x\"\n  done\n  printf \"\\n\"\n  printf \"|\"\n  for x in \"$@\"; do\n    printf \"%s|\" \"---\"\n  done\n  printf \"\\n\"\n}\n\ngithub::md::table::line(){\n  printf \"|\"\n  for x in \"$@\"; do\n    printf \" %s |\" \"$x\"\n  done\n  printf \"\\n\"\n}\n\ngithub::md::pie(){\n  local title=\"$1\"\n  local label\n  local value\n  shift\n\n  printf '```mermaid\\npie\\n    title %s\\n' \"$title\"\n  while [ \"$#\" -gt 0 ]; do\n    label=\"$1\"\n    value=\"$2\"\n    shift\n    shift\n    printf '    \"%s\" : %s\\n' \"$label\" \"$value\"\n  done\n  printf '```\\n\\n'\n\n}\n\ngithub::log::group(){\n  echo \"::group::$*\"\n}\n\ngithub::log::endgroup(){\n  echo \"::endgroup::\"\n}\n\ngithub::log::warning(){\n  local title=\"$1\"\n  local msg=\"$2\"\n\n  echo \"::warning title=$title::$msg\"\n}\n\n_begin=${_begin:-}\n_duration=\ngithub::timer::begin(){\n  _begin=\"$(date +%s)\"\n}\n\ngithub::timer::tick(){\n  local tick\n\n  tick=\"$(date +%s)\"\n  printf \"%s\" \"$((tick - _begin))\"\n}\n\ngithub::timer::format() {\n  local t\n  t=\"$(cat \"$1\")\"\n  local h=$((t/60/60%24))\n  local m=$((t/60%60))\n  local s=$((t%60))\n\n  [[ \"$h\" == 0 ]] || printf \"%d hours \" \"$h\"\n  [[ \"$m\" == 0 ]] || printf \"%d minutes \" \"$m\"\n  printf '%d seconds' \"$s\"\n}\n"
  },
  {
    "path": "hack/github/gotestsum-reporter.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# shellcheck disable=SC2034,SC2015\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\nroot=\"$(cd \"$(dirname \"${BASH_SOURCE[0]:-$PWD}\")\" 2>/dev/null 1>&2 && pwd)\"\nreadonly root\n\n# shellcheck source=/dev/null\n. \"$root\"/action-helpers.sh\n\nGITHUB_STEP_SUMMARY=\"${GITHUB_STEP_SUMMARY:-/dev/null}\"\n\n# Identify consistently failing tests: those that failed but never passed, even on retry.\n# Tests that failed then passed on retry (flaky) are excluded.\nfailing_tests=\"$(jq -rc 'select(.Test) | select(.Action == \"fail\" or .Action == \"pass\") | [.Action, .Test] | @tsv' < \"$GOTESTSUM_JSONFILE\" \\\n  | awk -F'\\t' '\n    $1 == \"fail\" { failed[$2] = 1 }\n    $1 == \"pass\" { passed[$2] = 1 }\n    END {\n      for (t in failed) {\n        if (!(t in passed)) print t\n      }\n    }\n  ' | sort)\"\n\n{\n  github::md::h3 \"Total number of tests: $TESTS_TOTAL\"\n  github::md::pie \"Status\" \"Skipped\" \"$TESTS_SKIPPED\" \"Failed\" \"$TESTS_FAILED\" \"Passed\" \"$(( TESTS_TOTAL - TESTS_FAILED - TESTS_SKIPPED ))\"\n\n  # shellcheck disable=SC2207\n  pie=($(jq -rc 'select(has(\"Test\") | not) | select(.Elapsed) | select(.Elapsed > 0) | \"\\(.Package) \\(.Elapsed) \"' < \"$GOTESTSUM_JSONFILE\"))\n  github::md::pie \"Time spent per package\" \"${pie[@]}\"\n\n  github::md::h3 \"Failing tests\"\n  echo '```'\n  echo \"${failing_tests:-}\"\n  echo '```'\n\n  github::md::h3 \"Tests taking more than 15 seconds\"\n  echo '```'\n  gotestsum tool slowest --threshold 15s --jsonfile \"$GOTESTSUM_JSONFILE\"\n  echo '```'\n} >> \"$GITHUB_STEP_SUMMARY\"\n\n# Print failing tests to stdout so they are visible at the end of the job log.\nif [ -n \"${failing_tests:-}\" ]; then\n  printf '\\n=== Failing tests ===\\n%s\\n=====================\\n' \"$failing_tests\"\n  # Also emit as a GitHub Actions error annotation (visible in PR checks and annotations panel).\n  # GitHub Actions uses %0A for newlines inside annotation messages.\n  encoded=\"${failing_tests//$'\\n'/%0A}\"\n  echo \"::error title=Failing tests::${encoded}\"\nfi\n"
  },
  {
    "path": "hack/provisioning/README.md",
    "content": "# Dependencies provisioning for integration testing\n\nThis folder provides a set of scripts useful (for the CI) to configure hosts for\nthe purpose of testing.\n\nWhile this is agnostic and would (probably) work outside the context of GitHub Actions,\nthis is not the right way for people to install a functioning stack.\nUse provided installation scripts instead (see user documentation).\n\n## Contents\n\n- `/version` allows retrieving latest (or experimental) versions of certain products (golang, containerd, etc)\n- `/linux` allows updating in-place containerd, cni (future: buildkit)\n- `/windows` allows install WinCNI, containerd\n- `/kube` allows spinning-up a Kind cluster"
  },
  {
    "path": "hack/provisioning/gpg/docker",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFit2ioBEADhWpZ8/wvZ6hUTiXOwQHXMAlaFHcPH9hAtr4F1y2+OYdbtMuth\nlqqwp028AqyY+PRfVMtSYMbjuQuu5byyKR01BbqYhuS3jtqQmljZ/bJvXqnmiVXh\n38UuLa+z077PxyxQhu5BbqntTPQMfiyqEiU+BKbq2WmANUKQf+1AmZY/IruOXbnq\nL4C1+gJ8vfmXQt99npCaxEjaNRVYfOS8QcixNzHUYnb6emjlANyEVlZzeqo7XKl7\nUrwV5inawTSzWNvtjEjj4nJL8NsLwscpLPQUhTQ+7BbQXAwAmeHCUTQIvvWXqw0N\ncmhh4HgeQscQHYgOJjjDVfoY5MucvglbIgCqfzAHW9jxmRL4qbMZj+b1XoePEtht\nku4bIQN1X5P07fNWzlgaRL5Z4POXDDZTlIQ/El58j9kp4bnWRCJW0lya+f8ocodo\nvZZ+Doi+fy4D5ZGrL4XEcIQP/Lv5uFyf+kQtl/94VFYVJOleAv8W92KdgDkhTcTD\nG7c0tIkVEKNUq48b3aQ64NOZQW7fVjfoKwEZdOqPE72Pa45jrZzvUFxSpdiNk2tZ\nXYukHjlxxEgBdC/J3cMMNRE1F4NCA3ApfV1Y7/hTeOnmDuDYwr9/obA8t016Yljj\nq5rdkywPf4JF8mXUW5eCN1vAFHxeg9ZWemhBtQmGxXnw9M+z6hWwc6ahmwARAQAB\ntCtEb2NrZXIgUmVsZWFzZSAoQ0UgZGViKSA8ZG9ja2VyQGRvY2tlci5jb20+iQI3\nBBMBCgAhBQJYrefAAhsvBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEI2BgDwO\nv82IsskP/iQZo68flDQmNvn8X5XTd6RRaUH33kXYXquT6NkHJciS7E2gTJmqvMqd\ntI4mNYHCSEYxI5qrcYV5YqX9P6+Ko+vozo4nseUQLPH/ATQ4qL0Zok+1jkag3Lgk\njonyUf9bwtWxFp05HC3GMHPhhcUSexCxQLQvnFWXD2sWLKivHp2fT8QbRGeZ+d3m\n6fqcd5Fu7pxsqm0EUDK5NL+nPIgYhN+auTrhgzhK1CShfGccM/wfRlei9Utz6p9P\nXRKIlWnXtT4qNGZNTN0tR+NLG/6Bqd8OYBaFAUcue/w1VW6JQ2VGYZHnZu9S8LMc\nFYBa5Ig9PxwGQOgq6RDKDbV+PqTQT5EFMeR1mrjckk4DQJjbxeMZbiNMG5kGECA8\ng383P3elhn03WGbEEa4MNc3Z4+7c236QI3xWJfNPdUbXRaAwhy/6rTSFbzwKB0Jm\nebwzQfwjQY6f55MiI/RqDCyuPj3r3jyVRkK86pQKBAJwFHyqj9KaKXMZjfVnowLh\n9svIGfNbGHpucATqREvUHuQbNnqkCx8VVhtYkhDb9fEP2xBu5VvHbR+3nfVhMut5\nG34Ct5RS7Jt6LIfFdtcn8CaSas/l1HbiGeRgc70X/9aYx/V/CEJv0lIe8gP6uDoW\nFPIZ7d6vH+Vro6xuWEGiuMaiznap2KhZmpkgfupyFmplh0s6knymuQINBFit2ioB\nEADneL9S9m4vhU3blaRjVUUyJ7b/qTjcSylvCH5XUE6R2k+ckEZjfAMZPLpO+/tF\nM2JIJMD4SifKuS3xck9KtZGCufGmcwiLQRzeHF7vJUKrLD5RTkNi23ydvWZgPjtx\nQ+DTT1Zcn7BrQFY6FgnRoUVIxwtdw1bMY/89rsFgS5wwuMESd3Q2RYgb7EOFOpnu\nw6da7WakWf4IhnF5nsNYGDVaIHzpiqCl+uTbf1epCjrOlIzkZ3Z3Yk5CM/TiFzPk\nz2lLz89cpD8U+NtCsfagWWfjd2U3jDapgH+7nQnCEWpROtzaKHG6lA3pXdix5zG8\neRc6/0IbUSWvfjKxLLPfNeCS2pCL3IeEI5nothEEYdQH6szpLog79xB9dVnJyKJb\nVfxXnseoYqVrRz2VVbUI5Blwm6B40E3eGVfUQWiux54DspyVMMk41Mx7QJ3iynIa\n1N4ZAqVMAEruyXTRTxc9XW0tYhDMA/1GYvz0EmFpm8LzTHA6sFVtPm/ZlNCX6P1X\nzJwrv7DSQKD6GGlBQUX+OeEJ8tTkkf8QTJSPUdh8P8YxDFS5EOGAvhhpMBYD42kQ\npqXjEC+XcycTvGI7impgv9PDY1RCC1zkBjKPa120rNhv/hkVk/YhuGoajoHyy4h7\nZQopdcMtpN2dgmhEegny9JCSwxfQmQ0zK0g7m6SHiKMwjwARAQABiQQ+BBgBCAAJ\nBQJYrdoqAhsCAikJEI2BgDwOv82IwV0gBBkBCAAGBQJYrdoqAAoJEH6gqcPyc/zY\n1WAP/2wJ+R0gE6qsce3rjaIz58PJmc8goKrir5hnElWhPgbq7cYIsW5qiFyLhkdp\nYcMmhD9mRiPpQn6Ya2w3e3B8zfIVKipbMBnke/ytZ9M7qHmDCcjoiSmwEXN3wKYI\nmD9VHONsl/CG1rU9Isw1jtB5g1YxuBA7M/m36XN6x2u+NtNMDB9P56yc4gfsZVES\nKA9v+yY2/l45L8d/WUkUi0YXomn6hyBGI7JrBLq0CX37GEYP6O9rrKipfz73XfO7\nJIGzOKZlljb/D9RX/g7nRbCn+3EtH7xnk+TK/50euEKw8SMUg147sJTcpQmv6UzZ\ncM4JgL0HbHVCojV4C/plELwMddALOFeYQzTif6sMRPf+3DSj8frbInjChC3yOLy0\n6br92KFom17EIj2CAcoeq7UPhi2oouYBwPxh5ytdehJkoo+sN7RIWua6P2WSmon5\nU888cSylXC0+ADFdgLX9K2zrDVYUG1vo8CX0vzxFBaHwN6Px26fhIT1/hYUHQR1z\nVfNDcyQmXqkOnZvvoMfz/Q0s9BhFJ/zU6AgQbIZE/hm1spsfgvtsD1frZfygXJ9f\nirP+MSAI80xHSf91qSRZOj4Pl3ZJNbq4yYxv0b1pkMqeGdjdCYhLU+LZ4wbQmpCk\nSVe2prlLureigXtmZfkqevRz7FrIZiu9ky8wnCAPwC7/zmS18rgP/17bOtL4/iIz\nQhxAAoAMWVrGyJivSkjhSGx1uCojsWfsTAm11P7jsruIL61ZzMUVE2aM3Pmj5G+W\n9AcZ58Em+1WsVnAXdUR//bMmhyr8wL/G1YO1V3JEJTRdxsSxdYa4deGBBY/Adpsw\n24jxhOJR+lsJpqIUeb999+R8euDhRHG9eFO7DRu6weatUJ6suupoDTRWtr/4yGqe\ndKxV3qQhNLSnaAzqW/1nA3iUB4k7kCaKZxhdhDbClf9P37qaRW467BLCVO/coL3y\nVm50dwdrNtKpMBh3ZpbB1uJvgi9mXtyBOMJ3v8RZeDzFiG8HdCtg9RvIt/AIFoHR\nH3S+U79NT6i0KPzLImDfs8T7RlpyuMc4Ufs8ggyg9v3Ae6cN3eQyxcK3w0cbBwsh\n/nQNfsA6uu+9H7NhbehBMhYnpNZyrHzCmzyXkauwRAqoCbGCNykTRwsur9gS41TQ\nM8ssD1jFheOJf3hODnkKU+HKjvMROl1DK7zdmLdNzA1cvtZH/nCC9KPj1z8QC47S\nxx+dTZSx4ONAhwbS/LN3PoKtn8LPjY9NP9uDWI+TWYquS2U+KHDrBDlsgozDbs/O\njCxcpDzNmXpWQHEtHU7649OXHP7UeNST1mCUCH5qdank0V1iejF6/CfTFU4MfcrG\nYT90qFF93M3v01BbxP+EIY2/9tiIPbrd\n=0YYh\n-----END PGP PUBLIC KEY BLOCK-----\n"
  },
  {
    "path": "hack/provisioning/gpg/hashicorp",
    "content": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGO9u+MBEADmE9i8rpt8xhRqxbzlBG06z3qe+e1DI+SyjscyVVRcGDrEfo+J\nW5UWw0+afey7HFkaKqKqOHVVGSjmh6HO3MskxcpRm/pxRzfni/OcBBuJU2DcGXnG\nnuRZ+ltqBncOuONi6Wf00McTWviLKHRrP6oWwWww7sYF/RbZp5xGmMJ2vnsNhtp3\n8LIMOmY2xv9LeKMh++WcxQDpIeRohmSJyknbjJ0MNlhnezTIPajrs1laLh/IVKVz\n7/Z73UWX+rWI/5g+6yBSEtj368N7iyq+hUvQ/bL00eyg1Gs8nE1xiCmRHdNjMBLX\nlHi0V9fYgg3KVGo6Hi/Is2gUtmip4ZPnThVmB5fD5LzS7Y5joYVjHpwUtMD0V3s1\nHiHAUbTH+OY2JqxZDO9iW8Gl0rCLkfaFDBS2EVLPjo/kq9Sn7vfp2WHffWs1fzeB\nHI6iUl2AjCCotK61nyMR33rNuNcbPbp+17NkDEy80YPDRbABdgb+hQe0o8htEB2t\nCDA3Ev9t2g9IC3VD/jgncCRnPtKP3vhEhlhMo3fUCnJI7XETgbuGntLRHhmGJpTj\nydudopoMWZAU/H9KxJvwlVXiNoBYFvdoxhV7/N+OBQDLMevB8XtPXNQ8ZOEHl22G\nhbL8I1c2SqjEPCa27OIccXwNY+s0A41BseBr44dmu9GoQVhI7TsetpR+qwARAQAB\ntFFIYXNoaUNvcnAgU2VjdXJpdHkgKEhhc2hpQ29ycCBQYWNrYWdlIFNpZ25pbmcp\nIDxzZWN1cml0eStwYWNrYWdpbmdAaGFzaGljb3JwLmNvbT6JAlQEEwEIAD4CGwMF\nCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQR5iuxlTlwVQoyOQu6qFvy8piHnAQUC\nY728PQUJCWYB2gAKCRCqFvy8piHnAd16EADeBtTgkdVEvct40TH/9HKkR/Lc/ohM\nrer6FFHdKmceJ6Ma8/Qm4nCO5C7c4+EPjsUXdhK5w8DSdC5VbKLJDY1EnDlmU5B1\nwSFkGoYKoB8lUn30E77E33MTu2kfrSuF605vetq269CyBwIJV7oNN6311dW8iQ6z\nIytTtlJbVr4YZ7Vst40/uR4myumk9bVBGEd6JhFAPmr/um+BZFhRf9/8xtOryOyB\nGF2d+bc9IoAugpxwv0IowHEqkI4RpK2U9hvxG80sTOcmerOuFbmNyPwnEgtJ6CM1\nbc8WAmObJiQcRSLbcgF+a7+2wqrUbCqRE7QoS2wjd1HpUVPmSdJN925c2uaua2A4\nQCbTEg8kV2HiP0HGXypVNhZJt5ouo0YgR6BSbMlsMHniDQaSIP1LgmEz5xD4UAxO\nY/GRR3LWojGzVzBb0T98jpDgPtOu/NpKx3jhSpE2U9h/VRDiL/Pf7gvEIxPUTKuV\n5D8VqAiXovlk4wSH13Q05d9dIAjuinSlxb4DVr8IL0lmx9DyHehticmJVooHDyJl\nHoA2q2tFnlBBAFbN92662q8Pqi9HbljVRTD1vUjof6ohaoM+5K1C043dmcwZZMTc\n7gV1rbCuxh69rILpjwM1stqgI1ONUIkurKVGZHM6N2AatNKqtBRdGEroQo1aL4+4\nu+DKFrMxOqa5b7kCDQRjvbwTARAA0ut7iKLj9sOcp5kRG/5V+T0Ak2k2GSus7w8e\nkFh468SVCNUgLJpLzc5hBiXACQX6PEnyhLZa8RAG+ehBfPt03GbxW6cK9nx7HRFQ\nGA79H5B4AP3XdEdT1gIL2eaHdQot0mpF2b07GNfADgj99MhpxMCtTdVbBqHY8YEQ\nUq7+E9UCNNs45w5ddq07EDk+o6C3xdJ42fvS2x44uNH6Z6sdApPXLrybeun74C1Z\nOo4Ypre4+xkcw2q2WIhy0Qzeuw+9tn4CYjrhw/+fvvPGUAhtYlFGF6bSebmyua8Q\nMTKhwqHqwJxpjftM3ARdgFkhlH1H+PcmpnVutgTNKGcy+9b/lu/Rjq/47JZ+5VkK\nZtYT/zO1oW5zRklHvB6R/OcSlXGdC0mfReIBcNvuNlLhNcBA9frNdOk3hpJgYDzg\nf8Ykkc+4z8SZ9gA3g0JmDHY1X3SnSadSPyMas3zH5W+16rq9E+MZztR0RWwmpDtg\nFf1XGMmvc+FVEB8dRLKFWSt/E1eIhsK2CRnaR8uotKW/A/gosao0E3mnIygcyLB4\nfnOM3mnTF3CcRumxJvnTEmSDcoKSOpv0xbFgQkRAnVSn/gHkcbVw/ZnvZbXvvseh\n7dstp2ljCs0queKU+Zo22TCzZqXX/AINs/j9Ll67NyIJev445l3+0TWB0kego5Fi\nUVuSWkMAEQEAAYkEcgQYAQgAJhYhBHmK7GVOXBVCjI5C7qoW/LymIecBBQJjvbwT\nAhsCBQkJZgGAAkAJEKoW/LymIecBwXQgBBkBCAAdFiEE6wr14plJaVlvmYc+cG5m\ng2nAhekFAmO9vBMACgkQcG5mg2nAhenPURAAimI0EBZbqpyHpwpbeYq3Pygg1bdo\nIlBQUVoutaN1lR7kqGXwYH+BP6G40x79LwVy/fWV8gO7cDX6D1yeKLNbhnJHPBus\nFJDmzDPbjTlyWlDqJoWMiPqfAOc1A1cHodsUJDUlA01j1rPTho0S9iALX5R50Wa9\nsIenpfe7RVunDwW5gw6y8me7ncl5trD0LM2HURw6nYnLrxePiTAF1MF90jrAhJDV\n+krYqd6IFq5RHKveRtCuTvpL7DlgVCtntmbXLbVC/Fbv6w1xY3A7rXko/03nswAi\nAXHKMP14UutVEcLYDBXbDrvgpb2p2ZUJnujs6cNyx9cOPeuxnke8+ACWvpnWxwjL\nM5u8OckiqzRRobNxQZ1vLxzdovYTwTlUAG7QjIXVvOk9VNp/ERhh0eviZK+1/ezk\nZ8nnPjx+elThQ+r16EM7hD0RDXtOR1VZ0R3OL64AlZYDZz1jEA3lrGhvbjSIfBQk\nT6mxKUsCy3YbElcOyuohmPRgT1iVDIZ/1iPL0Q0HGm4+EsWCdH6fAPB7TlHD8z2D\n7JCFLihFDWs5lrZyuWMO9nryZiVjJrOLPcStgJYVd/MhRHR4hC6g09bgo25RMJ6f\ngyzL4vlEB7aSUih7yjgL9s5DKXP2J71dAhIlF8nnM403R2xEeHyivnyeR/9Ifn7M\nPJvUMUuoG+ZANSMkrw//XA31o//TVk9WsLD1Edxt5XZCoR+fS+Vz8ScLwP1d/vQE\nOW/EWzeMRG15C0td1lfHvwPKvf2MN+WLenp9TGZ7A1kEHIpjKvY51AIkX2kW5QLu\nY3LBb+HGiZ6j7AaU4uYR3kS1+L79v4kyvhhBOgx/8V+b3+2pQIsVOp79ySGvVwpL\nFJ2QUgO15hnlQJrFLRYa0PISKrSWf35KXAy04mjqCYqIGkLsz2qQCY2lGcD5k05z\nbBC4TvxwVxv0ftl2C5Bd0ydl/2YM7GfLrmZmTijK067t4OO+2SROT2oYPDsMtZ6S\nE8vUXvoGpQ8tf5Nkrn2t0zDG3UDtgZY5UVYnZI+xT7WHsCz//8fY3QMvPXAuc33T\nvVdiSfP0aBnZXj6oGs/4Vl1Dmm62XLr13+SMoepMWg2Vt7C8jqKOmhFmSOWyOmRH\nUZJR7nKvTpFnL8atSyFDa4o1bk2U3alOscWS8u8xJ/iMcoONEBhItft6olpMVdzP\nCTrnCAqMjTSPlQU/9EGtp21KQBed2KdAsJBYuPgwaQeyNIvQEOXmINavl58VD72Y\n2T4TFEY8dUiExAYpSodbwBL2fr8DJxOX68WH6e3fF7HwX8LRBjZq0XUwh0KxgHN+\nb9gGXBvgWnJr4NSQGGPiSQVNNHt2ZcBAClYhm+9eC5/VwB+Etg4+1wDmggztiqE=\n=FdUF\n-----END PGP PUBLIC KEY BLOCK-----"
  },
  {
    "path": "hack/provisioning/kube/kind.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\nroot=\"$(cd \"$(dirname \"${BASH_SOURCE[0]:-$PWD}\")\" 2>/dev/null 1>&2 && pwd)\"\nreadonly root\n# shellcheck source=/dev/null\n. \"$root/../../scripts/lib.sh\"\n\nGO_VERSION=1.25\nKIND_VERSION=v0.31.0\nCNI_PLUGINS_VERSION=v1.9.0\n# shellcheck disable=SC2034\nCNI_PLUGINS_SHA_AMD64=58c03705426e929658f45a851df15a86d06ef680cacbf3f2dc127731ca265c28\n# shellcheck disable=SC2034\nCNI_PLUGINS_SHA_ARM64=2596ef56329dd1269026f46b8df262f09ba43c92dbfb940e1e69fbccccd30a29\n\n[ \"$(uname -m)\" == \"aarch64\" ] && GOARCH=arm64 || GOARCH=amd64\n\n_rootful=\n\nconfigure::rootful(){\n  log::debug \"Configuring rootful to: ${1:+true}\"\n  _rootful=\"${1:+true}\"\n}\n\ninstall::kind(){\n  local version=\"$1\"\n  local temp\n  temp=\"$(fs::mktemp \"install\")\"\n\n  http::get \"$temp\"/kind \"https://kind.sigs.k8s.io/dl/$version/kind-linux-${GOARCH:-amd64}\"\n  host::install \"$temp\"/kind\n}\n\n# shellcheck disable=SC2120\ninstall::kubectl(){\n  local version=\"${1:-}\"\n  [ \"$version\" ] || version=\"$(http::get /dev/stdout https://dl.k8s.io/release/stable.txt)\"\n  local temp\n  temp=\"$(fs::mktemp \"install\")\"\n\n  http::get \"$temp\"/kubectl \"https://dl.k8s.io/release/$version/bin/linux/${GOARCH:-amd64}/kubectl\"\n\n  host::install \"$temp\"/kubectl\n}\n\nexec::kind(){\n  local args=()\n  [ ! \"$_rootful\" ] || args=(sudo env PATH=\"$PATH\" KIND_EXPERIMENTAL_PROVIDER=\"$KIND_EXPERIMENTAL_PROVIDER\")\n  args+=(kind)\n\n  log::debug \"${args[*]} $*\"\n  \"${args[@]}\" \"$@\"\n}\n\nexec::nerdctl(){\n  local args=()\n  [ ! \"$_rootful\" ] || args=(sudo env PATH=\"$PATH\")\n  args+=(\"$(pwd)\"/_output/nerdctl)\n\n  log::debug \"${args[*]} $*\"\n  \"${args[@]}\" \"$@\"\n}\n\n# Install dependencies\nmain(){\n  log::info \"Configuring rootful\"\n  configure::rootful \"${ROOTFUL:-}\"\n\n  log::info \"Installing host dependencies if necessary\"\n  host::require make go\n  host::require kind 2>/dev/null || install::kind \"$KIND_VERSION\"\n  host::require kubectl 2>/dev/null || install::kubectl\n\n  # Build nerdctl to use for kind\n  make -f \"$root/../../../Makefile\" binaries\n  PATH=$(pwd)/_output:\"$PATH\"\n  export PATH\n\n  # Add CNI plugins\n  local sha\n  sha=\"CNI_PLUGINS_SHA_$(tr \"[:lower:]\" \"[:upper:]\" <<<\"$GOARCH\")\"\n  # shellcheck source=/dev/null\n  \"$root\"/../linux/cni.sh install \"$CNI_PLUGINS_VERSION\" \"$GOARCH\" \"${!sha}\"\n\n  # Hack to get go into kind control plane\n  exec::nerdctl rm -f go-kind 2>/dev/null || true\n  exec::nerdctl run -d --quiet --name go-kind golang:\"$GO_VERSION\" sleep Inf\n  exec::nerdctl cp go-kind:/usr/local/go /tmp/go\n  exec::nerdctl rm -f go-kind\n\n  # Create fresh cluster\n  log::info \"Creating new cluster\"\n  export KIND_EXPERIMENTAL_PROVIDER=nerdctl\n  exec::kind delete cluster --name nerdctl-test 2>/dev/null || true\n  exec::kind create cluster --name nerdctl-test --config=\"$root\"/kind.yaml\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "hack/provisioning/kube/kind.yaml",
    "content": "# https://pkg.go.dev/sigs.k8s.io/kind/pkg/apis/config/v1alpha4#Cluster\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n  - role: control-plane\n    extraMounts:\n      - hostPath: _output/nerdctl\n        containerPath: /usr/local/bin/nerdctl\n      - hostPath: /tmp/go\n        containerPath: /usr/local/go\n      - hostPath: .\n        containerPath: /nerdctl-source\n      - hostPath: /opt/cni\n        containerPath: /opt/cni\n"
  },
  {
    "path": "hack/provisioning/linux/cni.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\nroot=\"$(cd \"$(dirname \"${BASH_SOURCE[0]:-$PWD}\")\" 2>/dev/null 1>&2 && pwd)\"\nreadonly root\n# shellcheck source=/dev/null\n. \"$root/../../scripts/lib.sh\"\n\nprovision::cni::uninstall(){\n  [ \"$(id -u)\" == 0 ] || {\n    log::error \"You need to be root\"\n    return 1\n  }\n\n  rm -Rf /opt/cni/bin\n}\n\n# provision::containerd::cni will retrieve a specific version of cni plugins and extract it in place on the host\nprovision::cni::install(){\n  local version=\"$1\"\n  local arch=\"$2\"\n  local bin_sha=\"$3\"\n\n  cd \"$(fs::mktemp \"cni-install\")\"\n\n  http::get::secure \\\n    cni.tgz \\\n    https://github.com/containernetworking/plugins/releases/download/\"$version\"/cni-plugins-linux-\"$arch\"-\"$version\".tgz \\\n    \"$bin_sha\"\n\n  sudo mkdir -p /opt/cni/bin\n  sudo tar -C /opt/cni/bin -xzf cni.tgz\n\n  cd - >/dev/null\n}\n\ncom=\"$1\"\nshift\nprovision::cni::\"$com\" \"$@\"\n"
  },
  {
    "path": "hack/provisioning/linux/containerd.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\nroot=\"$(cd \"$(dirname \"${BASH_SOURCE[0]:-$PWD}\")\" 2>/dev/null 1>&2 && pwd)\"\nreadonly root\n# shellcheck source=/dev/null\n. \"$root/../../scripts/lib.sh\"\n\n# provision::containerd::uninstall will ensure deb containerd are purged\nprovision::containerd::uninstall(){\n  [ \"$(id -u)\" == 0 ] || {\n    log::error \"You need to be root\"\n    return 1\n  }\n\n  # Purge deb package\n  apt-get -q purge containerd* 2>/dev/null || true\n  # Remove conf\n  rm -f /etc/containerd/containerd.toml\n  # Remove manually installed containerd if leftover\n  systemctl stop containerd 2>/dev/null || true\n  rm -f /lib/systemd/system/containerd.service\n  systemctl daemon-reload 2>/dev/null || true\n  ! command -v containerd || rm -f \"$(which containerd)\"\n}\n\n# provision::containerd::rootful will retrieve a specific version of containerd and install it on the host.\nprovision::containerd::rootful(){\n  local version=\"$1\"\n  local arch=\"$2\"\n  local bin_sha=\"$3\"\n  local service_sha=\"$4\"\n\n  # Be tolerant with passed versions - with or without leading \"v\"\n  [ \"${version:0:1}\" != \"v\" ] || version=\"${version:1}\"\n\n  cd \"$(fs::mktemp \"containerd-install\")\"\n\n  # Get the binary and install it\n  if [ \"$bin_sha\" == \"canary is volatile and I accept the risk\" ]; then\n    http::get \\\n      containerd.tar.gz \\\n      https://github.com/containerd/containerd/releases/download/v\"$version\"/containerd-\"$version\"-linux-\"$arch\".tar.gz\n  else\n    http::get::secure \\\n      containerd.tar.gz \\\n      https://github.com/containerd/containerd/releases/download/v\"$version\"/containerd-\"$version\"-linux-\"$arch\".tar.gz \\\n      \"$bin_sha\"\n  fi\n\n  sudo tar -C /usr/local -xzf containerd.tar.gz\n\n  # Get the systemd unit\n  http::get::secure \\\n    containerd.service \\\n    https://raw.githubusercontent.com/containerd/containerd/refs/tags/v\"$version\"/containerd.service \\\n    \"$service_sha\"\n\n  sudo cp containerd.service /lib/systemd/system/containerd.service\n\n  # Start it\n  sudo systemctl daemon-reload\n  sudo systemctl start containerd\n\n  cd - >/dev/null || true\n}\n\ncom=\"$1\"\nshift\nprovision::containerd::\"$com\" \"$@\"\n"
  },
  {
    "path": "hack/provisioning/version/fetch.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\n\n# go::canary::for::go-setup retrieves the latest unstable golang version and format it for use by\n# https://github.com/actions/setup-go\n# Note that if the latest unstable is an old RC for the current stable, we print the empty string.\ngo::canary::for::go-setup(){\n  local all_versions\n  local stable_major_minor\n  local canary_major_minor\n  local canary_full_version\n\n  # Get all golang versions\n  all_versions=\"$(curl -fsSL --proto '=https' --tlsv1.2 \"https://go.dev/dl/?mode=json&include=all\")\"\n\n  # Get the latest stable release major.minor for comparison\n  read -r stable_major_minor < \\\n    <(sed -E 's/^go([0-9]+[.][0-9]+).*/\\1/i' \\\n      <(jq -rc 'map(select(.stable==true)).[0].version' <<<\"$all_versions\") \\\n    )\n\n  # Get the latest unstable release major.minor, and full version (formatted for use by go-setup)\n  read -r canary_major_minor canary_full_version < \\\n    <(sed -E 's/^go([0-9]+)[.]([0-9]+)(([a-z]+)([0-9]+))?/\\1.\\2 \\1.\\2.0-\\4.\\5/i' \\\n      <(jq -rc 'map(select(.stable==false)).[0].version' <<<\"$all_versions\") \\\n    )\n\n  # If the latest RC is for the same major.minor as the latest stable one, then there is no canary, return empty string\n  [ \"$canary_major_minor\" != \"$stable_major_minor\" ] || return 0\n\n  # Otherwise, print the full version\n  printf \"%s\" \"$canary_full_version\"\n}\n\n# github::project::latest retrieves the latest tag from a github project\ngithub::project::latest(){\n  local project=\"$1\"\n  local args\n\n  # Get latest\n  args=(curl -fsSL --proto '=https' --tlsv1.2 -H \"Accept: application/vnd.github+json\" -H \"X-GitHub-Api-Version: 2022-11-28\")\n  [ \"${GITHUB_TOKEN:-}\" == \"\" ] && {\n    >&2 printf \"GITHUB_TOKEN is not set - you might face rate limitations with the Github API\\n\"\n  } || args+=(-H \"Authorization: Bearer $GITHUB_TOKEN\")\n\n  \"${args[@]}\" https://api.github.com/repos/\"$project\"/tags | jq -rc .[0].name\n}"
  },
  {
    "path": "hack/provisioning/windows/cni.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# adapted from: https://raw.githubusercontent.com/containerd/containerd/refs/tags/v2.0.3/script/setup/install-cni-windows\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\n\nWINCNI_VERSION=\"${WINCNI_VERSION:-v0.3.1}\"\n\ngit config --global advice.detachedHead false\n\nDESTDIR=\"${DESTDIR:-\"C:\\\\Program Files\\\\containerd\\\\cni\"}\"\nWINCNI_BIN_DIR=\"${DESTDIR}/bin\"\nWINCNI_PKG=github.com/Microsoft/windows-container-networking\n\ngit clone --quiet --depth 1 --branch \"${WINCNI_VERSION}\" \"https://${WINCNI_PKG}.git\" \"${GOPATH}/src/${WINCNI_PKG}\"\ncd \"${GOPATH}/src/${WINCNI_PKG}\"\nmake all\ninstall -D -m 755 \"out/nat.exe\" \"${WINCNI_BIN_DIR}/nat.exe\"\ninstall -D -m 755 \"out/sdnbridge.exe\" \"${WINCNI_BIN_DIR}/sdnbridge.exe\"\ninstall -D -m 755 \"out/sdnoverlay.exe\" \"${WINCNI_BIN_DIR}/sdnoverlay.exe\"\n\nCNI_CONFIG_DIR=\"${DESTDIR}/conf\"\nmkdir -p \"${CNI_CONFIG_DIR}\"\n\n# split_ip splits ip into a 4-element array.\nsplit_ip() {\n  local -r varname=\"$1\"\n  local -r ip=\"$2\"\n  for i in {0..3}; do\n    eval \"$varname\"[\"$i\"]=\"$( echo \"$ip\" | cut -d '.' -f $((i + 1)) )\"\n  done\n}\n\n# subnet gets subnet for a gateway, e.g. 192.168.100.0/24.\ncalculate_subnet() {\n  local -r gateway=\"$1\"\n  local -r prefix_len=\"$2\"\n  split_ip gateway_array \"$gateway\"\n  local len=$prefix_len\n  for i in {0..3}; do\n    if (( len >= 8 )); then\n      mask=255\n    elif (( len > 0 )); then\n      mask=$(( 256 - 2 ** ( 8 - len ) ))\n    else\n      mask=0\n    fi\n    (( len -= 8 ))\n    #shellcheck disable=SC2154\n    result_array[i]=$(( gateway_array[i] & mask ))\n  done\n  result=\"$(printf \".%s\" \"${result_array[@]}\")\"\n  result=\"${result:1}\"\n  echo \"$result/$((32 - prefix_len))\"\n}\n\n# nat already exists on the Windows VM, the subnet and gateway\n# we specify should match that.\n: \"${GATEWAY:=$(powershell -c \"(Get-NetIPAddress -InterfaceAlias 'vEthernet (nat)' -AddressFamily IPv4).IPAddress\")}\"\n: \"${PREFIX_LEN:=$(powershell -c \"(Get-NetIPAddress -InterfaceAlias 'vEthernet (nat)' -AddressFamily IPv4).PrefixLength\")}\"\n\nsubnet=\"$(calculate_subnet \"$GATEWAY\" \"$PREFIX_LEN\")\"\n\n# The \"name\" field in the config is used as the underlying\n# network type right now (see\n# https://github.com/microsoft/windows-container-networking/pull/45),\n# so it must match a network type in:\n# https://docs.microsoft.com/en-us/windows-server/networking/technologies/hcn/hcn-json-document-schemas\nbash -c 'cat >\"'\"${CNI_CONFIG_DIR}\"'\"/0-containerd-nat.conflist <<EOF\n{\n  \"cniVersion\": \"1.0.0\",\n  \"name\": \"nat\",\n  \"plugins\": [\n    {\n      \"type\": \"nat\",\n      \"master\": \"Ethernet\",\n      \"ipam\": {\n        \"subnet\": \"'\"$subnet\"'\",\n        \"routes\": [\n          {\n            \"GW\": \"'\"$GATEWAY\"'\"\n          }\n        ]\n      },\n      \"capabilities\": {\n        \"portMappings\": true,\n        \"dns\": true\n      }\n    }\n  ]\n}\nEOF'"
  },
  {
    "path": "hack/provisioning/windows/containerd.ps1",
    "content": "$ErrorActionPreference = \"Stop\"\n\n#install containerd\n$version=$env:ctrdVersion\necho \"Installing containerd $version\"\ncurl.exe -L https://github.com/containerd/containerd/releases/download/v$version/containerd-$version-windows-amd64.tar.gz -o containerd-windows-amd64.tar.gz\ntar.exe xvf containerd-windows-amd64.tar.gz\nmkdir -force \"$Env:ProgramFiles\\containerd\"\ncp ./bin/* \"$Env:ProgramFiles\\containerd\"\n\n& $Env:ProgramFiles\\containerd\\containerd.exe config default | Out-File \"$Env:ProgramFiles\\containerd\\config.toml\" -Encoding ascii\n& $Env:ProgramFiles\\containerd\\containerd.exe --register-service\nStart-Service containerd\n\necho \"configuration complete! Printing configuration...\"\necho \"Service:\"\nget-service containerd\necho \"cni configuration\"\ncat \"$Env:ProgramFiles\\containerd\\cni\\conf\\0-containerd-nat.conflist\"\nls \"$Env:ProgramFiles\\containerd\\cni\\bin\"\necho \"containerd install\"\nls \"$Env:ProgramFiles\\containerd\\\"\n& \"$Env:ProgramFiles\\containerd\\containerd.exe\" --version\n"
  },
  {
    "path": "hack/scripts/lib.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# shellcheck disable=SC2034,SC2015\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\n\n## This is a library of generic helpers that can be used across different projects\n\n# Simple logger\nreadonly LOG_LEVEL_DEBUG=0\nreadonly LOG_LEVEL_INFO=1\nreadonly LOG_LEVEL_WARNING=2\nreadonly LOG_LEVEL_ERROR=3\n\nreadonly LOG_COLOR_BLACK=0\nreadonly LOG_COLOR_RED=1\nreadonly LOG_COLOR_GREEN=2\nreadonly LOG_COLOR_YELLOW=3\nreadonly LOG_COLOR_BLUE=4\nreadonly LOG_COLOR_MAGENTA=5\nreadonly LOG_COLOR_CYAN=6\nreadonly LOG_COLOR_WHITE=7\nreadonly LOG_COLOR_DEFAULT=9\n\nreadonly LOG_STYLE_DEBUG=( setaf \"$LOG_COLOR_WHITE\" )\nreadonly LOG_STYLE_INFO=( setaf \"$LOG_COLOR_GREEN\" )\nreadonly LOG_STYLE_WARNING=( setaf \"$LOG_COLOR_YELLOW\" )\nreadonly LOG_STYLE_ERROR=( setaf \"$LOG_COLOR_RED\" )\n\n_log::log(){\n  local level\n  local style\n  local numeric_level\n  local message=\"$2\"\n\n  level=\"$(printf \"%s\" \"$1\" | tr '[:lower:]' '[:upper:]')\"\n  numeric_level=\"$(printf \"LOG_LEVEL_%s\" \"$level\")\"\n  style=\"LOG_STYLE_${level}[@]\"\n\n  [ \"${!numeric_level}\" -ge \"$LOG_LEVEL\" ] || return 0\n\n  [ ! \"$TERM\" ] || [ ! -t 2 ] || >&2 tput \"${!style}\" 2>/dev/null || true\n  >&2 printf \"[%s] %s: %s\\n\" \"$(date 2>/dev/null || true)\" \"$(printf \"%s\" \"$level\" | tr '[:lower:]' '[:upper:]')\" \"$message\"\n  [ ! \"$TERM\" ] || [ ! -t 2 ] || >&2 tput op 2>/dev/null || true\n}\n\nlog::init(){\n  local _ll\n  # Default log to warning if unspecified\n  _ll=\"$(printf \"LOG_LEVEL_%s\" \"${LOG_LEVEL:-warning}\" | tr '[:lower:]' '[:upper:]')\"\n  # Default to 3 (warning) if unrecognized\n  LOG_LEVEL=\"${!_ll:-3}\"\n}\n\nlog::debug(){\n  _log::log debug \"$@\"\n}\n\nlog::info(){\n  _log::log info \"$@\"\n}\n\nlog::warning(){\n  _log::log warning \"$@\"\n}\n\nlog::error(){\n  _log::log error \"$@\"\n}\n\n# Helpers\nhost::require(){\n  local binary\n\n  miss=\n  for binary in \"$@\"; do\n    log::debug \"Checking presence of $binary\"\n    command -v \"$binary\" >/dev/null || {\n      miss+=\"$binary\"\n    }\n  done\n\n  [ \"$miss\" == \"\" ] || {\n    log::error \"For this script to work, you need: $miss (could not find them in your path)\"\n    return 1\n  }\n}\n\nhost::install(){\n  local binary\n\n  for binary in \"$@\"; do\n    log::debug \"sudo install -D -m 755 $binary /usr/local/bin/$(basename \"$binary\")\"\n    sudo install -D -m 755 \"$binary\" /usr/local/bin/\"$(basename \"$binary\")\"\n  done\n}\n\nfs::mktemp(){\n  local prefix=\"${1:-temporary}\"\n\n  mktemp -dq \"${TMPDIR:-/tmp}/$prefix.XXXXXX\" 2>/dev/null || mktemp -dq || {\n    log::error \"Failed to create temporary directory\"\n    return 1\n  }\n}\n\ntar::expand(){\n  local dir=\"$1\"\n  local arc=\"$2\"\n\n  log::debug \"tar -C $dir -xzf $arc\"\n  tar -C \"$dir\" -xzf \"$arc\"\n}\n\n_http::get(){\n  local url=\"$1\"\n  local output=\"$2\"\n  local retry=\"$3\"\n  local delay=\"$4\"\n  local user=\"${5:-}\"\n  local password=\"${6:-}\"\n  shift\n  shift\n  shift\n  shift\n  shift\n  shift\n\n  local header\n  local command=(curl -fsSL --retry \"$retry\" --retry-delay \"$delay\" -o \"$output\")\n  # Add a basic auth user if necessary\n  [ \"$user\" == \"\" ] || command+=(--user \"$user:$password\")\n  # Force tls v1.2 and no redirect to http if url scheme is https\n  [ \"${url:0:5}\" != \"https\" ] || command+=(--proto '=https' --tlsv1.2)\n  # Stuff in any additional arguments as headers\n  for header in \"$@\"; do\n    command+=(-H \"$header\")\n  done\n  # Debug\n  log::debug \"${command[*]} $url\"\n  # Exec\n  \"${command[@]}\" \"$url\" || {\n    log::error \"Failed to connect to $url with $retry retries every $delay seconds\"\n    return 1\n  }\n}\n\nhttp::get(){\n  local output=\"$1\"\n  local url=\"$2\"\n  shift\n  shift\n\n  _http::get \"$url\" \"$output\" \"2\" \"1\" \"\" \"\" \"$@\"\n}\n\nhttp::get::secure(){\n  local output=\"$1\"\n  local url=\"$2\"\n  local sha=\"$3\"\n  shift\n  shift\n  shift\n\n  _http::get \"$url\" \"$output\" \"2\" \"1\" \"\" \"\" \"$@\"\n  shasum -a 256 -c <<<\"$sha  $output\" || {\n    ret=$?\n    log::error \"Expected sha: $sha\"\n    log::error \"Actual sha: $(shasum -a 256 \"$output\")\"\n    return $ret\n  }\n}\n\nhttp::healthcheck(){\n  local url=\"$1\"\n  local retry=\"${2:-5}\"\n  local delay=\"${3:-1}\"\n  local user=\"${4:-}\"\n  local password=\"${5:-}\"\n  shift\n  shift\n  shift\n  shift\n  shift\n\n  _http::get \"$url\" /dev/null \"$retry\" \"$delay\" \"$user\" \"$password\" \"$@\"\n}\n\nhttp::checksum(){\n  local urls=(\"$@\")\n  local url\n\n  local temp\n  temp=\"$(fs::mktemp \"http-checksum\")\"\n\n  for url in \"${urls[@]}\"; do\n    http::get \"$temp/${url##*/}\" \"$url\"\n  done\n\n  cd \"$temp\"\n  shasum -a 256 ./*\n  cd - >/dev/null || true\n}\n\n# Github API helpers\n# Set GITHUB_TOKEN to use authenticated requests to workaround limitations\n\ngithub::settoken(){\n  local token=\"$1\"\n  # If passed token is a github action pattern replace, and we are NOT on github, ignore it\n  # shellcheck disable=SC2016\n  [ \"${token:0:3}\" == '${{' ] || GITHUB_TOKEN=\"$token\"\n}\n\ngithub::request(){\n  local accept=\"$1\"\n  local endpoint=\"$2\"\n  local args=(\n    \"Accept: $accept\"\n    \"X-GitHub-Api-Version: 2022-11-28\"\n  )\n\n  [ \"${GITHUB_TOKEN:-}\" == \"\" ] || args+=(\"Authorization: Bearer $GITHUB_TOKEN\")\n\n  http::get /dev/stdout https://api.github.com/\"$endpoint\" \"${args[@]}\"\n}\n\ngithub::file(){\n  local repo=\"$1\"\n  local path=\"$2\"\n  local ref=\"${3:-main}\"\n  github::request \"application/vnd.github.v3.raw\" \"repos/$repo/contents/$path?ref=$ref\"\n}\n\ngithub::tags::latest(){\n  local repo=\"$1\"\n  github::request \"application/vnd.github+json\" \"repos/$repo/tags\" | jq -rc .[0].name\n}\n\ngithub::releases(){\n  local repo=\"$1\"\n  github::request \"application/vnd.github+json\" \"repos/$repo/releases\" |\n    jq -rc .[]\n}\n\ngithub::releases::latest(){\n  local repo=\"$1\"\n  github::request \"application/vnd.github+json\" \"repos/$repo/releases/latest\" | jq -rc .\n}\n\nlog::init\nhost::require jq tar curl shasum\n\n[[ \"${1:-}\" != \"github\"* ]] || \"$@\"\n"
  },
  {
    "path": "hack/test-integration.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# shellcheck disable=SC2034,SC2015\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\nroot=\"$(cd \"$(dirname \"${BASH_SOURCE[0]:-$PWD}\")\" 2>/dev/null 1>&2 && pwd)\"\nreadonly root\n\nif [[ \"$(id -u)\" = \"0\" ]]; then\n  # Ensure securityfs is mounted for apparmor to work\n  if ! mountpoint -q /sys/kernel/security; then\n    mount -tsecurityfs securityfs /sys/kernel/security\n  fi\nfi\n\nreadonly timeout=\"30m\"\nreadonly retries=\"2\"\nreadonly needsudo=\"${WITH_SUDO:-}\"\n\n# See https://github.com/containerd/nerdctl/blob/main/docs/testing/README.md#about-parallelization\nargs=(--format=testname --jsonfile /tmp/test-integration.log --packages=\"$root\"/../cmd/nerdctl/...)\n# FIXME: not working on windows. Need to change approach: move away from --post-run-command and\n# just process the log file. This might also allow multi-steps/multi-target results aggregation.\n[ \"$(uname -s)\" != \"Linux\" ] || args+=(--post-run-command \"$root\"/github/gotestsum-reporter.sh)\n\nif [ \"$#\" == 0 ]; then\n  \"$root\"/test-integration.sh -test.only-flaky=false\n  \"$root\"/test-integration.sh -test.only-flaky=true\n  exit\nfi\n\nfor arg in \"$@\"; do\n  if [ \"$arg\" == \"-test.only-flaky=true\" ] || [ \"$arg\" == \"-test.only-flaky\" ]; then\n    args+=(\"--rerun-fails=$retries\")\n    break\n  fi\ndone\n\nif [ \"$needsudo\" == \"true\" ] || [ \"$needsudo\" == \"yes\" ] || [ \"$needsudo\" == \"1\" ]; then\n  gotestsum \"${args[@]}\" -- -timeout=\"$timeout\" -p 1 -exec sudo -args -test.allow-kill-daemon \"$@\"\nelse\n  gotestsum \"${args[@]}\" -- -timeout=\"$timeout\" -p 1 -args -test.allow-kill-daemon \"$@\"\nfi\n"
  },
  {
    "path": "mod/tigron/.golangci.yml",
    "content": "version: \"2\"\n\nrun:\n  issues-exit-code: 2\n  modules-download-mode: readonly\n  allow-parallel-runners: true\n  allow-serial-runners: true\n\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\n\nlinters:\n  default: all\n  enable:\n    # These are the default set of golangci (errcheck is disabled, see below)\n    - govet             # Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes.\n    - ineffassign       # Detects when assignments to existing variables are not used.\n    - staticcheck       # It's the set of rules from staticcheck.\n    - unused            # Checks Go code for unused constants, variables, functions and types.\n    # These are the linters we knowingly want enabled in addition to the default set\n    - containedctx      # avoid embedding context into structs\n    - depguard          # Allows to explicitly allow or disallow third party modules\n    - err113            # encourage static errors\n    - gochecknoglobals  # globals should be avoided as much as possible\n    - godot             # forces dot at the end of comments\n    - gosec             # various security checks\n    - interfacebloat    # limit complexity in public APIs\n    - paralleltest      # enforces tests using parallel\n    - revive            # meta linter (see settings below)\n    - testpackage       # test packages should be separate from the package they test (eg: name them package_test)\n    - testableexamples  # makes sure that examples are testable (have an expected output)\n    - thelper           # enforces use of t.Helper()\n    - varnamelen        # encourage readable descriptive names for variables instead of x, y, z\n  disable:\n    # These are the linters that we know we do not want\n    - cyclop            # provided by revive\n    - exhaustruct       # does not serve much of a purpose\n    - errcheck          # provided by revive\n    - errchkjson        # forces handling of json err (eg: prevents _), which is too much\n    - forcetypeassert   # provided by revive\n    - funlen            # provided by revive\n    - gocognit          # provided by revive\n    - goconst           # provided by revive\n    - godox             # not helpful unless we could downgrade it to warning / info\n    - ginkgolinter      # no ginkgo\n    - gomodguard        # we use depguard instead\n    - ireturn           # too annoying with not enough value\n    - lll               # provided by golines\n    - nestif            # already provided ten different ways with revive cognitive complexity, etc\n    - nonamedreturns    # named returns are occasionally useful\n    - prealloc          # premature optimization\n    - promlinter        # no prometheus\n    - sloglint          # no slog\n    - testifylint       # no testify\n    - zerologlint       # no zerolog\n    - funcorder\n    - noctx\n    - noinlineerr\n    - wsl_v5\n  settings:\n    interfacebloat:\n      # Default is 10\n      max: 20\n    revive:\n      enable-all-rules: true\n      rules:\n        - name: max-public-structs\n          # Default is 5\n          arguments: 7\n        - name: cognitive-complexity\n          # Default is 7\n          arguments: [100]\n        - name: function-length\n          # Default is 50, 75\n          arguments: [80, 220]\n        - name: cyclomatic\n          # Default is 10\n          arguments: [30]\n        - name: add-constant\n          arguments:\n            - allowInts: \"0,1,2\"\n              allowStrs: '\"\"'\n        - name: flag-parameter\n          # Not sure why this is valuable.\n          disabled: true\n        - name: line-length-limit\n          # Formatter `golines` takes care of this.\n          disabled: true\n        - name: unhandled-error\n          arguments:\n            - \"fmt.Print\"\n            - \"fmt.Println\"\n            - \"fmt.Printf\"\n            - \"fmt.Fprint\"\n            - \"fmt.Fprintln\"\n            - \"fmt.Fprintf\"\n        - name: redundant-test-main-exit\n          disabled: true\n        - name: enforce-switch-style\n          disabled: true\n        - name: var-naming\n          disabled: true\n    depguard:\n      rules:\n        main:\n          files:\n            - $all\n          allow:\n            # Allowing go standard library and tigron itself\n            - $gostd\n            - github.com/containerd/nerdctl/mod/tigron\n            # We use creack as our base for pty\n            - github.com/creack/pty\n            # Used for display width computation in internal/formatter\n            - golang.org/x/text/width\n            # errgroups and term (make raw) are used by internal/pipes\n            - golang.org/x/sync\n            - golang.org/x/term\n            # EXPERIMENTAL: for go routines leakage detection\n            - go.uber.org/goleak\n    staticcheck:\n      checks:\n        - all\n  exclusions:\n    generated: disable\n\nformatters:\n  settings:\n    gci:\n      sections:\n        - standard\n        - default\n        - prefix(github.com/containerd)\n        - localmodule\n      no-inline-comments: true\n      no-prefix-comments: true\n      custom-order: true\n    gofumpt:\n      extra-rules: true\n    golines:\n      max-len: 120\n      tab-len: 4\n      shorten-comments: true\n  enable:\n    - gci\n    - gofumpt\n    - golines\n  exclusions:\n    generated: disable\n"
  },
  {
    "path": "mod/tigron/.yamllint",
    "content": "---\n\nextends: default\n\nrules:\n  indentation:\n    spaces: 2\n    indent-sequences: consistent\n  truthy:\n    allowed-values: ['true', 'false', 'on', 'off']\n  comments-indentation: disable\n  document-start: disable\n  line-length: disable\n"
  },
  {
    "path": "mod/tigron/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "mod/tigron/Makefile",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# project-checks is broken.\n# See https://github.com/containerd/nerdctl/pull/3889\n\n##########################\n# Configuration\n##########################\nORG_PREFIXES := \"github.com/containerd\"\n\nMAKEFILE_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))\nVERSION ?= $(shell git -C $(MAKEFILE_DIR) describe --match 'v[0-9]*' --dirty='.m' --always --tags)\nVERSION_TRIMMED := $(VERSION:v%=%)\nREVISION ?= $(shell git -C $(MAKEFILE_DIR) rev-parse HEAD)$(shell if ! git -C $(MAKEFILE_DIR) diff --no-ext-diff --quiet --exit-code; then echo .m; fi)\nLINT_COMMIT_RANGE ?= main..HEAD\n\n##########################\n# Helpers\n##########################\nifdef VERBOSE\n\tVERBOSE_FLAG := -v\n\tVERBOSE_FLAG_LONG := --verbose\nendif\n\nifndef NO_COLOR\n    NC := \\033[0m\n    GREEN := \\033[1;32m\n    ORANGE := \\033[1;33m\nendif\n\n# Helpers\nrecursive_wildcard=$(wildcard $1$2) $(foreach e,$(wildcard $1*),$(call recursive_wildcard,$e/,$2))\n\ndefine title\n\t@printf \"$(GREEN)____________________________________________________________________________________________________\\n\"\n\t@printf \"$(GREEN)%*s\\n\" $$(( ( $(shell echo \"🐯$(1) 🐯\" | wc -c ) + 100 ) / 2 )) \"🐯$(1) 🐯\"\n\t@printf \"$(GREEN)____________________________________________________________________________________________________\\n$(ORANGE)\"\nendef\n\ndefine footer\n\t@printf \"$(GREEN)> %s: done!\\n\" \"$(1)\"\n\t@printf \"$(GREEN)____________________________________________________________________________________________________\\n$(NC)\"\nendef\n\n##########################\n# High-level tasks definitions\n##########################\nlint: lint-go-all lint-yaml lint-shell lint-commits lint-headers lint-mod lint-licenses-all\ntest: test-unit test-unit-race test-unit-bench\nunit: test-unit test-unit-race test-unit-bench\nfix: fix-mod fix-go-all\n\n##########################\n# Linting tasks\n##########################\nlint-go:\n\t$(call title, $@: $(GOOS))\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& golangci-lint run $(VERBOSE_FLAG_LONG) ./...\n\t$(call footer, $@)\n\nlint-go-all:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& GOOS=darwin make lint-go \\\n\t\t&& GOOS=freebsd make lint-go \\\n\t\t&& GOOS=linux make lint-go \\\n\t\t&& GOOS=windows make lint-go\n\t$(call footer, $@)\n\nlint-yaml:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& yamllint .\n\t$(call footer, $@)\n\nlint-shell: $(call recursive_wildcard,$(MAKEFILE_DIR)/,*.sh)\n\t$(call title, $@)\n\t@shellcheck -a -x $^\n\t$(call footer, $@)\n\nlint-commits:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& git-validation $(VERBOSE_FLAG) -run DCO,short-subject,dangling-whitespace -range \"$(LINT_COMMIT_RANGE)\"\n\t$(call footer, $@)\n\nlint-headers:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& ltag -t \"./hack/headers\" --check -v\n\t$(call footer, $@)\n\nlint-mod:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go mod tidy --diff\n\t$(call footer, $@)\n\n# FIXME: go-licenses cannot find LICENSE from root of repo when submodule is imported:\n# https://github.com/google/go-licenses/issues/186\n# This is impacting gotest.tools\nlint-licenses:\n\t$(call title, $@: $(GOOS))\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go-licenses check --include_tests --allowed_licenses=Apache-2.0,BSD-2-Clause,BSD-3-Clause,MIT,MPL-2.0 \\\n\t\t  --ignore gotest.tools \\\n\t\t  ./...\n\t$(call footer, $@)\n\nlint-licenses-all:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& GOOS=darwin make lint-licenses \\\n\t\t&& GOOS=freebsd make lint-licenses \\\n\t\t&& GOOS=linux make lint-licenses \\\n\t\t&& GOOS=windows make lint-licenses\n\t$(call footer, $@)\n\n##########################\n# Automated fixing tasks\n##########################\nfix-go:\n\t$(call title, $@: $(GOOS))\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& golangci-lint run --fix\n\t$(call footer, $@)\n\nfix-go-all:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& GOOS=darwin make fix-go \\\n\t\t&& GOOS=freebsd make fix-go \\\n\t\t&& GOOS=linux make fix-go \\\n\t\t&& GOOS=windows make fix-go\n\t$(call footer, $@)\n\nfix-mod:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go mod tidy\n\t$(call footer, $@)\n\nup:\n\t$(call title, $@)\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go get -u ./...\n\t$(call footer, $@)\n\n##########################\n# Development tools installation\n##########################\ninstall-dev-tools:\n\t$(call title, $@)\n\t# golangci: v2.4.0 (2025-08-14)\n\t# git-validation: main (2025-02-25)\n\t# ltag: main (2025-03-04)\n\t# go-licenses: v2.0.0-alpha.1 (2024-06-27)\n\t# stubbing go-licenses with dependency upgrade due to non-compatibility with golang 1.25rc1\n\t# Issue: https://github.com/google/go-licenses/issues/312\n\t@cd $(MAKEFILE_DIR) \\\n\t\t&& go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@43d03392d7dc3746fa776dbddd66dfcccff70651 \\\n\t\t&& go install github.com/vbatts/git-validation@7b60e35b055dd2eab5844202ffffad51d9c93922 \\\n\t\t&& go install github.com/containerd/ltag@66e6a514664ee2d11a470735519fa22b1a9eaabd \\\n\t\t&& go install github.com/Shubhranshu153/go-licenses/v2@f8c503d1357dffb6c97ed3b94e912ab294dde24a\n\t@echo \"Remember to add \\$$HOME/go/bin to your path\"\n\t$(call footer, $@)\n\n##########################\n# Testing tasks\n##########################\ntest-unit:\n\t$(call title, $@)\n\t@HIGHK_EXPERIMENTAL_FD=true go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/...\n\t$(call footer, $@)\n\ntest-unit-bench:\n\t$(call title, $@)\n\t@go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/... -bench=.\n\t$(call footer, $@)\n\ntest-unit-race:\n\t$(call title, $@)\n\t@HIGHK_EXPERIMENTAL_FD=true CGO_ENABLED=1 go test $(VERBOSE_FLAG) $(MAKEFILE_DIR)/... -race\n\t$(call footer, $@)\n\n.PHONY: \\\n\tlint \\\n\tfix \\\n\ttest \\\n\tup \\\n\tunit \\\n\tinstall-dev-tools \\\n\tlint-commits lint-go lint-go-all lint-headers lint-licenses lint-licenses-all lint-mod lint-shell lint-yaml \\\n\tfix-go fix-go-all fix-mod \\\n\ttest-unit test-unit-race test-unit-bench"
  },
  {
    "path": "mod/tigron/README.md",
    "content": "# Tigron test framework\n\n>  no-one likes you, `if [ $? -eq 0 ]`\n\nA modern testing framework for command-line applications.\n\n## TL;DR\n\nTBD\n\n## Documentation\n\nTBD\n\nFor now, see [nerdctl-specific testing documentation](https://github.com/containerd/nerdctl/blob/main/docs/testing/tools.md).\n\n## Motivation and goals\n\nTesting (go) binaries is a journey fraught with many pitfalls.\n\nWhile some tooling exist (the venerable [bats](https://github.com/bats-core/bats-core), or the solid work going\non at [gotestyourself](https://github.com/gotestyourself)), they either focus on relatively low-level testing\nprimitives (`assert`, `exec`), or do not integrate well into the natural go environment\n(`bats` requires you to write shell scripts), and routinely require additional third-party tools for advanced scenarios\n(hello `unbuffer`), and for the developer to write a large set of \"helpers\".\n\nProjects and companies thus routinely end-up growing in-house tooling, that generally suffer from a number of\nrampant issues: lack of structure and expressiveness, helpers spaghetti, unclear test lifecycle (specifically\ncleanup), resource leakage and cross test interaction, ultimately encouraging bad test design leading to degraded\nand un-scalable situations (flakyness being of course the number 1 scourge).\n\nTigron was developed specifically to address these issues, based on the experience testing nerdctl, a large cli\nwith a lot of integration tests.\n\nTigron does not replace `gotest.tools`, nor `gotestsum`. In fact, it leverages and encourages use of these where\nappropriate.\n\nTigron ambition is to provide a ready-to-use, clean, simple, go-native framework meant specifically to\nwrite tests for cli binaries, encouraging good test design and a stronger basis to build tests suite.\nIt also comes with a set of helpers to accomodate most advanced scenarios (command backgrounding, stdin manipulation,\nsupport for pseudo ttys, environment filtering, etc.)\n\n## Hack\n\n### Initial setup\n\nClone, then:\n\n```\n./hack/dev-setup-linux.sh\n# Or\n# ./hack/dev-setup-macos.sh\n\nmake install-dev-tools\n```\n\n### Work\n\n```\n# Update dependencies\nmake up\n```\n\n```\n# Re-order imports, gofmt, go mod tidy, etc\nmake fix\n```\n\n```\n# Ensure linters are happy\nmake lint\n```\n\n```\n# Run tests\nmake test\n```\n"
  },
  {
    "path": "mod/tigron/expect/comparators.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:package-comments // annoying false positive behavior\n\npackage expect\n\nimport (\n\t\"encoding/json\"\n\t\"regexp\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\n// All can be used as a parameter for expected.Output to group a set of comparators.\nfunc All(comparators ...test.Comparator) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\tt.Helper()\n\n\t\tfor _, comparator := range comparators {\n\t\t\tcomparator(stdout, t)\n\t\t}\n\t}\n}\n\n// Contains can be used as a parameter for expected.Output and ensures a comparison string is found contained in the\n// output.\nfunc Contains(compare string, more ...string) test.Comparator {\n\treturn func(stdout string, testing tig.T) {\n\t\ttesting.Helper()\n\n\t\tassertive.Contains(assertive.WithFailLater(testing), stdout, compare, \"Inspecting output (contains)\")\n\n\t\tfor _, m := range more {\n\t\t\tassertive.Contains(assertive.WithFailLater(testing), stdout, m, \"Inspecting output (contains)\")\n\t\t}\n\t}\n}\n\n// DoesNotContain is to be used for expected.Output to ensure a comparison string is NOT found in the output.\nfunc DoesNotContain(compare string, more ...string) test.Comparator {\n\treturn func(stdout string, testing tig.T) {\n\t\ttesting.Helper()\n\n\t\tassertive.DoesNotContain(\n\t\t\tassertive.WithFailLater(testing),\n\t\t\tstdout,\n\t\t\tcompare,\n\t\t\t\"Inspecting output (does not contain)\",\n\t\t)\n\n\t\tfor _, m := range more {\n\t\t\tassertive.DoesNotContain(\n\t\t\t\tassertive.WithFailLater(testing),\n\t\t\t\tstdout,\n\t\t\t\tm,\n\t\t\t\t\"Inspecting output (does not contain)\",\n\t\t\t)\n\t\t}\n\t}\n}\n\n// Equals is to be used for expected.Output to ensure it is exactly the output.\nfunc Equals(compare string) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\tt.Helper()\n\t\tassertive.IsEqual(assertive.WithFailLater(t), stdout, compare, \"Inspecting output (equals)\")\n\t}\n}\n\n// Match is to be used for expected.Output to ensure we match a regexp.\nfunc Match(reg *regexp.Regexp) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\tt.Helper()\n\t\tassertive.Match(assertive.WithFailLater(t), stdout, reg, \"Inspecting output (match)\")\n\t}\n}\n\n// DoesNotMatch returns a comparator verifying the output does not match the provided regexp.\nfunc DoesNotMatch(reg *regexp.Regexp) test.Comparator {\n\treturn func(stdout string, t tig.T) {\n\t\tt.Helper()\n\t\tassertive.DoesNotMatch(assertive.WithFailLater(t), stdout, reg, \"Inspecting output (!match)\")\n\t}\n}\n\n// JSON allows to verify that the output can be marshalled into T, and optionally can be further verified by a provided\n// method.\nfunc JSON[T any](obj T, verifier func(T, tig.T)) test.Comparator {\n\treturn func(stdout string, testing tig.T) {\n\t\ttesting.Helper()\n\n\t\terr := json.Unmarshal([]byte(stdout), &obj)\n\t\tassertive.ErrorIsNil(assertive.WithSilentSuccess(testing), err, \"Unmarshalling JSON from stdout must succeed\")\n\n\t\tif verifier != nil && err == nil {\n\t\t\tverifier(obj, testing)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/expect/comparators_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:add-constant\npackage expect_test\n\n// TODO: add a lot more tests including failure conditions with mimicry\n\nimport (\n\t\"encoding/json\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nfunc TestExpect(t *testing.T) {\n\t// TODO: write more tests once we can mock t in Comparator signature\n\tt.Parallel()\n\n\texpect.Contains(\"b\")(\"a b c\", t)\n\texpect.DoesNotContain(\"d\")(\"a b c\", t)\n\texpect.Equals(\"a b c\")(\"a b c\", t)\n\texpect.Match(regexp.MustCompile(\"[a-z ]+\"))(\"a b c\", t)\n\n\texpect.All(\n\t\texpect.Contains(\"b\"),\n\t\texpect.Contains(\"b\", \"c\"),\n\t\texpect.DoesNotContain(\"d\"),\n\t\texpect.DoesNotContain(\"d\", \"e\"),\n\t\texpect.Equals(\"a b c\"),\n\t\texpect.Match(regexp.MustCompile(\"[a-z ]+\")),\n\t)(\"a b c\", t)\n\n\ttype foo struct {\n\t\tFoo map[string]string `json:\"foo\"`\n\t}\n\n\tdata, err := json.Marshal(&foo{\n\t\tFoo: map[string]string{\n\t\t\t\"foo\": \"bar\",\n\t\t},\n\t})\n\n\tassertive.ErrorIsNil(t, err)\n\n\texpect.JSON(&foo{}, nil)(string(data), t)\n\n\texpect.JSON(&foo{}, func(obj *foo, t tig.T) {\n\t\tassertive.IsEqual(t, obj.Foo[\"foo\"], \"bar\")\n\t})(string(data), t)\n}\n"
  },
  {
    "path": "mod/tigron/expect/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package expect provides a set of simple concrete test.Comparator implementations to use by tests\n// on stdout, along with exit code expectations.\npackage expect\n"
  },
  {
    "path": "mod/tigron/expect/doc.md",
    "content": "# Expectations\n\nAttaching expectations to a test case is how the developer can express conditions on exit code, stdout, or stderr,\nto be verified for the test to pass.\n\nThe simplest way to do that is to use the helper `test.Expects(exitCode int, errors []error, outputCompare test.Comparator)`.\n\n```go\npackage main\n\nimport (\n    \"testing\"\n\n    \"github.com/containerd/nerdctl/mod/tigron/test\"\n)\n\nfunc TestMyThing(t *testing.T) {\n    // Declare your test\n    myTest := &test.Case{}\n\n    // Attach a command to run\n    myTest.Command = test.Custom(\"ls\")\n\n    // Set your expectations\n    myTest.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil)\n\n    // Run it\n    myTest.Run(t)\n}\n```\n\n### Exit status expectations\n\nThe first parameter, `exitCode` should be set to one of the provided `expect.ExitCodeXXX` constants:\n- `expect.ExitCodeSuccess`: validates that the command ran and exited successfully\n- `expect.ExitCodeTimeout`: validates that the command did time out\n- `expect.ExitCodeSignaled`: validates that the command received a signal\n- `expect.ExitCodeGenericFail`: validates that the command failed (failed to start, or returned a non-zero exit code)\n- `expect.ExitCodeNoCheck`: does not enforce any verification at all on the command\n\n... you may also pass explicit exit codes directly (> 0) if you want to precisely match them.\n\n### Stderr expectations with []error\n\nTo validate that stderr contain specific information, you can pass a slice of `error` as `test.Expects`\nsecond parameter.\n\nThe command output on stderr is then verified to contain all stringified errors.\n\n### Stdout expectations with Comparators\n\nThe last parameter of `test.Expects` accepts a `test.Comparator`, which allows testing the content of the command\noutput on `stdout`.\n\nThe following ready-made `test.Comparator` generators are provided:\n- `expect.Contains(string, ...string)`: verifies that stdout does contain the provided parameters\n- `expect.DoesNotContain(string, ...string)`: verifies that stdout does not contain any of the passed parameters\n- `expect.Equals(string)`: strict equality\n- `expect.Match(*regexp.Regexp)`: regexp matching\n- `expect.All(comparators ...Comparator)`: allows to bundle together a bunch of other comparators\n- `expect.JSON[T any](obj T, verifier func(T, tig.T))`: allows to verify the output is valid JSON and optionally\npass `verifier(T, string, tig.T)` extra validation\n\n### A complete example\n\n```go\npackage main\n\nimport (\n    \"testing\"\n    \"errors\"\n\n    \"github.com/containerd/nerdctl/mod/tigron/tig\"\n    \"github.com/containerd/nerdctl/mod/tigron/test\"\n    \"github.com/containerd/nerdctl/mod/tigron/expect\"\n)\n\ntype Thing struct {\n    Name string\n}\n\nfunc TestMyThing(t *testing.T) {\n    // Declare your test\n    myTest := &test.Case{}\n\n    // Attach a command to run\n    myTest.Command = test.Custom(\"bash\", \"-c\", \"--\", \">&2 echo thing; echo '{\\\"Name\\\": \\\"out\\\"}'; exit 42;\")\n\n    // Set your expectations\n    myTest.Expected = test.Expects(\n        expect.ExitCodeGenericFail,\n        []error{errors.New(\"thing\")},\n        expect.All(\n            expect.Contains(\"out\"),\n            expect.DoesNotContain(\"something\"),\n            expect.JSON(&Thing{}, func(obj *Thing, t tig.T) {\n                assert.Equal(t, obj.Name, \"something\")\n            }),\n        ),\n    )\n\n    // Run it\n    myTest.Run(t)\n}\n```\n\n### Custom stdout comparators\n\nIf you need to implement more advanced verifications on stdout that the ready-made comparators can't do,\nyou can implement your own custom `test.Comparator`.\n\nFor example:\n\n```go\npackage whatever\n\nimport (\n    \"testing\"\n\n    \"gotest.tools/v3/assert\"\n\n    \"github.com/containerd/nerdctl/mod/tigron/tig\"\n    \"github.com/containerd/nerdctl/mod/tigron/test\"\n)\n\nfunc TestMyThing(t *testing.T) {\n    // Declare your test\n    myTest := &test.Case{}\n\n    // Attach a command to run\n    myTest.Command = test.Custom(\"ls\")\n\n    // Set your expectations\n    myTest.Expected = test.Expects(0, nil, func(stdout string, t tig.T){\n        t.Helper()\n        // Bla bla, do whatever advanced stuff and some asserts\n    })\n\n    // Run it\n    myTest.Run(t)\n}\n\n// You can of course generalize your comparator into a generator if it is going to be useful repeatedly\n\nfunc MyComparatorGenerator(param1, param2 any) test.Comparator {\n    return func(stdout string, t tig.T) {\n        t.Helper()\n        // Do your thing...\n        // ...\n    }\n}\n\n```\n\nYou can now pass along `MyComparator(comparisonString)` as the third parameter of `test.Expects`, or compose it with\nother comparators using `expect.All(MyComparator(comparisonString), OtherComparator(somethingElse))`\n\n### Advanced expectations\n\nYou may want to have expectations that contain a certain piece of data that is being used in the command or at\nother stages of your test (like `Setup` for example).\n\nTo achieve that, you should write your own `test.Manager` instead of using the helper `test.Expects`.\n\nA manager is a simple function which only role is to return a `test.Expected` struct.\nThe `test.Manager` signature makes available `test.Data` and `test.Helpers` to you.\n\nHere is an example, where we are using `data.Labels().Get(\"sometestdata\")`.\n\n```go\npackage main\n\nimport (\n    \"errors\"\n    \"testing\"\n\n    \"gotest.tools/v3/assert\"\n\n    \"github.com/containerd/nerdctl/mod/tigron/tig\"\n    \"github.com/containerd/nerdctl/mod/tigron/test\"\n)\n\nfunc TestMyThing(t *testing.T) {\n    // Declare your test\n    myTest := &test.Case{}\n\n    myTest.Setup = func(data test.Data, helpers test.Helpers){\n        // Do things...\n        // ...\n        // Save this for later\n        data.Labels().Set(\"something\", \"lalala\")\n    }\n\n    // Attach a command to run\n    myTest.Command = test.Custom(\"somecommand\")\n\n    // Set your fully custom expectations\n    myTest.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {\n        // With a custom Manager you have access to both the test.Data and test.Helpers to perform more\n        // refined verifications.\n        return &test.Expected{\n            ExitCode: 1,\n            Errors: []error{\n                errors.New(\"foobla\"),\n            },\n            Output: func(stdout string, t tig.T) {\n                t.Helper()\n\n                // Retrieve the data that was set during the Setup phase.\n                assert.Assert(t, stdout == data.Labels().Get(\"sometestdata\"))\n            },\n        }\n    }\n\n    // Run it\n    myTest.Run(t)\n}\n```\n"
  },
  {
    "path": "mod/tigron/expect/exit.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage expect\n\nconst (\n\t// ExitCodeSuccess will ensure that the command effectively ran returned with exit code zero.\n\tExitCodeSuccess = 0\n\t// ExitCodeSigkill verifies a container exited due to SIGKILL.\n\tExitCodeSigkill = 137\n\t// ExitCodeGenericFail will verify that the command ran and exited with a non-zero error code.\n\t// This does NOT include timeouts, cancellation, or signals.\n\tExitCodeGenericFail = -10\n\t// ExitCodeNoCheck does not enforce any check at all on the function.\n\tExitCodeNoCheck = -11\n\t// ExitCodeTimeout verifies that the command was cancelled on timeout.\n\tExitCodeTimeout = -12\n\t// ExitCodeSignaled verifies that the command has been terminated by a signal.\n\tExitCodeSignaled = -13\n\t// ExitCodeCancelled = -14.\n)\n"
  },
  {
    "path": "mod/tigron/go.mod",
    "content": "module github.com/containerd/nerdctl/mod/tigron\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/creack/pty v1.1.24\n\tgo.uber.org/goleak v1.3.0\n\tgolang.org/x/sync v0.13.0\n\tgolang.org/x/term v0.30.0\n\tgolang.org/x/text v0.24.0\n)\n\nrequire golang.org/x/sys v0.31.0 // indirect\n"
  },
  {
    "path": "mod/tigron/go.sum",
    "content": "github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngolang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=\ngolang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=\ngolang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=\ngolang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=\ngolang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=\ngolang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "mod/tigron/hack/dev-setup-linux.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\n\nsudo apt-get install -qq --no-install-recommends golang make yamllint shellcheck\n"
  },
  {
    "path": "mod/tigron/hack/dev-setup-macos.sh",
    "content": "#!/usr/bin/env bash\n\n#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\nset -o errexit -o errtrace -o functrace -o nounset -o pipefail\n\nbrew install golang make yamllint shellcheck\n"
  },
  {
    "path": "mod/tigron/hack/headers/bash.txt",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License."
  },
  {
    "path": "mod/tigron/hack/headers/dockerfile.txt",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License."
  },
  {
    "path": "mod/tigron/hack/headers/go.txt",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/"
  },
  {
    "path": "mod/tigron/hack/headers/makefile.txt",
    "content": "#   Copyright The containerd Authors.\n\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n#   you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n\n#       http://www.apache.org/licenses/LICENSE-2.0\n\n#   Unless required by applicable law or agreed to in writing, software\n#   distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n#   limitations under the License.\n\n# project-checks is broken.\n# See https://github.com/containerd/nerdctl/pull/3889"
  },
  {
    "path": "mod/tigron/internal/assertive/assertive.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:add-constant,package-comments\npackage assertive\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/formatter\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nconst (\n\tmarkLineLength           = 20\n\texpectedSuccessDecorator = \"✅️ does verify:\\t\\t\"\n\texpectedFailDecorator    = \"❌ FAILED!\\t\\t\"\n\treceivedDecorator        = \"👀 testing:\\t\\t\"\n\tannotationDecorator      = \"🖊️\"\n\thyperlinkDecorator       = \"🔗\"\n)\n\n// ErrorIsNil fails a test if err is not nil.\nfunc ErrorIsNil(testing tig.T, err error, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(testing, errors.Is(err, nil), err, \"is `<nil>`\", msg...)\n}\n\n// ErrorIs fails a test if err is not the comparison error.\nfunc ErrorIs(testing tig.T, err, expected error, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(testing, errors.Is(err, expected), err, fmt.Sprintf(\"is `%v`\", expected), msg...)\n}\n\n// IsEqual fails a test if the two interfaces are not equal.\nfunc IsEqual[T comparable](testing tig.T, actual, expected T, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(testing, actual == expected, actual, fmt.Sprintf(\"= `%v`\", expected), msg...)\n}\n\n// IsNotEqual fails a test if the two interfaces are equal.\nfunc IsNotEqual[T comparable](testing tig.T, actual, expected T, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(testing, actual != expected, actual, fmt.Sprintf(\"!= `%v`\", expected), msg...)\n}\n\n// Contains fails a test if the actual string does not contain the other string.\nfunc Contains(testing tig.T, actual, contains string, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(\n\t\ttesting,\n\t\tstrings.Contains(actual, contains),\n\t\tactual,\n\t\tfmt.Sprintf(\"~= `%v`\", contains),\n\t\tmsg...)\n}\n\n// DoesNotContain fails a test if the actual string contains the other string.\nfunc DoesNotContain(testing tig.T, actual, contains string, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(\n\t\ttesting,\n\t\t!strings.Contains(actual, contains),\n\t\tactual,\n\t\tfmt.Sprintf(\"! ~= `%v`\", contains),\n\t\tmsg...)\n}\n\n// HasSuffix fails a test if the string does not end with suffix.\nfunc HasSuffix(testing tig.T, actual, suffix string, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(\n\t\ttesting,\n\t\tstrings.HasSuffix(actual, suffix),\n\t\tactual,\n\t\tfmt.Sprintf(\"`%v` $\", suffix),\n\t\tmsg...)\n}\n\n// HasPrefix fails a test if the string does not start with prefix.\nfunc HasPrefix(testing tig.T, actual, prefix string, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(\n\t\ttesting,\n\t\tstrings.HasPrefix(actual, prefix),\n\t\tactual,\n\t\tfmt.Sprintf(\"^ `%v`\", prefix),\n\t\tmsg...)\n}\n\n// Match fails a test if the string does not match the regexp.\nfunc Match(testing tig.T, actual string, reg *regexp.Regexp, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(testing, reg.MatchString(actual), actual, fmt.Sprintf(\"`%v`\", reg), msg...)\n}\n\n// DoesNotMatch fails a test if the string does match the regexp.\nfunc DoesNotMatch(testing tig.T, actual string, reg *regexp.Regexp, msg ...string) {\n\ttesting.Helper()\n\n\tevaluate(testing, !reg.MatchString(actual), actual, fmt.Sprintf(\"`%v`\", reg), msg...)\n}\n\n// IsLessThan fails a test if the actual is more or equal than the reference.\nfunc IsLessThan[T ~int | ~float64 | time.Duration](\n\ttesting tig.T,\n\tactual, expected T,\n\tmsg ...string,\n) {\n\ttesting.Helper()\n\n\tevaluate(testing, actual < expected, actual, fmt.Sprintf(\"< `%v`\", expected), msg...)\n}\n\n// IsMoreThan fails a test if the actual is less or equal than the reference.\nfunc IsMoreThan[T ~int | ~float64 | time.Duration](\n\ttesting tig.T,\n\tactual, expected T,\n\tmsg ...string,\n) {\n\ttesting.Helper()\n\n\tevaluate(testing, actual > expected, actual, fmt.Sprintf(\"< `%v`\", expected), msg...)\n}\n\n// True fails a test if the boolean is not true...\nfunc True(testing tig.T, comp bool, msg ...string) bool {\n\ttesting.Helper()\n\n\tevaluate(testing, comp, comp, true, msg...)\n\n\treturn comp\n}\n\n// WithFailLater will allow an assertion to not fail the test immediately.\n// Failing later is necessary when asserting inside go routines, and also if you want many\n// successive asserts to all evaluate instead of stopping at the first failing one.\n// FIXME: it should be possible to have both WithFailLater and WithSilentSuccess at the same time.\nfunc WithFailLater(t tig.T) tig.T {\n\treturn &failLater{\n\t\tt,\n\t}\n}\n\n// WithSilentSuccess (used to wrap a *testing.T struct) will not log debugging assertive information\n// when the result is\n// a success.\n// In some cases, this is convenient to avoid crowding the display with successful checks info.\nfunc WithSilentSuccess(t tig.T) tig.T {\n\treturn &silentSuccess{\n\t\tt,\n\t}\n}\n\ntype failLater struct {\n\ttig.T\n}\ntype silentSuccess struct {\n\ttig.T\n}\n\nfunc evaluate(testing tig.T, isSuccess bool, actual, expected any, msg ...string) {\n\ttesting.Helper()\n\n\tdecorate(testing, isSuccess, actual, expected, msg...)\n\n\tif !isSuccess {\n\t\tif _, ok := testing.(*failLater); ok {\n\t\t\ttesting.Fail()\n\t\t} else {\n\t\t\ttesting.FailNow()\n\t\t}\n\t}\n}\n\nfunc decorate(testing tig.T, isSuccess bool, actual, expected any, msg ...string) {\n\ttesting.Helper()\n\n\tif _, ok := testing.(*silentSuccess); !isSuccess || !ok {\n\t\thead := strings.Repeat(\"<\", markLineLength)\n\t\tfooter := strings.Repeat(\">\", markLineLength)\n\t\theader := \"\\t\"\n\n\t\tcustom := fmt.Sprintf(\"\\t%s %s\", annotationDecorator, strings.Join(msg, \"\\n\"))\n\n\t\tmsg = append([]string{\"\", head}, custom)\n\n\t\tmsg = append([]string{getTopFrameFile()}, msg...)\n\n\t\tmsg = append(msg, fmt.Sprintf(\"\\t%s`%v`\", receivedDecorator, actual))\n\n\t\tif isSuccess {\n\t\t\tmsg = append(msg,\n\t\t\t\tfmt.Sprintf(\"\\t%s%v\", expectedSuccessDecorator, expected),\n\t\t\t)\n\t\t} else {\n\t\t\tmsg = append(msg,\n\t\t\t\tfmt.Sprintf(\"\\t%s%v\", expectedFailDecorator, expected),\n\t\t\t)\n\t\t}\n\n\t\ttesting.Log(header + strings.Join(msg, \"\\n\") + \"\\n\" + footer + \"\\n\")\n\t}\n}\n\n// XXX FIXME #expert\n// Because of how golang testing works, the upper frame is the one from where t.Run is being called,\n// as (presumably) the passed function is starting with its own stack in a go routine.\n// In the case of subtests, t.Run being called from inside Tigron will make it so that the top frame\n// is case.go around line 233 (where we call Command.Run(), which is the one calling assertive).\n// To possibly address this:\n// plan a. just drop entirely OSC8 links and source extracts and trash all of this\n// plan b. get the top frame from the root test, and pass it to subtests on a custom property, the somehow into here\n// plan c. figure out a hack to call t.Run from the test file without ruining the Tigron UX\n// Dereference t.Run? Return a closure to be called from the top? w/o disabling inlining in the right place?\n// Short term, blacklisting /tigron (and /nerdtest) will at least prevent the wrong links from appearing in the output.\nfunc getTopFrameFile() string {\n\t// Get the frames. Skip the first two frames - current one and caller.\n\t//nolint:mnd // Whatever mnd...\n\tpc := make([]uintptr, 40)\n\t//nolint:mnd // Whatever mnd...\n\tn := runtime.Callers(2, pc)\n\tcallersFrames := runtime.CallersFrames(pc[:n])\n\n\tvar (\n\t\tfile       string\n\t\tlineNumber int\n\t\tframe      runtime.Frame\n\t)\n\n\tmore := true\n\tfor more {\n\t\tframe, more = callersFrames.Next()\n\n\t\t// Once we are in the go main stack, bail out\n\t\tif !strings.Contains(frame.Function, \"/\") {\n\t\t\tbreak\n\t\t}\n\n\t\t// XXX see note above\n\t\tif strings.Contains(frame.File, \"/tigron\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.Contains(frame.File, \"/nerdtest\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tfile = frame.File\n\t\tlineNumber = frame.Line\n\t}\n\n\tif file == \"\" {\n\t\treturn \"\"\n\t}\n\n\t//nolint:gosec // file is coming from runtime frames so, fine\n\tsource, err := os.Open(file)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tdefer func() {\n\t\t_ = source.Close()\n\t}()\n\n\tindex := 1\n\tscanner := bufio.NewScanner(source)\n\n\tvar line string\n\n\tfor ; scanner.Err() == nil && index <= lineNumber; index++ {\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\n\t\tline = strings.Trim(scanner.Text(), \"\\t \")\n\t}\n\n\treturn hyperlinkDecorator + \" \" + (&formatter.OSC8{\n\t\tText:     line,\n\t\tLocation: \"file://\" + file,\n\t\tLine:     lineNumber,\n\t}).String()\n}\n"
  },
  {
    "path": "mod/tigron/internal/assertive/assertive_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:add-constant\npackage assertive_test\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/mimicry\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/mocks\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nfunc TestAssertivePass(t *testing.T) {\n\tt.Parallel()\n\n\tvar nilErr error\n\t//nolint:err113 // Fine, this is a test\n\tnotNilErr := errors.New(\"some error\")\n\n\tassertive.ErrorIsNil(t, nilErr, \"a nil error should pass ErrorIsNil\")\n\tassertive.ErrorIs(t, nilErr, nil, \"a nil error should pass ErrorIs(err, nil)\")\n\tassertive.ErrorIs(\n\t\tt,\n\t\tfmt.Errorf(\"neh %w\", notNilErr),\n\t\tnotNilErr,\n\t\t\"an error wrapping another should match with ErrorIs\",\n\t)\n\n\tassertive.IsEqual(t, \"foo\", \"foo\", \"= should work as expected (on string)\")\n\tassertive.IsNotEqual(t, \"foo\", \"else\", \"!= should work as expected (on string)\")\n\n\tassertive.IsEqual(t, true, true, \"= should work as expected (on bool)\")\n\tassertive.IsNotEqual(t, true, false, \"!= should work as expected (on bool)\")\n\n\tassertive.IsEqual(t, 1, 1, \"= should work as expected (on int)\")\n\tassertive.IsNotEqual(t, 1, 0, \"!= should work as expected (on int)\")\n\n\tassertive.IsEqual(t, -1.0, -1, \"= should work as expected (on float)\")\n\tassertive.IsNotEqual(t, -1.0, 0, \"!= should work as expected (on float)\")\n\n\ttype foo struct {\n\t\tname string\n\t}\n\n\tassertive.IsEqual(t, foo{}, foo{}, \"= should work as expected (on struct)\")\n\tassertive.IsEqual(\n\t\tt,\n\t\tfoo{name: \"foo\"},\n\t\tfoo{name: \"foo\"},\n\t\t\"= should work as expected (on struct)\",\n\t)\n\tassertive.IsNotEqual(\n\t\tt,\n\t\tfoo{name: \"bar\"},\n\t\tfoo{name: \"foo\"},\n\t\t\"!= should work as expected (on struct)\",\n\t)\n\n\tassertive.Contains(t, \"foo\", \"o\", \"⊂ should work\")\n\tassertive.DoesNotContain(t, \"foo\", \"a\", \"¬⊂ should work\")\n\tassertive.HasPrefix(t, \"foo\", \"f\", \"prefix should work\")\n\tassertive.HasSuffix(t, \"foo\", \"o\", \"suffix should work\")\n\tassertive.Match(t, \"foo\", regexp.MustCompile(\"^[fo]{3,}$\"), \"match should work\")\n\tassertive.DoesNotMatch(t, \"foo\", regexp.MustCompile(\"^[abc]{3,}$\"), \"match should work\")\n\n\tassertive.True(t, true, \"is true should work as expected\")\n\n\tassertive.IsLessThan(t, time.Minute, time.Hour, \"< should work (duration)\")\n\tassertive.IsMoreThan(t, time.Minute, time.Second, \"< should work (duration)\")\n\tassertive.IsLessThan(t, 1, 2, \"< should work (int)\")\n\tassertive.IsMoreThan(t, 2, 1, \"> should work (int)\")\n\tassertive.IsLessThan(t, -1.2, 2, \"< should work (float)\")\n\tassertive.IsMoreThan(t, 2, -1.2, \"> should work (float)\")\n}\n\nfunc TestAssertiveFailBehavior(t *testing.T) {\n\tt.Parallel()\n\n\tmockT := &mocks.MockT{}\n\n\tvar nilErr error\n\t//nolint:err113 // Fine, this is a test\n\tnotNilErr := errors.New(\"some error\")\n\n\tassertive.ErrorIsNil(mockT, notNilErr, \"a nil error should pass ErrorIsNil\")\n\tassertive.ErrorIs(mockT, notNilErr, nil, \"a nil error should pass ErrorIs(err, nil)\")\n\tassertive.ErrorIs(\n\t\tmockT,\n\t\tfmt.Errorf(\"neh %w\", nilErr),\n\t\tnilErr,\n\t\t\"an error wrapping another should match with ErrorIs\",\n\t)\n\n\tassertive.IsEqual(mockT, \"foo\", \"else\", \"= should work as expected (on string)\")\n\tassertive.IsNotEqual(mockT, \"foo\", \"foo\", \"!= should work as expected (on string)\")\n\n\tassertive.IsEqual(mockT, true, false, \"= should work as expected (on bool)\")\n\tassertive.IsNotEqual(mockT, true, true, \"!= should work as expected (on bool)\")\n\n\tassertive.IsEqual(mockT, 1, 0, \"= should work as expected (on int)\")\n\tassertive.IsNotEqual(mockT, 1, 1, \"!= should work as expected (on int)\")\n\n\tassertive.IsEqual(mockT, -1.0, 0, \"= should work as expected (on float)\")\n\tassertive.IsNotEqual(mockT, -1.0, -1, \"!= should work as expected (on float)\")\n\n\ttype foo struct {\n\t\tname string\n\t}\n\n\tassertive.IsEqual(mockT, foo{}, foo{name: \"foo\"}, \"= should work as expected (on struct)\")\n\tassertive.IsEqual(\n\t\tmockT,\n\t\tfoo{name: \"bar\"},\n\t\tfoo{name: \"foo\"},\n\t\t\"= should work as expected (on struct)\",\n\t)\n\tassertive.IsNotEqual(\n\t\tmockT,\n\t\tfoo{name: \"\"},\n\t\tfoo{name: \"\"},\n\t\t\"!= should work as expected (on struct)\",\n\t)\n\n\tassertive.Contains(mockT, \"foo\", \"a\", \"⊂ should work\")\n\tassertive.DoesNotContain(mockT, \"foo\", \"o\", \"¬⊂ should work\")\n\tassertive.HasPrefix(mockT, \"foo\", \"o\", \"prefix should work\")\n\tassertive.HasSuffix(mockT, \"foo\", \"f\", \"suffix should work\")\n\tassertive.Match(mockT, \"foo\", regexp.MustCompile(\"^[abc]{3,}$\"), \"match should work\")\n\tassertive.DoesNotMatch(mockT, \"foo\", regexp.MustCompile(\"^[fo]{3,}$\"), \"match should work\")\n\n\tassertive.True(mockT, false, \"is true should work as expected\")\n\n\tassertive.IsLessThan(mockT, time.Hour, time.Minute, \"< should work (duration)\")\n\tassertive.IsMoreThan(mockT, time.Second, time.Minute, \"< should work (duration)\")\n\tassertive.IsLessThan(mockT, 2, 1, \"< should work (int)\")\n\tassertive.IsMoreThan(mockT, 1, 2, \"> should work (int)\")\n\tassertive.IsLessThan(mockT, 2, -1.2, \"< should work (float)\")\n\tassertive.IsMoreThan(mockT, -1.2, 2, \"> should work (float)\")\n\n\tif len(mockT.Report(tig.T.FailNow)) != 27 {\n\t\tt.Error(\"we should have called FailNow as many times as we have asserts here\")\n\t}\n\n\tif len(mockT.Report(tig.T.Fail)) != 0 {\n\t\tt.Error(\"we should NOT have called Fail\")\n\t}\n}\n\nfunc TestAssertiveFailLater(t *testing.T) {\n\tt.Parallel()\n\n\tmockT := &mocks.MockT{}\n\n\tassertive.True(assertive.WithFailLater(mockT), false, \"is true should work as expected\")\n\n\tif len(mockT.Report(tig.T.FailNow)) != 0 {\n\t\tt.Log(mimicry.PrintCall(mockT.Report(tig.T.FailNow)[0]))\n\t\tt.Error(\"we should NOT have called FailNow\")\n\t}\n\n\tif len(mockT.Report(tig.T.Fail)) != 1 {\n\t\tt.Error(\"we should have called Fail\")\n\t}\n}\n\nfunc TestAssertiveSilentSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tmockT := &mocks.MockT{}\n\n\tassertive.True(mockT, true, \"is true should work as expected\")\n\tassertive.True(mockT, false, \"is true should work as expected\")\n\n\tif len(mockT.Report(tig.T.Log)) != 2 {\n\t\tt.Error(\"we should have called Log on both success and failure\")\n\t}\n\n\tmockT.Reset()\n\n\tassertive.True(assertive.WithSilentSuccess(mockT), true, \"is true should work as expected\")\n\n\tif len(mockT.Report(tig.T.Log)) != 0 {\n\t\tt.Log(mimicry.PrintCall(mockT.Report(tig.T.Log)[0]))\n\t\tt.Error(\"we should NOT have called Log on success\")\n\t}\n\n\tassertive.True(assertive.WithSilentSuccess(mockT), false, \"is true should work as expected\")\n\n\tif len(mockT.Report(tig.T.Log)) != 1 {\n\t\tt.Error(\"we should still have called Log on failure\")\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/internal/assertive/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package assertive is an experimental, zero-dependencies assert library.\n// Right now, it is not public and meant to be used only inside tigron.\n// Consumers of tigron are free to use whatever assert library they want.\n// In the future, this may become public for peeps who want `assert` to be\n// bundled in.\npackage assertive\n"
  },
  {
    "path": "mod/tigron/internal/com/command.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage com\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/logger\"\n)\n\nconst (\n\tdefaultTimeout = 10 * time.Second\n\tdelayAfterWait = 100 * time.Millisecond\n)\n\nvar (\n\t// ErrTimeout is returned by Wait() in case a command fail to complete within allocated time.\n\tErrTimeout = errors.New(\"command timed out\")\n\t// ErrFailedStarting is returned by Run() and Wait() in case a command fails to start (eg:\n\t// binary missing).\n\tErrFailedStarting = errors.New(\"command failed starting\")\n\t// ErrSignaled is returned by Wait() if a signal was sent to the command while running.\n\tErrSignaled = errors.New(\"command execution signaled\")\n\t// ErrExecutionFailed is returned by Wait() when a command executes but returns a non-zero error code.\n\tErrExecutionFailed = errors.New(\"command returned a non-zero exit code\")\n\t// ErrFailedSendingSignal may happen if sending a signal to an already terminated process.\n\tErrFailedSendingSignal = errors.New(\"failed sending signal\")\n\n\t// ErrExecAlreadyStarted is a system error normally indicating a bogus double call to Run().\n\tErrExecAlreadyStarted = errors.New(\"command has already been started (double `Run`)\")\n\t// ErrExecNotStarted is a system error normally indicating that Wait() has been called without first calling Run().\n\tErrExecNotStarted = errors.New(\"command has not been started (call `Run` first)\")\n\t// ErrExecAlreadyFinished is a system error indicating a double call to Wait().\n\tErrExecAlreadyFinished = errors.New(\"command is already finished\")\n\n\terrExecutionCancelled = errors.New(\"command execution cancelled\")\n)\n\ntype contextKey string\n\n// LoggerKey defines the key to attach a logger to on the context.\nconst LoggerKey = contextKey(\"logger\")\n\n// Result carries the resulting output of a command once it has finished.\ntype Result struct {\n\tEnviron  []string\n\tStdout   string\n\tStderr   string\n\tExitCode int\n\tSignal   os.Signal\n\tDuration time.Duration\n}\n\ntype execution struct {\n\t//nolint:containedctx // Is there a way around this?\n\tcontext context.Context\n\tcancel  context.CancelFunc\n\tcommand *exec.Cmd\n\tpipes   *stdPipes\n\tlog     logger.Logger\n\terr     error\n}\n\n// Command is a thin wrapper on-top of golang exec.Command.\ntype Command struct {\n\tBinary      string\n\tPrependArgs []string\n\tArgs        []string\n\tWrapBinary  string\n\tWrapArgs    []string\n\tTimeout     time.Duration\n\n\tWorkingDir   string\n\tEnv          map[string]string\n\tEnvBlackList []string\n\tEnvWhiteList []string\n\n\twriters []func() io.Reader\n\n\tptyStdout bool\n\tptyStderr bool\n\tptyStdin  bool\n\n\texec      *execution\n\tmutex     sync.Mutex\n\tresult    *Result\n\tstartTime time.Time\n}\n\n// Clone does just duplicate a command, resetting its execution.\nfunc (gc *Command) Clone() *Command {\n\tcom := &Command{\n\t\tBinary:      gc.Binary,\n\t\tPrependArgs: append([]string(nil), gc.PrependArgs...),\n\t\tArgs:        append([]string(nil), gc.Args...),\n\t\tWrapBinary:  gc.WrapBinary,\n\t\tWrapArgs:    append([]string(nil), gc.WrapArgs...),\n\t\tTimeout:     gc.Timeout,\n\n\t\tWorkingDir:   gc.WorkingDir,\n\t\tEnv:          map[string]string{},\n\t\tEnvBlackList: append([]string(nil), gc.EnvBlackList...),\n\t\tEnvWhiteList: append([]string(nil), gc.EnvWhiteList...),\n\n\t\twriters: append([]func() io.Reader(nil), gc.writers...),\n\n\t\tptyStdout: gc.ptyStdout,\n\t\tptyStderr: gc.ptyStderr,\n\t\tptyStdin:  gc.ptyStdin,\n\t}\n\n\tfor k, v := range gc.Env {\n\t\tcom.Env[k] = v\n\t}\n\n\treturn com\n}\n\n// WithPTY requests that the command be executed with a pty for std streams.\n// Parameters allow showing which streams are to be tied to the pty.\n// This command has no effect if Run has already been called.\nfunc (gc *Command) WithPTY(stdin, stdout, stderr bool) {\n\tgc.ptyStdout = stdout\n\tgc.ptyStderr = stderr\n\tgc.ptyStdin = stdin\n}\n\n// WithFeeder ensures that the provider function will be executed and its output fed to the command stdin.\n// WithFeeder, like Feed, can be used multiple times, and writes will be performed sequentially, in order.\n// This command has no effect if Run has already been called.\n// Note that if the `writer` function runs a forever loop, we will deadlock and just Wait() forever on the errgroup.\nfunc (gc *Command) WithFeeder(writers ...func() io.Reader) {\n\tgc.writers = append(gc.writers, writers...)\n}\n\n// Feed ensures that the provider reader will be copied on the command stdin.\n// Feed, like WithFeeder, can be used multiple times, and writes will be performed in sequentially, in order.\n// This command has no effect if Run has already been called.\nfunc (gc *Command) Feed(reader io.Reader) {\n\tgc.writers = append(gc.writers, func() io.Reader {\n\t\treturn reader\n\t})\n}\n\n// Run starts the command in the background.\n// It may error out immediately if the command fails to start (ErrFailedStarting).\nfunc (gc *Command) Run(parentCtx context.Context) error {\n\t// Lock\n\tgc.mutex.Lock()\n\tdefer gc.mutex.Unlock()\n\n\t// Protect against dumb calls\n\tif gc.result != nil {\n\t\treturn ErrExecAlreadyFinished\n\t} else if gc.exec != nil {\n\t\treturn ErrExecAlreadyStarted\n\t}\n\n\tvar (\n\t\tctx       context.Context\n\t\tctxCancel context.CancelFunc\n\t\tpipes     *stdPipes\n\t\tcmd       *exec.Cmd\n\t\terr       error\n\t)\n\n\t// Get a timing-out context\n\tif gc.Timeout == 0 {\n\t\tgc.Timeout = defaultTimeout\n\t}\n\n\tctx, ctxCancel = context.WithTimeout(parentCtx, gc.Timeout)\n\tgc.startTime = time.Now()\n\n\t// Create a contextual command, set the logger\n\tcmd = gc.buildCommand(ctx)\n\t// Get a debug-logger from the context\n\tvar (\n\t\tlog logger.Logger\n\t\tok  bool\n\t)\n\n\tif log, ok = parentCtx.Value(LoggerKey).(logger.Logger); !ok {\n\t\tlog = nil\n\t}\n\n\tconLog := logger.NewLogger(log).Set(\"command\", cmd.String())\n\t// FIXME: this is manual silencing of pipe logs (very noisy)\n\t// It should be possible to enable this with some debug flag.\n\t// Note that one probably never want this on unless they are actually debugging pipes issues...\n\temLog := logger.NewLogger(nil).Set(\"command\", cmd.String())\n\n\tgc.exec = &execution{\n\t\tcontext: ctx,\n\t\tcancel:  ctxCancel,\n\t\tcommand: cmd,\n\t\tlog:     conLog,\n\t}\n\n\t// Prepare pipes\n\tpipes, err = newStdPipes(ctx, emLog, gc.ptyStdout, gc.ptyStderr, gc.ptyStdin, gc.writers)\n\tif err != nil {\n\t\tctxCancel()\n\n\t\tgc.exec.err = errors.Join(ErrFailedStarting, err)\n\n\t\t// No wrapping here - we do not even have pipes, and the command has not been started.\n\n\t\treturn gc.exec.err\n\t}\n\n\t// Attach pipes\n\tgc.exec.pipes = pipes\n\tcmd.Stdout = pipes.stdout.writer\n\tcmd.Stderr = pipes.stderr.writer\n\tcmd.Stdin = pipes.stdin.reader\n\n\t// Start it\n\tif err = cmd.Start(); err != nil {\n\t\t// On failure, can the context, wrap whatever we have and return\n\t\tgc.exec.log.Log(\"start failed\", err)\n\n\t\tgc.exec.err = errors.Join(ErrFailedStarting, err)\n\n\t\t_ = gc.wrap()\n\n\t\tdefer ctxCancel()\n\n\t\treturn gc.exec.err\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\t// There is no good reason for this to happen, so, log it\n\t\terr = gc.wrap()\n\n\t\tgc.exec.log.Log(\"stdout\", gc.result.Stdout)\n\t\tgc.exec.log.Log(\"stderr\", gc.result.Stderr)\n\t\tgc.exec.log.Log(\"exitcode\", gc.result.ExitCode)\n\t\tgc.exec.log.Log(\"err\", err)\n\t\tgc.exec.log.Log(\"ctxerr\", ctx.Err())\n\n\t\treturn err\n\tdefault:\n\t}\n\n\treturn nil\n}\n\n// Wait should be called after Run(), and will return the outcome of the command execution.\nfunc (gc *Command) Wait() (*Result, error) {\n\tgc.mutex.Lock()\n\tdefer gc.mutex.Unlock()\n\n\tswitch {\n\tcase gc.exec == nil:\n\t\treturn nil, ErrExecNotStarted\n\tcase gc.exec.err != nil:\n\t\treturn gc.result, gc.exec.err\n\tcase gc.result != nil:\n\t\treturn gc.result, ErrExecAlreadyFinished\n\t}\n\n\t// Cancel the context in any case now\n\tdefer gc.exec.cancel()\n\n\t// Wait for the command\n\t_ = gc.exec.command.Wait()\n\n\t// Capture timeout and cancellation\n\tselect {\n\tcase <-gc.exec.context.Done():\n\tdefault:\n\t}\n\n\t// Wrap the results and return\n\terr := gc.wrap()\n\n\treturn gc.result, err\n}\n\n// Signal sends a signal to the command. It should be called after Run() but before Wait().\nfunc (gc *Command) Signal(sig os.Signal) error {\n\tgc.mutex.Lock()\n\tdefer gc.mutex.Unlock()\n\n\tif gc.exec == nil {\n\t\treturn ErrExecNotStarted\n\t}\n\n\terr := gc.exec.command.Process.Signal(sig)\n\tif err != nil {\n\t\terr = errors.Join(ErrFailedSendingSignal, err)\n\t}\n\n\treturn err\n}\n\nfunc (gc *Command) wrap() error {\n\tpipes := gc.exec.pipes\n\tcmd := gc.exec.command\n\tctx := gc.exec.context\n\n\t// Close and drain the pipes\n\tpipes.closeCallee()\n\t_ = pipes.ioGroup.Wait()\n\tpipes.closeCaller()\n\n\t// Get the status, exitCode, signal, error\n\tvar (\n\t\tstatus   syscall.WaitStatus\n\t\tsignal   os.Signal\n\t\texitCode int\n\t\terr      error\n\t)\n\n\t// XXXgolang: this is troubling. cmd.ProcessState.ExitCode() is always fine, even if cmd.ProcessState is nil.\n\texitCode = cmd.ProcessState.ExitCode()\n\n\tif cmd.ProcessState != nil {\n\t\tvar ok bool\n\t\tif status, ok = cmd.ProcessState.Sys().(syscall.WaitStatus); !ok {\n\t\t\tpanic(\"failed casting process state sys\")\n\t\t}\n\n\t\tif status.Signaled() {\n\t\t\tsignal = status.Signal()\n\t\t\terr = ErrSignaled\n\t\t} else if exitCode != 0 {\n\t\t\terr = ErrExecutionFailed\n\t\t}\n\t}\n\n\t// Catch-up on the context.\n\tswitch ctx.Err() {\n\tcase context.DeadlineExceeded:\n\t\terr = ErrTimeout\n\tcase context.Canceled:\n\t\terr = errExecutionCancelled\n\tdefault:\n\t}\n\n\t// Stuff everything in Result and return err.\n\tgc.result = &Result{\n\t\tExitCode: exitCode,\n\t\tStdout:   pipes.fromStdout,\n\t\tStderr:   pipes.fromStderr,\n\t\tEnviron:  cmd.Environ(),\n\t\tSignal:   signal,\n\t\tDuration: time.Since(gc.startTime),\n\t}\n\n\tif gc.exec.err == nil {\n\t\tgc.exec.err = err\n\t}\n\n\treturn gc.exec.err\n}\n\nfunc (gc *Command) buildCommand(ctx context.Context) *exec.Cmd {\n\t// Build arguments and binary.\n\targs := gc.Args\n\tif gc.PrependArgs != nil {\n\t\targs = append(gc.PrependArgs, args...)\n\t}\n\n\tbinary := gc.Binary\n\n\tif gc.WrapBinary != \"\" {\n\t\targs = append([]string{gc.Binary}, args...)\n\t\targs = append(gc.WrapArgs, args...)\n\t\tbinary = gc.WrapBinary\n\t}\n\n\t//nolint:gosec\n\tcmd := exec.CommandContext(ctx, binary, args...)\n\n\t// Add dir.\n\tcmd.Dir = gc.WorkingDir\n\n\t// Set wait delay after waits returns.\n\tcmd.WaitDelay = delayAfterWait\n\n\t// Build env.\n\tcmd.Env = []string{}\n\n\tconst (\n\t\tstar  = \"*\"\n\t\tequal = \"=\"\n\t)\n\n\tfor _, envValue := range os.Environ() {\n\t\tadd := true\n\n\t\tfor _, needle := range gc.EnvBlackList {\n\t\t\tif strings.HasSuffix(needle, star) {\n\t\t\t\tneedle = strings.TrimSuffix(needle, star)\n\t\t\t} else if needle != star && !strings.Contains(needle, equal) {\n\t\t\t\tneedle += equal\n\t\t\t}\n\n\t\t\tif needle == star || strings.HasPrefix(envValue, needle) {\n\t\t\t\tadd = false\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif len(gc.EnvWhiteList) > 0 {\n\t\t\tadd = false\n\n\t\t\tfor _, needle := range gc.EnvWhiteList {\n\t\t\t\tif strings.HasSuffix(needle, star) {\n\t\t\t\t\tneedle = strings.TrimSuffix(needle, star)\n\t\t\t\t} else if needle != star && !strings.Contains(needle, equal) {\n\t\t\t\t\tneedle += equal\n\t\t\t\t}\n\n\t\t\t\tif needle == star || strings.HasPrefix(envValue, needle) {\n\t\t\t\t\tadd = true\n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif add {\n\t\t\tcmd.Env = append(cmd.Env, envValue)\n\t\t}\n\t}\n\n\t// Attach any explicit env we have\n\tfor k, v := range gc.Env {\n\t\tcmd.Env = append(cmd.Env, k+\"=\"+v)\n\t}\n\n\t// Attach platform ProcAttr and get optional custom cancellation routine.\n\tif cancellation := addAttr(cmd); cancellation != nil {\n\t\tcmd.Cancel = func() error {\n\t\t\tgc.exec.log.Log(\"command cancelled\")\n\n\t\t\t// Call the platform dependent cancellation routine.\n\t\t\treturn cancellation()\n\t\t}\n\t}\n\n\treturn cmd\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/command_other.go",
    "content": "//go:build !windows\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage com\n\nimport (\n\t\"os/exec\"\n\t\"syscall\"\n)\n\nfunc addAttr(cmd *exec.Cmd) func() error {\n\t// Default shutdown will leave child processes behind in certain circumstances.\n\tcmd.SysProcAttr = &syscall.SysProcAttr{\n\t\tSetsid: true,\n\t\t// FIXME: understand why we would want that.\n\t\t// Setctty: true,\n\t}\n\n\treturn func() error {\n\t\t_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/command_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:add-constant\npackage com_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/com\"\n)\n\nconst windows = \"windows\"\n\n// Testing faulty code (double run, etc.)\n\nfunc TestFaultyDoubleRunWait(t *testing.T) {\n\t// Double run returns an error on the second run, but Wait will still work properly\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary:  \"printf\",\n\t\tArgs:    []string{\"one\"},\n\t\tTimeout: time.Second,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIs(t, err, com.ErrExecAlreadyStarted, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, expect.ExitCodeSuccess, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"one\", res.Stdout, \"Stdout\")\n\tassertive.IsEqual(t, \"\", res.Stderr, \"Stderr\")\n}\n\nfunc TestFaultyRunDoubleWait(t *testing.T) {\n\t// Double wait returns an error on the second wait, but also returns the existing result\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary:  \"printf\",\n\t\tArgs:    []string{\"one\"},\n\t\tTimeout: time.Second,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, expect.ExitCodeSuccess, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"one\", res.Stdout, \"Stdout\")\n\tassertive.IsEqual(t, \"\", res.Stderr, \"Stderr\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrExecAlreadyFinished, \"Err\")\n\tassertive.IsEqual(t, expect.ExitCodeSuccess, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"one\", res.Stdout, \"Stdout\")\n\tassertive.IsEqual(t, \"\", res.Stderr, \"Stderr\")\n}\n\nfunc TestFailRun(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"does-not-exist\",\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIs(t, err, com.ErrFailedStarting, \"Err\")\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIs(t, err, com.ErrExecAlreadyFinished, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrFailedStarting, \"Err\")\n\tassertive.IsEqual(t, -1, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"\", res.Stdout, \"Stdout\")\n\tassertive.IsEqual(t, \"\", res.Stderr, \"Stderr\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrFailedStarting, \"Err\")\n\tassertive.IsEqual(t, -1, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"\", res.Stdout, \"Stdout\")\n\tassertive.IsEqual(t, \"\", res.Stderr, \"Stderr\")\n}\n\nfunc TestBasicRunWait(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"printf\",\n\t\tArgs:   []string{\"one\"},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"one\", res.Stdout, \"Stdout\")\n\tassertive.IsEqual(t, \"\", res.Stderr, \"Stderr\")\n}\n\nfunc TestBasicFail(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs:   []string{\"-c\", \"--\", \"does-not-exist\"},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrExecutionFailed, \"Err\")\n\tassertive.IsEqual(t, 127, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"\", res.Stdout, \"Stdout\")\n\tassertive.HasSuffix(t, res.Stderr, \"does-not-exist: command not found\\n\", \"Stderr\")\n}\n\nfunc TestWorkingDir(t *testing.T) {\n\tt.Parallel()\n\n\tdir := t.TempDir()\n\tcommand := &com.Command{\n\t\tBinary:     \"pwd\",\n\t\tWorkingDir: dir,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\n\t// Note:\n\t// - darwin will link to /private/DIR, so, check with HasSuffix\n\t// - windows+ming will go to C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\, so, ignore Windows\n\tif runtime.GOOS == windows {\n\t\tt.Skip(\"skipping last check on windows, see note\")\n\t}\n\n\tassertive.HasSuffix(t, res.Stdout, dir+\"\\n\", \"Stdout\")\n}\n\nfunc TestEnvBlacklist(t *testing.T) {\n\tt.Setenv(\"FOO\", \"BAR\")\n\tt.Setenv(\"FOOBAR\", \"BARBAR\")\n\n\t// First, test that environment gets through to the command\n\tcommand := &com.Command{\n\t\tBinary: \"env\",\n\t\t// Note: LS_COLORS is just too loud\n\t\tEnvBlackList: []string{\n\t\t\t\"LS_COLORS\",\n\t\t},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\tassertive.Contains(t, res.Stdout, \"FOO=BAR\", \"Stdout\")\n\tassertive.Contains(t, res.Stdout, \"FOOBAR=BARBAR\", \"Stdout\")\n\n\t// Now test that we can blacklist a single variable with fully qualified name (FOO)\n\tcommand = &com.Command{\n\t\tBinary: \"env\",\n\t\tEnvBlackList: []string{\n\t\t\t\"LS_COLORS\",\n\t\t\t\"FOO\",\n\t\t},\n\t}\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.DoesNotContain(t, res.Stdout, \"FOO=\", \"Stdout\")\n\tassertive.Contains(t, res.Stdout, \"FOOBAR=BARBAR\", \"Stdout\")\n\n\t// Now test that we can blacklist multiple variables with FOO*\n\tcommand = &com.Command{\n\t\tBinary: \"env\",\n\t\tEnvBlackList: []string{\n\t\t\t\"LS_COLORS\",\n\t\t\t\"FOO*\",\n\t\t},\n\t}\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.DoesNotContain(t, res.Stdout, \"FOO=\", \"Stdout\")\n\tassertive.DoesNotContain(t, res.Stdout, \"FOOBAR=\", \"Stdout\")\n\n\t// On windows, with mingw, SYSTEMROOT,TERM and HOME (possibly others) will be forcefully added\n\t// to the environment regardless, so, we can't test \"*\" blacklist\n\tif runtime.GOOS == windows {\n\t\tt.Skip(\n\t\t\t\"Windows/mingw will always repopulate the environment with extra variables we cannot bypass\",\n\t\t)\n\t}\n\n\t// Now, test that we can blacklist everything\n\tcommand = &com.Command{\n\t\tBinary:       \"env\",\n\t\tEnvBlackList: []string{\"*\"},\n\t}\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"\", \"Stdout\")\n}\n\nfunc TestEnvWhiteList(t *testing.T) {\n\tt.Setenv(\"FOO\", \"BAR\")\n\tt.Setenv(\"FOOBAR\", \"BARBAR\")\n\n\t// Test that whitelist does let through only FOO\n\tcommand := &com.Command{\n\t\tBinary: \"env\",\n\t\tEnvWhiteList: []string{\n\t\t\t\"FOO\",\n\t\t},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\tassertive.Contains(t, res.Stdout, \"FOO=BAR\", \"Stdout\")\n\tassertive.DoesNotContain(t, res.Stdout, \"FOOBAR=\", \"Stdout\")\n\tassertive.DoesNotContain(t, res.Stdout, \"LS_COLORS=\", \"Stdout\")\n\n\t// Test that whitelist does let through FOO and FOOBAR with FOO*\n\tcommand = &com.Command{\n\t\tBinary: \"env\",\n\t\tEnvWhiteList: []string{\n\t\t\t\"FOO*\",\n\t\t},\n\t}\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\tassertive.Contains(t, res.Stdout, \"FOO=BAR\", \"Stdout\")\n\tassertive.Contains(t, res.Stdout, \"FOOBAR=BARBAR\", \"Stdout\")\n\tassertive.DoesNotContain(t, res.Stdout, \"LS_COLORS=\", \"Stdout\")\n}\n\nfunc TestEnvBlacklistWhiteList(t *testing.T) {\n\tt.Setenv(\"FOO\", \"BAR\")\n\tt.Setenv(\"FOOBAR\", \"BARBAR\")\n\n\t// Test that if both are specified, only whitelist is taken into account\n\tcommand := &com.Command{\n\t\tBinary: \"env\",\n\t\tEnvBlackList: []string{\n\t\t\t\"*\",\n\t\t\t\"FOO*\",\n\t\t},\n\t\tEnvWhiteList: []string{\n\t\t\t\"*\",\n\t\t},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\tassertive.Contains(t, res.Stdout, \"FOO=BAR\", \"Stdout\")\n\tassertive.Contains(t, res.Stdout, \"FOOBAR=BARBAR\", \"Stdout\")\n}\n\nfunc TestEnvAdd(t *testing.T) {\n\tt.Setenv(\"FOO\", \"BAR\")\n\tt.Setenv(\"BLED\", \"BLED\")\n\tt.Setenv(\"BAZ\", \"OLD\")\n\n\tcommand := &com.Command{\n\t\tBinary: \"env\",\n\t\tEnv: map[string]string{\n\t\t\t\"FOO\":  \"REPLACE\",\n\t\t\t\"BAR\":  \"NEW\",\n\t\t\t\"BLED\": \"EXPLICIT\",\n\t\t},\n\t\tEnvBlackList: []string{\n\t\t\t\"LS_COLORS\",\n\t\t\t\"BLED\",\n\t\t},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\t// Confirm explicit Env: declaration overrides os.Environ\n\tassertive.Contains(t, res.Stdout, \"FOO=REPLACE\", \"Stdout\")\n\t// Confirm explicit Env: declaration does add a new variable\n\tassertive.Contains(t, res.Stdout, \"BAR=NEW\", \"Stdout\")\n\t// Confirm explicit Env: declaration for unrelated variable does not reset os.Environ\n\tassertive.Contains(t, res.Stdout, \"BAZ=OLD\", \"Stdout\")\n\t// Confirm that blacklist only operates on os.Environ and not on any explicitly added Env: declaration\n\tassertive.Contains(t, res.Stdout, \"BLED=EXPLICIT\", \"Stdout\")\n}\n\nfunc TestStdoutStderr(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs:   []string{\"-c\", \"--\", \"printf onstdout; >&2 printf onstderr;\"},\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"onstdout\", \"Stdout\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"onstderr\", \"Stderr\", \"Stderr\")\n}\n\nfunc TestTimeoutPlain(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\t// XXX unclear if windows is really able to terminate sleep 5, so, split it up to give it a\n\t\t// chance...\n\t\tArgs:    []string{\"-c\", \"--\", \"printf one; sleep 1; sleep 1; sleep 1; sleep 1; printf two\"},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tstart := time.Now()\n\tres, err := command.Wait()\n\tend := time.Now()\n\n\tassertive.ErrorIs(t, err, com.ErrTimeout, \"Err\")\n\t// FIXME? It seems like on windows exitcode is randomly 1 on timeout\n\t// This is not a problem, as with a time-out we do not care about exit code, but is raising questions\n\t// about golang underlying implementation / command cancellation mechanism.\n\t// assertive.IsEqual(t, res.ExitCode, -1, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"one\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"\", \"Stderr\")\n\tassertive.IsLessThan(t, end.Sub(start), 2*time.Second, \"Total execution time\")\n}\n\nfunc TestTimeoutDelayed(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\t// XXX unclear if windows is really able to terminate sleep 5, so, split it up to give it a\n\t\t// chance...\n\t\tArgs:    []string{\"-c\", \"--\", \"printf one; sleep 1; sleep 1; sleep 1; sleep 1; printf two\"},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tstart := time.Now()\n\n\ttime.Sleep(2 * time.Second)\n\n\tres, err := command.Wait()\n\tend := time.Now()\n\n\tassertive.ErrorIs(t, err, com.ErrTimeout, \"Err\")\n\t// FIXME? It seems like on windows exitcode is randomly 1 on timeout\n\t// This is not a problem, as with a time-out we do not care about exit code, but is raising questions\n\t// about golang underlying implementation / command cancellation mechanism.\n\t// assertive.IsEqual(t, res.ExitCode, -1, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"one\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"\", \"Stderr\")\n\tassertive.IsLessThan(t, end.Sub(start), 3*time.Second, \"Total execution time\")\n}\n\nfunc TestPTYStdout(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == windows {\n\t\tt.Skip(\"PTY are not supported on Windows\")\n\t}\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\",\n\t\t\t\"--\",\n\t\t\t\"[ -t 1 ] || { echo not a pty; exit 41; }; printf onstdout; >&2 printf onstderr;\",\n\t\t},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\tcommand.WithPTY(false, true, false)\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"onstdout\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"onstderr\", \"Stderr\")\n}\n\nfunc TestPTYStderr(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == windows {\n\t\tt.Skip(\"PTY are not supported on Windows\")\n\t}\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\",\n\t\t\t\"--\",\n\t\t\t\"[ -t 2 ] || { echo not a pty; exit 41; }; printf onstdout; >&2 printf onstderr;\",\n\t\t},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\tcommand.WithPTY(false, false, true)\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"onstdout\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"onstderr\", \"Stderr\")\n}\n\nfunc TestPTYBoth(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == windows {\n\t\tt.Skip(\"PTY are not supported on Windows\")\n\t}\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\", \"--\", \"[ -t 1 ] && [ -t 2 ] || { echo not a pty; exit 41; }; printf onstdout; >&2 printf onstderr;\",\n\t\t},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\tcommand.WithPTY(true, true, true)\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, res.ExitCode, 0, \"ExitCode\")\n\tassertive.IsEqual(t, res.Stdout, \"onstdoutonstderr\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"\", \"Stderr\")\n}\n\nfunc TestWriteStdin(t *testing.T) {\n\tt.Parallel()\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\", \"--\",\n\t\t\t\"read line1; read line2; read line3; printf 'from stdin%s%s%s' \\\"$line1\\\" \\\"$line2\\\" \\\"$line3\\\";\",\n\t\t},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\tcommand.WithFeeder(func() io.Reader {\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\treturn strings.NewReader(\"hello first\\n\")\n\t})\n\n\tcommand.Feed(strings.NewReader(\"hello world\\n\"))\n\tcommand.Feed(strings.NewReader(\"hello again\\n\"))\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\tassertive.IsEqual(t, 0, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"from stdinhello firsthello worldhello again\", res.Stdout, \"Stdout\")\n}\n\nfunc TestWritePTYStdin(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == windows {\n\t\tt.Skip(\"PTY are not supported on Windows\")\n\t}\n\n\tcommand := &com.Command{\n\t\tBinary:  \"bash\",\n\t\tArgs:    []string{\"-c\", \"--\", \"[ -t 0 ] || { echo not a pty; exit 41; }; cat /dev/stdin\"},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\tcommand.WithPTY(true, false, false)\n\n\tcommand.WithFeeder(func() io.Reader {\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\treturn strings.NewReader(\"hello first\")\n\t})\n\n\tcommand.Feed(strings.NewReader(\"hello world\"))\n\tcommand.Feed(strings.NewReader(\"hello again\"))\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrTimeout, \"Err\")\n\tassertive.IsEqual(t, -1, res.ExitCode, \"ExitCode\")\n\tassertive.IsEqual(t, \"hello firsthello worldhello again\", res.Stdout, \"Stdout\")\n}\n\nfunc TestSignalOnCompleted(t *testing.T) {\n\tt.Parallel()\n\n\tvar usig os.Signal = syscall.SIGTERM\n\n\tcommand := &com.Command{\n\t\tBinary:  \"true\",\n\t\tTimeout: 3 * time.Second,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\t_, err = command.Wait()\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\terr = command.Signal(usig)\n\n\tassertive.ErrorIs(t, err, com.ErrFailedSendingSignal, \"Err\")\n}\n\n// FIXME: this is not working as expected, and proc.Signal returns nil error while it should not.\n// func TestSignalTooLate(t *testing.T) {\n//\tt.Parallel()\n//\n//\tvar usig os.Signal\n//\tusig = syscall.SIGTERM\n//\n//\tcommand := &com.Command{\n//\t\tBinary:  \"true\",\n//\t\tTimeout: 3 * time.Second,\n//\t}\n//\n//  err := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n//\n//\tassertive.ErrorIsNil(t, err, \"Err\")\n//\n//\ttime.Sleep(1 * time.Second)\n//\n//\terr = command.Signal(usig)\n//\n//\tassertive.ErrorIs(t, err, com.ErrFailedSendingSignal)\n// }\n\nfunc TestSignalNormal(t *testing.T) {\n\tt.Parallel()\n\n\tvar usig os.Signal = syscall.SIGTERM\n\n\tsig, ok := usig.(syscall.Signal)\n\tif !ok {\n\t\tpanic(\"sig cast failed\")\n\t}\n\n\tcommand := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\", \"--\",\n\t\t\tfmt.Sprintf(\n\t\t\t\t\"printf entry; sig_msg () { printf \\\"caught\\\"; exit 42; }; trap sig_msg %s; \"+\n\t\t\t\t\t\"printf set; while true; do sleep 0.1; done\",\n\t\t\t\tstrconv.Itoa(int(sig)),\n\t\t\t),\n\t\t},\n\t\tTimeout: 3 * time.Second,\n\t}\n\n\terr := command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\t// A bit arbitrary - just want to wait for stdout to go through before sending the signal\n\ttime.Sleep(100 * time.Millisecond)\n\n\t_ = command.Signal(usig)\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err := command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrExecutionFailed, \"Err\")\n\tassertive.IsEqual(t, res.Stdout, \"entrysetcaught\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"\", \"Stderr\")\n\tassertive.IsEqual(t, res.ExitCode, 42, \"ExitCode\")\n\tassertive.True(t, res.Signal == nil, \"Signal\")\n\n\tcommand = &com.Command{\n\t\tBinary:  \"sleep\",\n\t\tArgs:    []string{\"10\"},\n\t\tTimeout: 3 * time.Second,\n\t}\n\n\terr = command.Run(context.WithValue(context.Background(), com.LoggerKey, t))\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\terr = command.Signal(usig)\n\n\tassertive.ErrorIsNil(t, err, \"Err\")\n\n\tres, err = command.Wait()\n\n\tassertive.ErrorIs(t, err, com.ErrSignaled, \"Err\")\n\tassertive.IsEqual(t, res.Stdout, \"\", \"Stdout\")\n\tassertive.IsEqual(t, res.Stderr, \"\", \"Stderr\")\n\tassertive.IsEqual(t, res.Signal, usig, \"Signal\")\n\tassertive.IsEqual(t, res.ExitCode, -1, \"ExitCode\")\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/command_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage com\n\nimport (\n\t\"os/exec\"\n)\n\nfunc addAttr(_ *exec.Cmd) func() error {\n\treturn nil\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package com is a lightweight wrapper around golang command execution.\n// It provides a simplified API to create commands with baked-in:\n// - timeout\n// - pty\n// - environment filtering\n// - stdin manipulation\n// - proper termination of the process group\n// - wrapping commands and prepended args\npackage com\n"
  },
  {
    "path": "mod/tigron/internal/com/package_benchmark_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage com_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/com\"\n)\n\n// FIXME: this requires go 1.24 - uncomment when go 1.23 is out of support\n// func BenchmarkCommand(b *testing.B) {\n//\tfor b.Loop() {\n//\t\tcmd := com.Command{\n//\t\t\tBinary: \"true\",\n//\t\t}\n//\n//\t\t_ = cmd.Run()\n//\t\t_, _ = cmd.Wait()\n//\t}\n// }\n\nfunc BenchmarkCommandParallel(b *testing.B) {\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\tcmd := &com.Command{\n\t\t\t\tBinary: \"true\",\n\t\t\t}\n\t\t\t_ = cmd.Run(context.Background())\n\t\t\t_, _ = cmd.Wait()\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/package_example_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:add-constant\npackage com_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/com\"\n)\n\nfunc ExampleCommand() {\n\tcmd := com.Command{\n\t\tBinary: \"printf\",\n\t\tArgs:   []string{\"hello world\"},\n\t}\n\n\terr := cmd.Run(context.Background())\n\tif err != nil {\n\t\tfmt.Println(\"Run err:\", err)\n\n\t\treturn\n\t}\n\n\texec, err := cmd.Wait()\n\tif err != nil {\n\t\tfmt.Println(\"Wait err:\", err)\n\n\t\treturn\n\t}\n\n\tfmt.Println(\"Exit code:\", exec.ExitCode)\n\tfmt.Println(\"Stdout:\")\n\tfmt.Println(exec.Stdout)\n\tfmt.Println(\"Stderr:\")\n\tfmt.Println(exec.Stderr)\n\n\t// Output:\n\t// Exit code: 0\n\t// Stdout:\n\t// hello world\n\t// Stderr:\n\t//\n}\n\nfunc ExampleCommand_Signal() {\n\tcmd := com.Command{\n\t\tBinary:  \"sleep\",\n\t\tArgs:    []string{\"3600\"},\n\t\tTimeout: time.Second,\n\t}\n\n\terr := cmd.Run(context.Background())\n\tif err != nil {\n\t\tfmt.Println(\"Run err:\", err)\n\n\t\treturn\n\t}\n\n\terr = cmd.Signal(os.Interrupt)\n\tif err != nil {\n\t\tfmt.Println(\"Signal err:\", err)\n\n\t\treturn\n\t}\n\n\texec, err := cmd.Wait()\n\tfmt.Println(\"Wait err:\", err)\n\tfmt.Println(\"Exit code:\", exec.ExitCode)\n\tfmt.Println(\"Stdout:\")\n\tfmt.Println(exec.Stdout)\n\tfmt.Println(\"Stderr:\")\n\tfmt.Println(exec.Stderr)\n\tfmt.Println(\"Signal:\", exec.Signal)\n\n\t// Output:\n\t// Wait err: command execution signaled\n\t// Exit code: -1\n\t// Stdout:\n\t//\n\t// Stderr:\n\t//\n\t// Signal: interrupt\n}\n\nfunc ExampleCommand_WithPTY() {\n\tcmd := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\",\n\t\t\t\"--\",\n\t\t\t\"[ -t 1 ] || { echo not a pty; exit 41; }; printf onstdout; >&2 printf onstderr;\",\n\t\t},\n\t\tTimeout: 1 * time.Second,\n\t}\n\n\t// The PTY can be set to any of stdin, stdout, stderr\n\t// Note that PTY are supported only on Linux, Darwin and FreeBSD\n\tcmd.WithPTY(false, true, false)\n\n\terr := cmd.Run(context.Background())\n\tif err != nil {\n\t\tfmt.Println(\"Run err:\", err)\n\n\t\treturn\n\t}\n\n\texec, err := cmd.Wait()\n\tif err != nil {\n\t\tfmt.Println(\"Wait err:\", err)\n\n\t\treturn\n\t}\n\n\tfmt.Println(\"Exit code:\", exec.ExitCode)\n\tfmt.Println(\"Stdout:\")\n\tfmt.Println(exec.Stdout)\n\tfmt.Println(\"Stderr:\")\n\tfmt.Println(exec.Stderr)\n\n\t// Output:\n\t// Exit code: 0\n\t// Stdout:\n\t// onstdout\n\t// Stderr:\n\t// onstderr\n}\n\nfunc ExampleCommand_Feed() {\n\tcmd := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs: []string{\n\t\t\t\"-c\", \"--\",\n\t\t\t\"read line1; read line2; printf 'from stdin%s%s%s' \\\"$line1\\\" \\\"$line2\\\";\",\n\t\t},\n\t}\n\n\t// Use WithFeeder if you do want to perform additional tasks before feeding to stdin\n\tcmd.WithFeeder(func() io.Reader {\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\treturn strings.NewReader(\"hello world\\n\")\n\t})\n\n\t// Or use the simpler Feed if you just want to pass along content to stdin\n\t// Note that successive calls to WithFeeder / Feed will be written to stdin in order.\n\tcmd.Feed(strings.NewReader(\"hello again\\n\"))\n\n\terr := cmd.Run(context.Background())\n\tif err != nil {\n\t\tfmt.Println(\"Run err:\", err)\n\n\t\treturn\n\t}\n\n\texec, err := cmd.Wait()\n\tif err != nil {\n\t\tfmt.Println(\"Wait err:\", err)\n\n\t\treturn\n\t}\n\n\tfmt.Println(\"Exit code:\", exec.ExitCode)\n\tfmt.Println(\"Stdout:\")\n\tfmt.Println(exec.Stdout)\n\tfmt.Println(\"Stderr:\")\n\tfmt.Println(exec.Stderr)\n\n\t// Output:\n\t// Exit code: 0\n\t// Stdout:\n\t// from stdinhello worldhello again\n\t// Stderr:\n\t//\n}\n\nfunc ExampleErrTimeout() {\n\tcmd := &com.Command{\n\t\tBinary:  \"sleep\",\n\t\tArgs:    []string{\"3600\"},\n\t\tTimeout: time.Second,\n\t}\n\n\terr := cmd.Run(context.Background())\n\tif err != nil {\n\t\tfmt.Println(\"Run err:\", err)\n\n\t\treturn\n\t}\n\n\texec, err := cmd.Wait()\n\tfmt.Println(\"Wait err:\", err)\n\tfmt.Println(\"Exit code:\", exec.ExitCode)\n\tfmt.Println(\"Stdout:\")\n\tfmt.Println(exec.Stdout)\n\tfmt.Println(\"Stderr:\")\n\tfmt.Println(exec.Stderr)\n\n\t// Output:\n\t// Wait err: command timed out\n\t// Exit code: -1\n\t// Stdout:\n\t//\n\t// Stderr:\n\t//\n}\n\nfunc ExampleErrFailedStarting() {\n\tcmd := &com.Command{\n\t\tBinary: \"non-existent\",\n\t}\n\n\terr := cmd.Run(context.Background())\n\n\tfmt.Println(\"Run err:\")\n\tfmt.Println(err)\n\n\t// Output:\n\t// Run err:\n\t// command failed starting\n\t// exec: \"non-existent\": executable file not found in $PATH\n}\n\nfunc ExampleErrExecutionFailed() {\n\tcmd := &com.Command{\n\t\tBinary: \"bash\",\n\t\tArgs:   []string{\"-c\", \"--\", \"does-not-exist\"},\n\t}\n\n\terr := cmd.Run(context.Background())\n\tif err != nil {\n\t\tfmt.Println(\"Run err:\", err)\n\n\t\treturn\n\t}\n\n\texec, err := cmd.Wait()\n\tfmt.Println(\"Wait err:\", err)\n\tfmt.Println(\"Exit code:\", exec.ExitCode)\n\n\t// Output:\n\t// Wait err: command returned a non-zero exit code\n\t// Exit code: 127\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/package_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage com_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/highk\"\n)\n\nfunc TestMain(m *testing.M) {\n\t// Prep exit code\n\texitCode := 0\n\tdefer func() { os.Exit(exitCode) }()\n\n\tvar (\n\t\tsnapFile      *os.File\n\t\tbefore, after []byte\n\t)\n\n\tif os.Getenv(\"HIGHK_EXPERIMENTAL_FD\") != \"\" {\n\t\tsnapFile, _ = os.CreateTemp(\"\", \"fileleaks\")\n\t\tbefore, _ = highk.SnapshotOpenFiles(snapFile)\n\t}\n\n\texitCode = m.Run()\n\n\tif exitCode != 0 {\n\t\treturn\n\t}\n\n\tif os.Getenv(\"HIGHK_EXPERIMENTAL_FD\") != \"\" {\n\t\tafter, _ = highk.SnapshotOpenFiles(snapFile)\n\t\tdiff := highk.Diff(string(before), string(after))\n\n\t\tif len(diff) != 0 {\n\t\t\tfmt.Fprintln(os.Stderr, \"Leaking file descriptors\")\n\n\t\t\tfor _, file := range diff {\n\t\t\t\tfmt.Fprintln(os.Stderr, file)\n\t\t\t}\n\n\t\t\texitCode = 1\n\t\t}\n\t}\n\n\tif err := highk.FindGoRoutines(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Leaking go routines\")\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\n\t\texitCode = 1\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/internal/com/pipes.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage com\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\n\t\"golang.org/x/sync/errgroup\"\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/logger\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/pty\"\n)\n\nvar (\n\t// ErrFailedCreating could be returned by newStdPipes() on pty creation failure.\n\tErrFailedCreating = errors.New(\"failed acquiring pipe\")\n\t// ErrFailedReading could be returned by the ioGroup in case the go routines fails to read out of a pipe.\n\tErrFailedReading = errors.New(\"failed reading\")\n\t// ErrFailedWriting could be returned by the ioGroup in case the go routines fails to write on a pipe.\n\tErrFailedWriting = errors.New(\"failed writing\")\n)\n\ntype pipe struct {\n\treader io.ReadCloser\n\twriter io.WriteCloser\n}\n\ntype stdPipes struct {\n\tioGroup    *errgroup.Group\n\tstdin      *pipe\n\tstdout     *pipe\n\tstderr     *pipe\n\tfromStdout string\n\tfromStderr string\n\tlog        logger.Logger\n}\n\nfunc (pipes *stdPipes) closeCallee() {\n\t// Failure to close will happen:\n\t// 1. on a \"normal\" context timeout:\n\t// - command is cancelled first (which forcibly closes the callee pipes)\n\t// - then context deadline is hit\n\t// - then closeCallee is called here\n\t// 2. if we have a pty attached on both stdout and stderr\n\tpipes.log.Helper()\n\tpipes.log.Log(\"<- closing callee pipes\")\n\n\tif pipes.stdin.reader != nil {\n\t\tif closeErr := pipes.stdin.reader.Close(); closeErr != nil {\n\t\t\tpipes.log.Log(\" x failed closing callee stdin\", closeErr)\n\t\t}\n\t}\n\n\tif pipes.stdout.writer != nil {\n\t\tif closeErr := pipes.stdout.writer.Close(); closeErr != nil {\n\t\t\tpipes.log.Log(\" x failed closing callee stdout\", closeErr)\n\t\t}\n\t}\n\n\tif pipes.stderr.writer != nil {\n\t\tif closeErr := pipes.stderr.writer.Close(); closeErr != nil {\n\t\t\tpipes.log.Log(\" x failed closing callee stderr\", closeErr)\n\t\t}\n\t}\n}\n\nfunc (pipes *stdPipes) closeCaller() {\n\tpipes.log.Helper()\n\tpipes.log.Log(\"<- closing caller pipes\")\n\n\tif pipes.stdin.writer != nil {\n\t\tif closeErr := pipes.stdin.writer.Close(); closeErr != nil {\n\t\t\tpipes.log.Log(\" x failed closing caller stdin\", closeErr)\n\t\t}\n\t}\n\n\tif pipes.stdout.reader != nil {\n\t\tif closeErr := pipes.stdout.reader.Close(); closeErr != nil {\n\t\t\tpipes.log.Log(\" x failed closing caller stdout\", closeErr)\n\t\t}\n\t}\n\n\tif pipes.stderr.reader != nil {\n\t\tif closeErr := pipes.stderr.reader.Close(); closeErr != nil {\n\t\t\tpipes.log.Log(\" x failed closing caller stderr\", closeErr)\n\t\t}\n\t}\n}\n\nfunc newStdPipes(\n\tctx context.Context,\n\tlog *logger.ConcreteLogger,\n\tptyStdout, ptyStderr, ptyStdin bool,\n\twriters []func() io.Reader,\n) (pipes *stdPipes, err error) {\n\t// Close everything cleanly in case we errored\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tpipes.closeCallee()\n\t\t\tpipes.closeCaller()\n\t\t}\n\t}()\n\n\tlog = log.Set(\">\", \"pipes\")\n\tpipes = &stdPipes{\n\t\tstdin:  &pipe{},\n\t\tstdout: &pipe{},\n\t\tstderr: &pipe{},\n\t\tlog:    log,\n\t}\n\n\tvar (\n\t\tmty *os.File\n\t\ttty *os.File\n\t)\n\n\t// If we want a pty, configure it now\n\tif ptyStdout || ptyStderr || ptyStdin {\n\t\tpipes.log.Log(\"<- opening pty\")\n\n\t\tmty, tty, err = pty.Open()\n\t\tif err != nil {\n\t\t\tpipes.log.Log(\" x failed opening pty\", err)\n\n\t\t\treturn nil, errors.Join(ErrFailedCreating, err)\n\t\t}\n\n\t\tif _, err = term.MakeRaw(int(tty.Fd())); err != nil {\n\t\t\tpipes.log.Log(\" x failed making pty raw\", err)\n\n\t\t\treturn nil, errors.Join(ErrFailedCreating, err)\n\t\t}\n\t}\n\n\tif ptyStdin {\n\t\tpipes.log.Log(\"<- assigning pty to stdin\")\n\n\t\tpipes.stdin.writer = mty\n\t\tpipes.stdin.reader = tty\n\t} else if len(writers) > 0 {\n\t\tpipes.log.Log(\" * assigning a pipe to stdin as we have writers\")\n\n\t\t// Only create a pipe for stdin if we intend on writing to stdin.\n\t\t// Otherwise, processes awaiting end of stream will just hang there.\n\t\tpipes.stdin.reader, pipes.stdin.writer, err = os.Pipe()\n\t\tif err != nil {\n\t\t\tpipes.log.Log(\" x failed creating pipe for stdin\", err)\n\n\t\t\treturn nil, errors.Join(ErrFailedCreating, err)\n\t\t}\n\t}\n\n\tif ptyStdout {\n\t\tpipes.log.Log(\"<- assigning pty to stdout\")\n\n\t\tpipes.stdout.writer = tty\n\t\tpipes.stdout.reader = mty\n\t} else {\n\t\tpipes.stdout.reader, pipes.stdout.writer, err = os.Pipe()\n\t\tif err != nil {\n\t\t\tpipes.log.Log(\" x failed creating pipe for stdout\", err)\n\n\t\t\treturn nil, errors.Join(ErrFailedCreating, err)\n\t\t}\n\t}\n\n\tif ptyStderr {\n\t\tpipes.log.Log(\"<- assigning pty to stderr\")\n\n\t\tpipes.stderr.writer = tty\n\t\tpipes.stderr.reader = mty\n\t} else {\n\t\tpipes.stderr.reader, pipes.stderr.writer, err = os.Pipe()\n\t\tif err != nil {\n\t\t\tpipes.log.Log(\" x failed creating pipe for stderr\", err)\n\n\t\t\treturn nil, errors.Join(ErrFailedCreating, err)\n\t\t}\n\t}\n\n\t// Prepare ioGroup\n\tpipes.ioGroup, _ = errgroup.WithContext(ctx)\n\n\t// Writers to stdin\n\tpipes.ioGroup.Go(func() error {\n\t\tpipes.log.Log(\"-> about to write to stdin\")\n\n\t\tfor _, writer := range writers {\n\t\t\tif _, copyErr := io.Copy(pipes.stdin.writer, writer()); copyErr != nil {\n\t\t\t\tpipes.log.Log(\" x failed writing to stdin\", copyErr)\n\n\t\t\t\treturn errors.Join(ErrFailedWriting, copyErr)\n\t\t\t}\n\t\t}\n\n\t\tpipes.log.Log(\"<- done writing to stdin\")\n\n\t\tif !ptyStdin && pipes.stdin.writer != nil {\n\t\t\tif closeErr := pipes.stdin.writer.Close(); closeErr != nil {\n\t\t\t\tpipes.log.Log(\" x failed closing caller stdin\", closeErr)\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\n\t// Read stdout...\n\tpipes.ioGroup.Go(func() error {\n\t\tpipes.log.Log(\"-> about to read stdout\")\n\n\t\tbuf := &bytes.Buffer{}\n\t\t_, copyErr := io.Copy(buf, pipes.stdout.reader)\n\t\tpipes.fromStdout = buf.String()\n\n\t\tif copyErr != nil {\n\t\t\tpipes.log.Log(\" x failed reading from stdout\", copyErr)\n\n\t\t\tcopyErr = errors.Join(ErrFailedReading, copyErr)\n\t\t}\n\n\t\tpipes.log.Log(\"<- done reading stdout\")\n\n\t\treturn copyErr\n\t})\n\n\t// ... and stderr (if not the same - eg: pty)\n\tif pipes.stderr.reader != pipes.stdout.reader {\n\t\tpipes.ioGroup.Go(func() error {\n\t\t\tpipes.log.Log(\"-> about to read stderr\")\n\n\t\t\tbuf := &bytes.Buffer{}\n\t\t\t_, copyErr := io.Copy(buf, pipes.stderr.reader)\n\t\t\tpipes.fromStderr = buf.String()\n\n\t\t\tif copyErr != nil {\n\t\t\t\tpipes.log.Log(\" x failed reading from stderr\", copyErr)\n\n\t\t\t\tcopyErr = errors.Join(ErrFailedReading, copyErr)\n\t\t\t}\n\n\t\t\tpipes.log.Log(\"<- done reading stderr\")\n\n\t\t\treturn copyErr\n\t\t})\n\t}\n\n\treturn pipes, nil\n}\n"
  },
  {
    "path": "mod/tigron/internal/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package internal provides an assert library, pty, a command wrapper, and a leak detection library\n// for internal use in Tigron. The objective for these is not to become generic use-cases libraries,\n// but instead to deliver what Tigron\n// needs in the simplest possible form.\npackage internal\n"
  },
  {
    "path": "mod/tigron/internal/exit.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage internal\n\n// This is duplicated from `expect` to avoid circular imports.\nconst (\n\tExitCodeSuccess     = 0\n\tExitCodeGenericFail = -10\n\tExitCodeNoCheck     = -11\n\tExitCodeTimeout     = -12\n\tExitCodeSignaled    = -13\n\t// ExitCodeCancelled = -14.\n)\n"
  },
  {
    "path": "mod/tigron/internal/formatter/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package formatter provides simple formatting helpers for internal consumption.\npackage formatter\n"
  },
  {
    "path": "mod/tigron/internal/formatter/formatter.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage formatter\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"golang.org/x/text/width\"\n)\n\nconst (\n\tmaxLineLength = 110\n\tmaxLines      = 50\n\tkMaxLength    = 7\n\tspacer        = \" \"\n)\n\n// Table formats a `n x 2` dataset into a series of n rows by 2 columns.\n//\n//nolint:mnd // Too annoying\nfunc Table(data [][]any, mark string) string {\n\tvar output string\n\n\tfor _, row := range data {\n\t\tkey := fmt.Sprintf(\"%v\", row[0])\n\t\tvalue := strings.ReplaceAll(fmt.Sprintf(\"%v\", row[1]), \"\\t\", \"  \")\n\t\tvalue = strings.ReplaceAll(value, \"\\r\", \"\")\n\n\t\toutput += fmt.Sprintf(\"+%s+\\n\", strings.Repeat(mark, maxLineLength-2))\n\n\t\tfor _, line := range chunk(value, maxLineLength-kMaxLength-7, maxLines) {\n\t\t\toutput += fmt.Sprintf(\n\t\t\t\t\"| %s | %s |\\n\",\n\t\t\t\t// Keys longer than one line of kMaxLength will be striped to one line\n\t\t\t\tchunk(key, kMaxLength, 1)[0],\n\t\t\t\tline,\n\t\t\t)\n\t\t\tkey = \"\"\n\t\t}\n\t}\n\n\toutput += fmt.Sprintf(\"+%s+\", strings.Repeat(mark, maxLineLength-2))\n\n\treturn output\n}\n\n// chunk does take a string and split it in lines of maxLength size, accounting for characters display width.\nfunc chunk(s string, maxLength, maxLines int) []string {\n\tchunks := []string{}\n\n\trunes := []rune(s)\n\n\tsize := 0\n\tstart := 0\n\n\tfor index := range runes {\n\t\tvar segment string\n\n\t\tswitch width.LookupRune(runes[index]).Kind() {\n\t\tcase width.EastAsianWide, width.EastAsianFullwidth:\n\t\t\tsize += 2\n\t\tcase width.EastAsianAmbiguous, width.Neutral, width.EastAsianHalfwidth, width.EastAsianNarrow:\n\t\t\tsize++\n\t\tdefault:\n\t\t\tsize++\n\t\t}\n\n\t\tswitch {\n\t\tcase runes[index] == '\\n':\n\t\t\t// Met a line-break. Pad to size (removing the line break)\n\t\t\tsegment = string(runes[start:index])\n\t\t\tsegment += strings.Repeat(spacer, maxLength-size+1)\n\t\t\tstart = index + 1\n\t\t\tsize = 0\n\t\tcase size == maxLength:\n\t\t\t// Line is full. Add the segment.\n\t\t\tsegment = string(runes[start : index+1])\n\t\t\tstart = index + 1\n\t\t\tsize = 0\n\t\tcase size > maxLength:\n\t\t\t// Last char was double width. Push it back to next line, and pad with a single space.\n\t\t\tsegment = string(runes[start:index]) + spacer\n\t\t\tstart = index\n\t\t\tsize = 2\n\t\tcase index == len(runes)-1:\n\t\t\t// End of string. Pad it to size.\n\t\t\tsegment = string(runes[start : index+1])\n\t\t\tsegment += strings.Repeat(spacer, maxLength-size)\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\tchunks = append(chunks, segment)\n\t}\n\n\t// If really long, preserve the starting first quarter, the trailing three quarters, and inform.\n\tactualLength := len(chunks)\n\tif actualLength > maxLines {\n\t\tabbreviator := fmt.Sprintf(\"... %d lines are being ignored...\", actualLength-maxLines)\n\t\tchunks = append(\n\t\t\tappend(chunks[0:maxLines/4], abbreviator+strings.Repeat(spacer, maxLength-len(abbreviator))),\n\t\t\tchunks[actualLength-maxLines*3/4:]...,\n\t\t)\n\t\tchunks = append(\n\t\t\t[]string{\n\t\t\t\tfmt.Sprintf(\"Actual content is %d lines long and has been abbreviated to %d\\n\", actualLength, maxLines),\n\t\t\t\tstrings.Repeat(spacer, maxLength),\n\t\t\t},\n\t\t\tchunks...,\n\t\t)\n\t} else if actualLength == 0 {\n\t\tchunks = []string{strings.Repeat(spacer, maxLength)}\n\t}\n\n\treturn chunks\n}\n"
  },
  {
    "path": "mod/tigron/internal/formatter/osc8.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage formatter\n\nimport \"fmt\"\n\n// OSC8 hyperlinks implementation.\ntype OSC8 struct {\n\tLocation string `json:\"location\"`\n\tLine     int    `json:\"line\"`\n\tText     string `json:\"text\"`\n}\n\nfunc (o *OSC8) String() string {\n\t// FIXME: not sure if any desktop software does support line numbers anchors?\n\t// FIXME: test that the terminal is able to display these and fallback to printing the information if not.\n\treturn fmt.Sprintf(\"\\x1b]8;;%s#%d:1\\x07%s\\x1b]8;;\\x07\"+\"\\u001b[0m\", o.Location, o.Line, o.Text)\n}\n"
  },
  {
    "path": "mod/tigron/internal/highk/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package highk (for \"high-κ dielectric\") is a highly experimental leak detection library (for file descriptors and go\n// routines).\n// It is purely internal for now and used only as part of the tests for tigron.\n// TODO:\n// - get rid of lsof and implement in go\n// - investigate feasibility of adding automatic leak detection for any test using tigron\n// - investigate feasibility of adding leak detection for tested binaries\n// - review usefulness of uber goroutines leak library\npackage highk\n"
  },
  {
    "path": "mod/tigron/internal/highk/fileleak.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage highk\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n)\n\n// FIXME: it seems that lsof (or go test) is interfering and showing false positive KQUEUE / inodes\n//\n//nolint:gochecknoglobals // FIXME rewrite all of this anyhow\nvar whitelist = map[string]bool{\n\t\"KQUEUE\":  true,\n\t\"a_inode\": true,\n}\n\n// SnapshotOpenFiles will capture the list of currently open-files for the process.\n//\n//nolint:wrapcheck // FIXME: work in progress\nfunc SnapshotOpenFiles(file *os.File) ([]byte, error) {\n\t// Using a buffer would add a pipe to the list of files.\n\t// Reimplement this stuff in go ASAP and toss lsof instead of passing around fd.\n\t_, _ = file.Seek(0, 0)\n\t_ = file.Truncate(0)\n\n\texe, err := exec.LookPath(\"lsof\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t//nolint:gosec // G204 is fine here\n\tcmd := exec.Command(exe, \"-nP\", \"-p\", strconv.Itoa(syscall.Getpid()))\n\tcmd.Stdout = file\n\n\t_ = cmd.Run()\n\n\t_, _ = file.Seek(0, 0)\n\n\treturn io.ReadAll(file)\n}\n\n// Diff will return a slice of strings showing the diff between two strings.\nfunc Diff(one, two string) []string {\n\taone := strings.Split(one, \"\\n\")\n\tatwo := strings.Split(two, \"\\n\")\n\n\tslices.Sort(aone)\n\tslices.Sort(atwo)\n\n\tloss := make(map[string]bool, len(aone))\n\tgain := map[string]bool{}\n\n\tfor _, v := range aone {\n\t\tloss[v] = true\n\t}\n\n\tfor _, v := range atwo {\n\t\tif _, ok := loss[v]; ok {\n\t\t\tdelete(loss, v)\n\t\t} else {\n\t\t\tgain[v] = true\n\t\t}\n\t}\n\n\tdiff := []string{}\n\n\tfor key := range loss {\n\t\tlegit := true\n\n\t\tfor wl := range whitelist {\n\t\t\tif strings.Contains(key, wl) {\n\t\t\t\tlegit = false\n\t\t\t}\n\t\t}\n\n\t\tif legit {\n\t\t\tdiff = append(diff, \"- \"+key)\n\t\t}\n\t}\n\n\tfor key := range gain {\n\t\tlegit := true\n\n\t\tfor wl := range whitelist {\n\t\t\tif strings.Contains(key, wl) {\n\t\t\t\tlegit = false\n\t\t\t}\n\t\t}\n\n\t\tif legit {\n\t\t\tdiff = append(diff, \"+ \"+key)\n\t\t}\n\t}\n\n\treturn diff\n}\n"
  },
  {
    "path": "mod/tigron/internal/highk/goroutines.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage highk\n\nimport (\n\t\"go.uber.org/goleak\"\n)\n\n// FindGoRoutines retrieves leaked go routines, which are returned as an error.\n//\n//nolint:wrapcheck // FIXME: work in progress\nfunc FindGoRoutines() error {\n\treturn goleak.Find()\n}\n"
  },
  {
    "path": "mod/tigron/internal/logger/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package logger is a very simple stub allowing developers to hook whatever logger they want to debug internal behavior\n// of the com package.\n// The passed logger just has to implement the Log(args...any) method.\n// Typically, that would be a *testing.T.\npackage logger\n"
  },
  {
    "path": "mod/tigron/internal/logger/logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logger\n\nimport (\n\t\"time\"\n)\n\n// Logger describes a passed logger, useful only for debugging.\ntype Logger interface {\n\tLog(args ...any)\n\tHelper()\n}\n\n// ConcreteLogger is a simple struct allowing to set additional metadata for a Logger.\ntype ConcreteLogger struct {\n\tmeta       []any\n\twrappedLog Logger\n}\n\n// Set allows attaching metadata to the logger display.\nfunc (cl *ConcreteLogger) Set(key, value string) *ConcreteLogger {\n\treturn &ConcreteLogger{\n\t\tmeta:       append(cl.meta, \"[\"+key+\"=\"+value+\"]\"),\n\t\twrappedLog: cl.wrappedLog,\n\t}\n}\n\n// Log prints a message using the Log method of the embedded Logger.\nfunc (cl *ConcreteLogger) Log(args ...any) {\n\tif cl.wrappedLog != nil {\n\t\tcl.wrappedLog.Helper()\n\t\tcl.wrappedLog.Log(\n\t\t\tappend(\n\t\t\t\tappend([]any{\"[\" + time.Now().Format(time.RFC3339) + \"]\"}, cl.meta...),\n\t\t\t\targs...)...)\n\t}\n}\n\n// Helper is called so that traces from t.Log are not linking to the logger methods themselves.\nfunc (cl *ConcreteLogger) Helper() {\n\tif cl.wrappedLog != nil {\n\t\tcl.wrappedLog.Helper()\n\t}\n}\n\n// NewLogger returns a new concrete logger from a struct satisfying the Logger interface.\nfunc NewLogger(logger Logger) *ConcreteLogger {\n\treturn &ConcreteLogger{\n\t\twrappedLog: logger,\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/internal/mimicry/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package mimicry provides a very rough and rudimentary mimicry library to help with internal tigron testing.\n// It does not require generation, does not abuse reflect (too much), and keeps the amount of boilerplate baloney to a\n// minimum.\n// This is NOT a generic mock library. Use something else if you need one.\npackage mimicry\n"
  },
  {
    "path": "mod/tigron/internal/mimicry/doc.md",
    "content": "# [INTERNAL] [EXPERIMENTAL] Mimicry\n\n## Creating a Mock\n\n```golang\npackage mymock\n\nimport \"github.com/containerd/nerdctl/mod/tigron/internal/mimicry\"\n\n// Let's assume we want to mock the following, likely defined somewhere else\n// type InterfaceToBeMocked interface {\n//    SomeMethod(one string, two int) error\n// }\n\n// Compile time ensure the mock does fulfill the interface\nvar _ InterfaceToBeMocked = &MyMock{}\n\ntype MyMock struct {\n    // Embed mimicry core\n\tmimicry.Core\n}\n\n// First, describe function parameters and return values.\ntype (\n    MyMockSomeMethodIn struct {\n        one string\n        two int\n    }\n\n    MyMockSomeMethodOut = error\n)\n\n// Satisfy the interface + wire-in the handler mechanism\n\nfunc (m *MyMock) SomeMethod(one string, two int) error {\n\t// Call mimicry method Retrieve that will record the call, and return a custom handler if one is defined\n    if handler := m.Retrieve(); handler != nil {\n\t\t// Call the optional handler if there is one.\n        return handler.(mimicry.Function[MyMockSomeMethodIn, MyMockSomeMethodOut])(MyMockSomeMethodIn{\n            one: one,\n            two: two,\n        })\n    }\n\n    return nil\n}\n```\n\n\n## Using a Mock\n\nFor consumers, the simplest way to use the mock is to inspect calls after the fact:\n\n```golang\npackage mymock\n\nimport \"testing\"\n\n// This is the code you want to test, that does depend on the interface we are mocking.\n// func functionYouWantToTest(o InterfaceToBeMocked, i int) {\n//    o.SomeMethod(\"lala\", i)\n// }\n\nfunc TestOne(t *testing.T) {\n\t// Create the mock from above\n    mocky := &MyMock{}\n\n\t// Call the function you want to test\n    functionYouWantToTest(mocky, 42)\n    functionYouWantToTest(mocky, 123)\n\n    // Now you can inspect the calls log for that function.\n    report := mocky.Report(InterfaceToBeMocked.SomeMethod)\n    t.Log(\"Number of times it was called:\", len(report))\n    t.Log(\"Inspecting the last call:\")\n    t.Log(mimicry.PrintCall(report[len(report)-1]))\n}\n```\n\n## Using handlers\n\nImplementing handlers allows active interception of the calls for more elaborate scenarios.\n\n```golang\npackage main_test\n\nimport \"testing\"\n\n// The method you want to test against the mock\n// func functionYouWantToTest(o InterfaceToBeMocked, i int) {\n//    o.SomeMethod(\"lala\", i)\n// }\n\nfunc TestTwo(t *testing.T) {\n\t// Create the base mock\n    mocky := &MyMock{}\n\n\t// Declare a custom handler for the method `SomeMethod`\n    mocky.Register(InterfaceToBeMocked.SomeMethod, func(in MyMockSomeMethodIn) MyMockSomeMethodOut {\n        t.Log(\"Got parameters\", in)\n\n        // We want to fail on that\n        if in.two == 42 {\n            // Print out the callstack\n            report := mocky.Report(InterfaceToBeMocked.SomeMethod)\n            t.Log(mimicry.PrintCall(report[len(report)-1]))\n            t.Error(\"We do not want to ever receive 42. Inspect trace above.\")\n        }else{\n            t.Log(\"all fine - we did not see 42\")\n        }\n\n        return nil\n    })\n\n    functionYouWantToTest(mocky, 123)\n    functionYouWantToTest(mocky, 42)\n}\n```\n"
  },
  {
    "path": "mod/tigron/internal/mimicry/mimicry.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mimicry\n\nimport (\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst callStackMaxDepth = 5\n\nvar _ Mocked = &Core{}\n\n// Mocked is the interface representing a fully-mocking struct (both for Designer and Consumer).\ntype Mocked interface {\n\tConsumer\n\tDesigner\n}\n\n// Function is a generics for any mockable function.\ntype Function[IN any, OUT any] = func(IN) OUT\n\n// Consumer is the mock interface exposed to mock users.\n// It defines a handful of methods to register a custom handler, get and reset calls reports.\ntype Consumer interface {\n\tRegister(fun, handler any)\n\tReport(fun any) []*Call\n\tReset()\n}\n\n// Designer is the mock interface that mock creators can use to write function boilerplate.\ntype Designer interface {\n\tRetrieve(args ...any) any\n}\n\n// Core is a concrete implementation that any mock struct can embed to satisfy Mocked.\n// FIXME: this is not safe to use concurrently.\ntype Core struct {\n\tmockedFunctions map[string]any\n\tcallsList       map[string][]*Call\n}\n\n// Reset does reset the callStack records for all functions.\nfunc (mi *Core) Reset() {\n\tmi.callsList = make(map[string][]*Call)\n}\n\n// Report returns all Calls made to the referenced function.\nfunc (mi *Core) Report(fun any) []*Call {\n\tfid := getFunID(fun)\n\n\tif mi.callsList == nil {\n\t\tmi.callsList = make(map[string][]*Call)\n\t}\n\n\tret, ok := mi.callsList[fid]\n\tif !ok {\n\t\tret = []*Call{}\n\t}\n\n\treturn ret\n}\n\n// Retrieve returns a registered custom handler for that function if there is one.\nfunc (mi *Core) Retrieve(args ...any) any {\n\t// Get the frames.\n\tpc := make([]uintptr, callStackMaxDepth)\n\t//nolint:mnd // Whatever mnd...\n\tn := runtime.Callers(2, pc)\n\tcallersFrames := runtime.CallersFrames(pc[:n])\n\t// This is the frame associate with the mock currently calling retrieve, so, extract the short\n\t// name of it.\n\tframe, _ := callersFrames.Next()\n\tnm := strings.Split(frame.Function, \".\")\n\tfid := nm[len(nm)-1]\n\n\t// Initialize callsList if need be\n\tif mi.callsList == nil {\n\t\tmi.callsList = make(map[string][]*Call)\n\t}\n\n\t// Now, get the remaining frames until we hit the go library or the call stack depth limit.\n\tframes := []*Frame{}\n\n\tfor range callStackMaxDepth {\n\t\tframe, _ = callersFrames.Next()\n\t\tif isStd(frame.Function) {\n\t\t\tbreak\n\t\t}\n\n\t\tframes = append(frames, &Frame{\n\t\t\tFile:     frame.File,\n\t\t\tFunction: frame.Function,\n\t\t\tLine:     frame.Line,\n\t\t})\n\t}\n\n\t// Stuff into the call list.\n\tmi.callsList[fid] = append(mi.callsList[fid], &Call{\n\t\tTime:   time.Now(),\n\t\tArgs:   args,\n\t\tFrames: frames,\n\t})\n\n\t// See if we have a registered handler and return it if so.\n\tif ret, ok := mi.mockedFunctions[fid]; ok {\n\t\treturn ret\n\t}\n\n\treturn nil\n}\n\n// Register does declare an explicit handler for that function.\nfunc (mi *Core) Register(fun, handler any) {\n\tif mi.mockedFunctions == nil {\n\t\tmi.mockedFunctions = make(map[string]any)\n\t}\n\n\tmi.mockedFunctions[getFunID(fun)] = handler\n}\n\nfunc getFunID(fun any) string {\n\t// The point of keeping only the func name is to avoid type mismatch dependent on what interface\n\t// is used by the consumer.\n\torigin := runtime.FuncForPC(reflect.ValueOf(fun).Pointer()).Name()\n\tseg := strings.Split(origin, \".\")\n\torigin = seg[len(seg)-1]\n\n\treturn origin\n}\n"
  },
  {
    "path": "mod/tigron/internal/mimicry/print.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mimicry\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/formatter\"\n)\n\nconst (\n\tmaxLineLength       = 110\n\tsourceLineAround    = 2\n\tbreakpointDecorator = \"🔴\"\n\tframeDecorator      = \"⬆️\"\n)\n\n// PrintCall does fancy format a Call.\nfunc PrintCall(call *Call) string {\n\tsectionSeparator := strings.Repeat(\"_\", maxLineLength)\n\n\tdebug := [][]any{\n\t\t{\"Arguments\", call.Args},\n\t\t{\"Time\", call.Time.Format(time.RFC3339)},\n\t}\n\n\toutput := []string{\n\t\tformatter.Table(debug, \"-\"),\n\t\tsectionSeparator,\n\t}\n\n\tmarker := breakpointDecorator\n\tfor _, v := range call.Frames {\n\t\toutput = append(output,\n\t\t\tv.String(),\n\t\t\tsectionSeparator,\n\t\t\tv.Excerpt(sourceLineAround, marker),\n\t\t\tsectionSeparator,\n\t\t)\n\t\tmarker = frameDecorator\n\t}\n\n\treturn \"\\n\" + strings.Join(output, \"\\n\")\n}\n"
  },
  {
    "path": "mod/tigron/internal/mimicry/stack.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mimicry\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/formatter\"\n)\n\nconst (\n\thyperlinkDecorator = \"🔗\"\n\tintoDecorator      = \"↪\"\n)\n\n// Call is used to store information about a call to a function of the mocked struct, including\n// arguments, time, and\n// frames.\ntype Call struct {\n\tTime   time.Time\n\tArgs   []any\n\tFrames []*Frame\n}\n\n// A Frame stores information about a call code-path: file, line number and function name.\ntype Frame struct {\n\tFile     string\n\tFunction string\n\tLine     int\n}\n\n// String returns an OSC8 hyperlink pointing to the source along with package and function\n// information.\n// FIXME: we are mixing formatting concerns here.\n// FIXME: this is gibberish to read.\nfunc (f *Frame) String() string {\n\tcwd, _ := os.Getwd()\n\n\trel, err := filepath.Rel(cwd, f.File)\n\tif err != nil {\n\t\trel = f.File\n\t}\n\n\tspl := strings.Split(f.Function, \".\")\n\tfun := spl[len(spl)-1]\n\tmod := strings.Join(spl[:len(spl)-1], \".\")\n\n\treturn hyperlinkDecorator + \" \" + (&formatter.OSC8{\n\t\tLocation: \"file://\" + f.File,\n\t\tLine:     f.Line,\n\t\tText:     fmt.Sprintf(\"%s:%d\", rel, f.Line),\n\t}).String() +\n\t\tfmt.Sprintf(\n\t\t\t\"\\n%6s package %q\\n\",\n\t\t\tintoDecorator,\n\t\t\tmod,\n\t\t) +\n\t\tfmt.Sprintf(\n\t\t\t\"%8s func %s\",\n\t\t\t\" \"+intoDecorator,\n\t\t\tfun,\n\t\t)\n}\n\n// Excerpt will return the source code content associated with the frame + a few lines around.\nfunc (f *Frame) Excerpt(add int, marker string) string {\n\tsource, err := os.Open(f.File)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tdefer func() {\n\t\t_ = source.Close()\n\t}()\n\n\tindex := 1\n\tscanner := bufio.NewScanner(source)\n\n\tfor ; scanner.Err() == nil && index < f.Line-add; index++ {\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\n\t\t_ = scanner.Text()\n\t}\n\n\tcapt := []string{}\n\n\tfor ; scanner.Err() == nil && index <= f.Line+add; index++ {\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\n\t\tline := scanner.Text()\n\t\tif index == f.Line {\n\t\t\tline = fmt.Sprintf(\"%6d %s %s\", index, marker, line)\n\t\t} else {\n\t\t\t// FIXME: see other similar note. Rune counting is not display-width, so...\n\t\t\tline = fmt.Sprintf(\"%6d %*s %s\", index, utf8.RuneCountInString(marker), \"\", line)\n\t\t}\n\n\t\tcapt = append(capt, line)\n\t}\n\n\treturn strings.Join(capt, \"\\n\")\n}\n\nfunc isStd(in string) bool {\n\treturn !strings.Contains(in, \"/\")\n}\n"
  },
  {
    "path": "mod/tigron/internal/mocks/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package mocks provides a collection of tigron internal mocks to ease testing.\npackage mocks\n"
  },
  {
    "path": "mod/tigron/internal/mocks/t.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//nolint:forcetypeassert\n//revive:disable:exported,max-public-structs,package-comments\npackage mocks\n\n// FIXME: type asserts...\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/mimicry\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\ntype T interface {\n\ttig.T\n\tmimicry.Consumer\n}\n\ntype (\n\tTHelperIn  struct{}\n\tTHelperOut struct{}\n\n\tTFailIn  struct{}\n\tTFailOut struct{}\n\n\tTFailNowIn  struct{}\n\tTFailNowOut struct{}\n\n\tTLogIn  []any\n\tTLogOut struct{}\n\n\tTNameIn  struct{}\n\tTNameOut = string\n\n\tTTempDirIn  struct{}\n\tTTempDirOut = string\n\n\tTSkipIn  []any\n\tTSkipOut struct{}\n)\n\ntype MockT struct {\n\tmimicry.Core\n}\n\nfunc (m *MockT) Helper() {\n\tif handler := m.Retrieve(); handler != nil {\n\t\thandler.(mimicry.Function[THelperIn, THelperOut])(THelperIn{})\n\t}\n}\n\nfunc (m *MockT) FailNow() {\n\tif handler := m.Retrieve(); handler != nil {\n\t\thandler.(mimicry.Function[TFailNowIn, TFailNowOut])(TFailNowIn{})\n\t}\n}\n\nfunc (m *MockT) Fail() {\n\tif handler := m.Retrieve(); handler != nil {\n\t\thandler.(mimicry.Function[TFailIn, TFailOut])(TFailIn{})\n\t}\n}\n\nfunc (m *MockT) Log(args ...any) {\n\tif handler := m.Retrieve(args...); handler != nil {\n\t\thandler.(mimicry.Function[TLogIn, TLogOut])(args)\n\t}\n}\n\nfunc (m *MockT) Name() string {\n\tif handler := m.Retrieve(); handler != nil {\n\t\treturn handler.(mimicry.Function[TNameIn, TNameOut])(TNameIn{})\n\t}\n\n\treturn \"\"\n}\n\nfunc (m *MockT) TempDir() string {\n\tif handler := m.Retrieve(); handler != nil {\n\t\treturn handler.(mimicry.Function[TTempDirIn, TTempDirOut])(TTempDirIn{})\n\t}\n\n\treturn \"\"\n}\n\nfunc (m *MockT) Skip(args ...any) {\n\tif handler := m.Retrieve(); handler != nil {\n\t\thandler.(mimicry.Function[TSkipIn, TSkipOut])(args)\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/internal/pty/pty.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package pty provides a simple to manipulate pty Open method.\n// Note that creack is MIT licensed, making it better to depend on it rather than using derived code\n// here. Underlying implementation is OK though they have more (unnecessary to us) features and do\n// not follow the same coding standards.\npackage pty\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\tcreack \"github.com/creack/pty\"\n)\n\nvar (\n\t// ErrFailure is wrapping system pty creation failure returned by Open().\n\tErrFailure = errors.New(\"pty failure\")\n\t// ErrUnsupportedPlatform is returned by Open() on unsupported platforms.\n\tErrUnsupportedPlatform = errors.New(\"pty not supported on this platform\")\n)\n\n// Open will allocate and return a new pty.\nfunc Open() (pty, tty *os.File, err error) {\n\tpty, tty, err = creack.Open()\n\tif err != nil {\n\t\tif errors.Is(err, creack.ErrUnsupported) {\n\t\t\terr = errors.Join(ErrUnsupportedPlatform, err)\n\t\t} else {\n\t\t\terr = errors.Join(ErrFailure, err)\n\t\t}\n\t}\n\n\treturn pty, tty, err\n}\n"
  },
  {
    "path": "mod/tigron/require/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package require provides a set of concrete test.Requirements to express the need for a specific\n// architecture, OS, or binary, along with Not() and All() which allow Requirements composition.\npackage require\n"
  },
  {
    "path": "mod/tigron/require/doc.md",
    "content": "# Requirements\n\nAny `test.Case` has a `Require` property that allow enforcing specific, per-test requirements.\n\nA `Requirement` is expected to make you `Skip` tests when the environment does not match\nexpectations.\n\nA number of ready-made requirements are provided:\n\n```go\nrequire.Windows // a test runs only on Windows\nrequire.Linux // a test runs only on Linux\ntest.Darwin // a test runs only on Darwin\ntest.OS(name string) // a test runs only on the OS `name`\nrequire.Binary(name string) // a test requires the binary `name` to be in the PATH\nrequire.Not(req Requirement) // a test runs only if the opposite of the requirement `req` is fulfilled\nrequire.All(req ...Requirement) // a test runs only if all requirements are fulfilled\n```\n\n## Custom requirements\n\nA requirement is a simple struct (`test.Requirement`), that must provide a `Check` function.\n\n`Check` function signature is `func(data Data, helpers Helpers) (bool, string)`, that is expected\nto return `true` if the environment is fine to run that test, or `false` if not.\nThe second parameter should return a meaningful message explaining what the requirement is.\n\nFor example: `found kernel version > 5.0`.\n\nGiven requirements can be negated with `require.Not(req)`, your message should describe the state of the environment\nand not whether the conditions are met (or not met).\n\nA `test.Requirement` can optionally provide custom `Setup` and `Cleanup` routines, in case you need to perform\nspecific operations before the test run (and cleanup something after).\n\n`Setup/Cleanup` will only run if the requirement `Check` returns true.\n\nHere is for example how the `require.Binary` method is implemented:\n\n```go\npackage thing\n\nfunc Binary(name string) *test.Requirement {\n    return &test.Requirement{\n        Check: func(_ test.Data, _ test.Helpers) (bool, string) {\n            mess := fmt.Sprintf(\"executable %q has been found in PATH\", name)\n            ret := true\n            if _, err := exec.LookPath(name); err != nil {\n                ret = false\n                mess = fmt.Sprintf(\"executable %q doesn't exist in PATH\", name)\n            }\n\n            return ret, mess\n        },\n    }\n}\n```\n\n## Gotcha\n\nObviously, `test.Not(otherreq)` will NOT perform any `Setup/Cleanup` provided by `otherreq`.\n\nAmbient requirements are currently undocumented.\n"
  },
  {
    "path": "mod/tigron/require/requirement.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage require\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"runtime\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n)\n\n// Binary requires a certain binary to be present in the PATH.\nfunc Binary(name string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(_ test.Data, _ test.Helpers) (bool, string) {\n\t\t\tmess := fmt.Sprintf(\"executable %q has been found in PATH\", name)\n\t\t\tret := true\n\t\t\tif _, err := exec.LookPath(name); err != nil {\n\t\t\t\tret = false\n\t\t\t\tmess = fmt.Sprintf(\"executable %q doesn't exist in PATH\", name)\n\t\t\t}\n\n\t\t\treturn ret, mess\n\t\t},\n\t}\n}\n\n// OS requires a specific operating system (matched against runtime.GOOS).\nfunc OS(os string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(_ test.Data, _ test.Helpers) (bool, string) {\n\t\t\treturn runtime.GOOS == os, fmt.Sprintf(\"current operating system is %q\", runtime.GOOS)\n\t\t},\n\t}\n}\n\n// Arch requires a specific CPU architecture (matched against runtime.GOARCH).\nfunc Arch(arch string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(_ test.Data, _ test.Helpers) (bool, string) {\n\t\t\treturn runtime.GOARCH == arch, fmt.Sprintf(\"current architecture is %q\", runtime.GOARCH)\n\t\t},\n\t}\n}\n\n//nolint:gochecknoglobals\nvar (\n\t// Amd64 requires an intel x86_64 CPU.\n\tAmd64 = Arch(\"amd64\")\n\t// Arm64 requires an ARM CPU.\n\tArm64 = Arch(\"arm64\")\n\t// Windows requires the OS to be Windows.\n\tWindows = OS(\"windows\")\n\t// Linux requires the OS to be Linux.\n\tLinux = OS(\"linux\")\n\t// Darwin requires the OS to be macOS.\n\tDarwin = OS(\"darwin\")\n)\n\n// Not will negate another requirement\n// Note that it will always *ignore* any setup and cleanup inside the wrapped requirement.\nfunc Not(requirement *test.Requirement) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\tret, mess := requirement.Check(data, helpers)\n\n\t\t\treturn !ret, mess\n\t\t},\n\t}\n}\n\n// All combines several other requirements together.\nfunc All(requirements ...*test.Requirement) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\tret := true\n\t\t\tmess := \"\"\n\t\t\tvar subMess string\n\t\t\tfor _, requirement := range requirements {\n\t\t\t\tret, subMess = requirement.Check(data, helpers)\n\t\t\t\tmess += \"\\n\" + subMess\n\n\t\t\t\tif !ret {\n\t\t\t\t\treturn ret, mess\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn ret, mess\n\t\t},\n\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\tfor _, requirement := range requirements {\n\t\t\t\tif requirement.Setup != nil {\n\t\t\t\t\trequirement.Setup(data, helpers)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\tfor _, requirement := range requirements {\n\t\t\t\tif requirement.Cleanup != nil {\n\t\t\t\t\trequirement.Cleanup(data, helpers)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/require/requirement_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage require_test\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n)\n\nconst (\n\tdarwin  = \"darwin\"\n\twindows = \"windows\"\n\tlinux   = \"linux\"\n)\n\nfunc TestRequire(t *testing.T) {\n\tt.Parallel()\n\n\tvar pass bool\n\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\tpass, _ = require.Windows.Check(nil, nil)\n\tcase \"linux\":\n\t\tpass, _ = require.Linux.Check(nil, nil)\n\tcase \"darwin\":\n\t\tpass, _ = require.Darwin.Check(nil, nil)\n\tdefault:\n\t\tpass, _ = require.OS(runtime.GOOS).Check(nil, nil)\n\t}\n\n\tassertive.IsEqual(t, pass, true)\n\n\tswitch runtime.GOARCH {\n\tcase \"amd64\":\n\t\tpass, _ = require.Amd64.Check(nil, nil)\n\tcase \"arm64\":\n\t\tpass, _ = require.Arm64.Check(nil, nil)\n\tdefault:\n\t\tpass, _ = require.Arch(runtime.GOARCH).Check(nil, nil)\n\t}\n\n\tassertive.IsEqual(t, pass, true, \"Require works as expected\")\n}\n\nfunc TestNot(t *testing.T) {\n\tt.Parallel()\n\n\tvar pass bool\n\n\tswitch runtime.GOOS {\n\tcase windows:\n\t\tpass, _ = require.Not(require.Linux).Check(nil, nil)\n\tcase linux:\n\t\tpass, _ = require.Not(require.Windows).Check(nil, nil)\n\tcase darwin:\n\t\tpass, _ = require.Not(require.Windows).Check(nil, nil)\n\tdefault:\n\t\tpass, _ = require.Not(require.Linux).Check(nil, nil)\n\t}\n\n\tassertive.IsEqual(t, pass, true, \"require.Not works as expected\")\n}\n\nfunc TestAllSuccess(t *testing.T) {\n\tt.Parallel()\n\n\tvar pass bool\n\n\tswitch runtime.GOOS {\n\tcase windows:\n\t\tpass, _ = require.All(require.Not(require.Linux), require.Not(require.Darwin)).\n\t\t\tCheck(nil, nil)\n\tcase linux:\n\t\tpass, _ = require.All(require.Not(require.Windows), require.Not(require.Darwin)).\n\t\t\tCheck(nil, nil)\n\tcase darwin:\n\t\tpass, _ = require.All(require.Not(require.Windows), require.Not(require.Linux)).\n\t\t\tCheck(nil, nil)\n\tdefault:\n\t\tpass, _ = require.All(require.Not(require.Windows), require.Not(require.Linux),\n\t\t\trequire.Not(require.Darwin)).Check(nil, nil)\n\t}\n\n\tassertive.IsEqual(t, pass, true, \"require.All works as expected\")\n}\n\nfunc TestAllOneFail(t *testing.T) {\n\tt.Parallel()\n\n\tvar pass bool\n\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\tpass, _ = require.All(require.Not(require.Linux), require.Not(require.Darwin)).\n\t\t\tCheck(nil, nil)\n\tcase \"linux\":\n\t\tpass, _ = require.All(require.Not(require.Windows), require.Not(require.Darwin)).\n\t\t\tCheck(nil, nil)\n\tcase \"darwin\":\n\t\tpass, _ = require.All(require.Not(require.Windows), require.Not(require.Linux)).\n\t\t\tCheck(nil, nil)\n\tdefault:\n\t\tpass, _ = require.All(require.Not(require.OS(runtime.GOOS)), require.Not(require.Linux),\n\t\t\trequire.Not(require.Darwin)).Check(nil, nil)\n\t}\n\n\tassertive.IsEqual(t, pass, true, \"mixing require.All and require.Not works as expected\")\n}\n"
  },
  {
    "path": "mod/tigron/test/case.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/formatter\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\n// Case describes an entire test-case, including data, setup and cleanup routines, command and\n// expectations.\ntype Case struct {\n\t// Description contains a human-readable short desc, used as a seed for the identifier and as a\n\t// title for the test.\n\tDescription string\n\t// NoParallel disables parallel execution if set to true.\n\t// This obviously implies that all tests run in parallel, by default. This is a design choice.\n\tNoParallel bool\n\t// Env contains a map of environment variables to use as a base for all commands run in Setup,\n\t// Command and Cleanup.\n\t// Note that the environment is inherited by subtests.\n\tEnv map[string]string\n\t// Data contains test specific data, accessible to all operations, also inherited by subtests.\n\tData Data\n\t// Config contains specific information meaningful to the binary being tested.\n\t// It is also inherited by subtests.\n\tConfig Config\n\n\t// Requirement\n\tRequire *Requirement\n\t// Setup\n\tSetup Butler\n\t// Command\n\tCommand Executor\n\t// Expected\n\tExpected Manager\n\t// Cleanup\n\tCleanup Butler\n\n\t// SubTests\n\tSubTests []*Case\n\n\t// Private\n\thelpers Helpers\n\tt       tig.T\n\tparent  *Case\n}\n\nconst (\n\tstartDecorator  = \"🚀\"\n\tcleanDecorator  = \"🧽\"\n\tsetupDecorator  = \"🏗\"\n\tsubinDecorator  = \"⤵️\"\n\tsuboutDecorator = \"↩️\"\n\ttempDecorator   = \"⏳\"\n)\n\n// Run prepares and executes the test, and any possible subtests.\nfunc (test *Case) Run(t *testing.T) {\n\tt.Helper()\n\t// Run the test\n\t//nolint:thelper\n\ttestRun := func(subT *testing.T) {\n\t\tsubT.Helper()\n\n\t\tsilentT := assertive.WithSilentSuccess(subT)\n\n\t\tassertive.True(silentT, test.t == nil, \"You cannot run a test multiple times\")\n\t\tassertive.True(silentT, test.Description != \"\" || test.parent == nil,\n\t\t\t\"A subtest description cannot be empty\")\n\t\tassertive.True(silentT, test.Command == nil || test.Expected != nil,\n\t\t\t\"Expectations for a test command cannot be nil. You may want to use `Setup` instead\"+\n\t\t\t\t\"of `Command`.\")\n\n\t\t// Attach testing.T\n\t\ttest.t = subT\n\n\t\t// Ensure we have env\n\t\tif test.Env == nil {\n\t\t\ttest.Env = map[string]string{}\n\t\t}\n\n\t\t// If we have a parent, get parent env, data and config\n\t\tvar parentData Data\n\n\t\tvar parentConfig Config\n\n\t\tif test.parent != nil {\n\t\t\tparentData = test.parent.Data\n\t\t\tparentConfig = test.parent.Config\n\n\t\t\tfor k, v := range test.parent.Env {\n\t\t\t\tif _, ok := test.Env[k]; !ok {\n\t\t\t\t\ttest.Env[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Inherit and attach Data and Config\n\t\ttest.Data = newData(test.t, test.Data, parentData)\n\t\ttest.Config = configureConfig(test.Config, parentConfig)\n\n\t\tvar custCom CustomizableCommand\n\t\tif registeredTestable == nil {\n\t\t\tcustCom = NewGenericCommand()\n\t\t} else {\n\t\t\tcustCom = registeredTestable.CustomCommand(test, test.t)\n\t\t}\n\n\t\t// Separate cwd from the temp directory\n\t\tcustCom.WithCwd(test.t.TempDir())\n\t\tcustCom.withT(test.t)\n\t\t// Set the command tempdir to another temp location.\n\t\t// This is required for the current extension mechanism to allow creation of command dependent configuration\n\t\t// assets. Note that this is a different location than both CWD and Data.Temp().Path().\n\t\tcustCom.withTempDir(test.t.TempDir())\n\t\tcustCom.withEnv(test.Env)\n\t\tcustCom.withConfig(test.Config)\n\n\t\t// Attach the base command, and t\n\t\ttest.helpers = &helpersInternal{\n\t\t\tcmdInternal: custCom,\n\t\t\tt:           test.t,\n\t\t}\n\n\t\tsetups := []func(data Data, helpers Helpers){}\n\t\tcleanups := []func(data Data, helpers Helpers){}\n\n\t\t// Check the requirements before going any further\n\t\tif test.Require != nil {\n\t\t\tshouldRun, message := test.Require.Check(test.Data, test.helpers)\n\t\t\tif !shouldRun {\n\t\t\t\ttest.t.Skip(\"test skipped as: \" + message)\n\t\t\t}\n\n\t\t\tif test.Require.Setup != nil {\n\t\t\t\tsetups = append(setups, test.Require.Setup)\n\t\t\t}\n\n\t\t\tif test.Require.Cleanup != nil {\n\t\t\t\tcleanups = append(cleanups, test.Require.Cleanup)\n\t\t\t}\n\t\t}\n\n\t\t// Register setup if any\n\t\tif test.Setup != nil {\n\t\t\tsetups = append(setups, test.Setup)\n\t\t}\n\n\t\t// Register cleanup if any\n\t\tif test.Cleanup != nil {\n\t\t\tcleanups = append(cleanups, test.Cleanup)\n\t\t}\n\n\t\t// Run optional post requirement hook\n\t\tif registeredTestable != nil {\n\t\t\tregisteredTestable.AmbientRequirements(test, test.t)\n\t\t}\n\n\t\t// Set parallel unless asked not to\n\t\tif !test.NoParallel {\n\t\t\tsubT.Parallel()\n\t\t}\n\n\t\t// Execute cleanups now\n\t\tif len(cleanups) > 0 {\n\t\t\ttest.t.Log(\n\t\t\t\t\"\\n\\n\" + formatter.Table(\n\t\t\t\t\t[][]any{{cleanDecorator, fmt.Sprintf(\"%q: initial cleanup\", test.t.Name())}},\n\t\t\t\t\t\"=\",\n\t\t\t\t) + \"\\n\",\n\t\t\t)\n\n\t\t\tfor _, cleanup := range cleanups {\n\t\t\t\tcleanup(test.Data, test.helpers)\n\t\t\t}\n\n\t\t\t// Register the cleanups, in reverse\n\t\t\tsubT.Cleanup(func() {\n\t\t\t\ttest.t.Helper()\n\t\t\t\ttest.t.Log(\n\t\t\t\t\t\"\\n\\n\" + formatter.Table(\n\t\t\t\t\t\t[][]any{{cleanDecorator, fmt.Sprintf(\"%q: post-cleanup\", test.t.Name())}},\n\t\t\t\t\t\t\"=\",\n\t\t\t\t\t) + \"\\n\",\n\t\t\t\t)\n\n\t\t\t\tslices.Reverse(cleanups)\n\n\t\t\t\tfor _, cleanup := range cleanups {\n\t\t\t\t\tcleanup(test.Data, test.helpers)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t// Run the setups\n\t\tif len(setups) > 0 {\n\t\t\ttest.t.Log(\n\t\t\t\t\"\\n\\n\" + formatter.Table(\n\t\t\t\t\t[][]any{{setupDecorator, fmt.Sprintf(\"%q: setup\", test.t.Name())}},\n\t\t\t\t\t\"=\",\n\t\t\t\t) + \"\\n\",\n\t\t\t)\n\n\t\t\tfor _, setup := range setups {\n\t\t\t\tsetup(test.Data, test.helpers)\n\t\t\t}\n\t\t}\n\n\t\t// Run the command if any, with expectations\n\t\t// Note: if we have a command, we already know we DO have Expected\n\t\tif test.Command != nil {\n\t\t\tcmd := test.Command(test.Data, test.helpers)\n\n\t\t\tdebugConfig, _ := json.MarshalIndent(test.Config.(*config).config, \"\", \"  \")\n\t\t\tdebugData, _ := json.MarshalIndent(test.Data.(*data).labels, \"\", \"  \")\n\n\t\t\t// Show the files in the temp directory BEFORE the command is executed\n\t\t\ttempFiles := []string{}\n\n\t\t\tif files, err := os.ReadDir(test.Data.Temp().Path()); err == nil {\n\t\t\t\tfor _, file := range files {\n\t\t\t\t\ttempFiles = append(tempFiles, file.Name())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttest.t.Log(\n\t\t\t\t\"\\n\\n\" + formatter.Table(\n\t\t\t\t\t[][]any{\n\t\t\t\t\t\t{startDecorator, fmt.Sprintf(\"%q: starting test!\", test.t.Name())},\n\t\t\t\t\t\t{tempDecorator, test.Data.Temp().Dir()},\n\t\t\t\t\t\t{\"\", strings.Join(tempFiles, \"\\n\")},\n\t\t\t\t\t\t{\"config\", string(debugConfig)},\n\t\t\t\t\t\t{\"labels\", string(debugData)},\n\t\t\t\t\t},\n\t\t\t\t\t\"=\",\n\t\t\t\t) + \"\\n\",\n\t\t\t)\n\t\t\t// FIXME: so, the expected function will run BEFORE the command\n\t\t\tcmd.Run(test.Expected(test.Data, test.helpers))\n\t\t}\n\n\t\tif len(test.SubTests) > 0 {\n\t\t\t// Now go for the subtests\n\t\t\ttest.t.Log(fmt.Sprintf(\"\\n%s️ %q: into subtests prep\", subinDecorator, test.t.Name()))\n\n\t\t\tfor _, subTest := range test.SubTests {\n\t\t\t\tsubTest.parent = test\n\t\t\t\tsubTest.Run(subT)\n\t\t\t}\n\n\t\t\ttest.t.Log(fmt.Sprintf(\"\\n%s️ %q: done with subtests prep\", suboutDecorator, test.t.Name()))\n\t\t}\n\t}\n\n\tif test.parent != nil {\n\t\tt.Run(test.Description, testRun)\n\t} else {\n\t\ttestRun(t)\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/test/command.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:exported,package-comments,add-constant // TODO.\npackage test\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/com\"\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/formatter\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nconst (\n\tdefaultExecutionTimeout = 3 * time.Minute\n\tcommandDecorator        = \"⚙️\"\n\terrorDecorator          = \"🚫\"\n\texitDecorator           = \"⚠️\"\n\tstdoutDecorator         = \"🟢\"\n\tstderrDecorator         = \"🟠\"\n\ttimeoutDecorator        = \"⏰\"\n\tcwdDecorator            = \"📁\"\n\tenvDecorator            = \"🌱\"\n\tsigDecorator            = \"⚡\"\n)\n\n// CustomizableCommand is an interface meant for people who want to heavily customize the base\n// command of their test case.\n// FIXME: now that most of the logic got moved to the internal command, consider simplifying this /\n// removing some of the extra layers from here\n//\n\ntype CustomizableCommand interface {\n\tTestableCommand\n\n\tPrependArgs(args ...string)\n\t// WithBlacklist allows to filter out unwanted variables from the embedding environment -\n\t// default it pass any that is defined by WithEnv\n\tWithBlacklist(env []string)\n\t// T returns the current testing object\n\tT() tig.T\n\n\t// withEnv *copies* the passed map to the environment of the command to be executed\n\t// Note that this will override any variable defined in the embedding environment\n\twithEnv(env map[string]string)\n\t// withTempDir specifies a temporary directory to use\n\t// FIXME: this is only required because of the current command extension mechanism\n\twithTempDir(path string)\n\t// WithConfig allows passing custom config properties from the test to the base command\n\twithConfig(config Config)\n\twithT(t tig.T)\n\t// Clear does a clone, but will clear binary and arguments while retaining the env, or any other\n\t// custom properties Gotcha: if genericCommand is embedded with a custom Run and an overridden\n\t// clear to return the embedding type the result will be the embedding command, no longer the\n\t// genericCommand\n\tclear() TestableCommand\n\n\t// Will manipulate specific configuration option on the command\n\t// Note that config is a copy of the test config\n\t// Any modification done here will not be passed along to subtests, although they are shared\n\t// amongst all commands of the test.\n\twrite(key ConfigKey, value ConfigValue)\n\tread(key ConfigKey) ConfigValue\n}\n\nfunc NewGenericCommand() CustomizableCommand {\n\tgenericCom := &GenericCommand{\n\t\tEnv: map[string]string{},\n\t\tcmd: &com.Command{},\n\t}\n\n\tgenericCom.cmd.Env = genericCom.Env\n\tgenericCom.cmd.Timeout = defaultExecutionTimeout\n\n\treturn genericCom\n}\n\n// GenericCommand is a concrete Command implementation.\ntype GenericCommand struct {\n\tConfig  Config\n\tTempDir string\n\tEnv     map[string]string\n\n\tt tig.T\n\n\tcmd   *com.Command\n\tasync bool\n\n\trawStdErr string\n}\n\nfunc (gc *GenericCommand) WithBinary(binary string) {\n\tgc.cmd.Binary = binary\n}\n\nfunc (gc *GenericCommand) WithArgs(args ...string) {\n\tgc.cmd.Args = append(gc.cmd.Args, args...)\n}\n\nfunc (gc *GenericCommand) WithWrapper(binary string, args ...string) {\n\tgc.cmd.WrapBinary = binary\n\tgc.cmd.WrapArgs = args\n}\n\nfunc (gc *GenericCommand) WithPseudoTTY() {\n\tgc.cmd.WithPTY(true, true, false)\n}\n\nfunc (gc *GenericCommand) Feed(r io.Reader) {\n\tgc.cmd.Feed(r)\n}\n\nfunc (gc *GenericCommand) Setenv(key, value string) {\n\tgc.cmd.Env[key] = value\n}\n\nfunc (gc *GenericCommand) WithFeeder(fun func() io.Reader) {\n\tgc.cmd.WithFeeder(fun)\n}\n\nfunc (gc *GenericCommand) WithCwd(path string) {\n\tgc.cmd.WorkingDir = path\n}\n\nfunc (gc *GenericCommand) WithBlacklist(env []string) {\n\tgc.cmd.EnvBlackList = env\n}\n\nfunc (gc *GenericCommand) WithWhitelist(env []string) {\n\tgc.cmd.EnvWhiteList = env\n}\n\nfunc (gc *GenericCommand) WithTimeout(timeout time.Duration) {\n\tgc.cmd.Timeout = timeout\n}\n\nfunc (gc *GenericCommand) PrependArgs(args ...string) {\n\tgc.cmd.PrependArgs = append(gc.cmd.PrependArgs, args...)\n}\n\nfunc (gc *GenericCommand) Background() {\n\tgc.async = true\n\n\t_ = gc.cmd.Run(context.WithValue(context.Background(), com.LoggerKey, gc.t))\n}\n\nfunc (gc *GenericCommand) Signal(sig os.Signal) error {\n\t//nolint:wrapcheck\n\treturn gc.cmd.Signal(sig)\n}\n\nfunc (gc *GenericCommand) Run(expect *Expected) {\n\tgc.t.Helper()\n\n\tvar debug [][]any\n\n\tif !gc.async {\n\t\t_ = gc.cmd.Run(context.WithValue(context.Background(), com.LoggerKey, gc.t))\n\t}\n\n\tdebug = append(debug,\n\t\t[]any{\"➡️\", commandDecorator + \" \" + gc.cmd.Binary + \" \" + strings.Join(gc.cmd.Args, \" \")},\n\t)\n\n\t// Wait for the command and if Err is not nil, append it to the debug information\n\tresult, err := gc.cmd.Wait()\n\tif err != nil {\n\t\tdebug = append(debug, []any{\"\", errorDecorator + \" \" + err.Error()})\n\t}\n\n\t// If we were requested to perform expectation matching, add non-empty debugging information\n\tif result != nil {\n\t\tgc.rawStdErr = result.Stderr\n\n\t\tif result.ExitCode != 0 {\n\t\t\tdebug = append(debug, []any{\"\", exitDecorator + \" \" + strconv.Itoa(result.ExitCode)})\n\t\t}\n\n\t\tif result.Stdout != \"\" {\n\t\t\tdebug = append(debug, []any{\"\", stdoutDecorator + \" \" + result.Stdout})\n\t\t}\n\n\t\tif result.Stderr != \"\" {\n\t\t\tdebug = append(debug, []any{\"\", stderrDecorator + \" \" + result.Stderr})\n\t\t}\n\n\t\tif result.Signal != nil {\n\t\t\tdebug = append(debug, []any{\"\", sigDecorator + \" \" + result.Signal.String()})\n\t\t}\n\n\t\tduration := result.Duration.String()\n\t\tif result.Duration < time.Second {\n\t\t\tduration = \"<1s\"\n\t\t}\n\n\t\tdebug = append(debug,\n\t\t\t[]any{envDecorator, strings.Join(result.Environ, \"\\n\")},\n\t\t\t[]any{timeoutDecorator, duration + \" (limit: \" + gc.cmd.Timeout.String() + \")\"},\n\t\t\t[]any{cwdDecorator, gc.cmd.WorkingDir},\n\t\t)\n\t}\n\n\t// Print the command info\n\tgc.t.Log(\"\\n\\n\" + formatter.Table(debug, \"-\") + \"\\n\")\n\n\t// Now, check our expectations, if any\n\tif expect != nil {\n\t\tassertT := assertive.WithSilentSuccess(gc.t)\n\n\t\t// ExitCode goes first\n\t\tswitch expect.ExitCode {\n\t\tcase internal.ExitCodeNoCheck:\n\t\t\t// ExitCodeNoCheck means we do not care at all about what happened. Fire and forget...\n\t\tcase internal.ExitCodeGenericFail:\n\t\t\t// ExitCodeGenericFail means we expect an error (excluding timeout, cancellation,\n\t\t\t// signalling).\n\t\t\tassertive.ErrorIs(\n\t\t\t\tassertT,\n\t\t\t\terr,\n\t\t\t\tcom.ErrExecutionFailed,\n\t\t\t\t\"Command should fail\",\n\t\t\t)\n\t\tcase internal.ExitCodeTimeout:\n\t\t\tassertive.ErrorIs(\n\t\t\t\tgc.t,\n\t\t\t\terr,\n\t\t\t\tcom.ErrTimeout,\n\t\t\t\t\"Command should time-out\",\n\t\t\t)\n\t\tcase internal.ExitCodeSignaled:\n\t\t\tassertive.ErrorIs(\n\t\t\t\tgc.t,\n\t\t\t\terr,\n\t\t\t\tcom.ErrSignaled,\n\t\t\t\t\"Command should get signaled\",\n\t\t\t)\n\t\tcase internal.ExitCodeSuccess:\n\t\t\tassertive.ErrorIsNil(\n\t\t\t\tassertT,\n\t\t\t\terr,\n\t\t\t\t\"Command should succeed\",\n\t\t\t)\n\t\tdefault:\n\t\t\texc := -1\n\t\t\tif result != nil {\n\t\t\t\texc = result.ExitCode\n\t\t\t}\n\n\t\t\tassertive.IsEqual(\n\t\t\t\tassertT,\n\t\t\t\texpect.ExitCode,\n\t\t\t\texc,\n\t\t\t\t\"Exit code should match expectation\",\n\t\t\t)\n\t\t}\n\n\t\t// Switch to fail later mode so that we get ALL subsequent asserts failures from there on.\n\t\t// Also does allow using assert(ive) in go routines.\n\t\tassertT = assertive.WithFailLater(gc.t)\n\n\t\t// Range through the expected errors and confirm they are seen on stderr\n\t\tfor _, expectErr := range expect.Errors {\n\t\t\tassertive.Contains(\n\t\t\t\tassertT,\n\t\t\t\tgc.rawStdErr,\n\t\t\t\texpectErr.Error(),\n\t\t\t\t\"Stderr should contain expected error\",\n\t\t\t)\n\t\t}\n\n\t\t// Finally, check the output if we are asked to\n\t\t// FIXME: we cannot pass on assertT further for now until we move to tig.T\n\t\tif expect.Output != nil {\n\t\t\texpect.Output(\n\t\t\t\tresult.Stdout,\n\t\t\t\tgc.t,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc (gc *GenericCommand) Stderr() string {\n\treturn gc.rawStdErr\n}\n\nfunc (gc *GenericCommand) withEnv(env map[string]string) {\n\tfor k, v := range env {\n\t\tgc.cmd.Env[k] = v\n\t}\n}\n\nfunc (gc *GenericCommand) withTempDir(path string) {\n\tgc.TempDir = path\n}\n\nfunc (gc *GenericCommand) withConfig(config Config) {\n\tgc.Config = config\n}\n\nfunc (gc *GenericCommand) Clone() TestableCommand {\n\t// Copy the command and return a new one - with almost everything from the parent command\n\tclone := *gc\n\tclone.rawStdErr = \"\"\n\tclone.async = false\n\n\t// Clone Env\n\tclone.Env = make(map[string]string, len(gc.Env))\n\tfor k, v := range gc.Env {\n\t\tclone.Env[k] = v\n\t}\n\n\t// Clone the underlying command\n\tclone.cmd = gc.cmd.Clone()\n\tclone.cmd.Env = clone.Env\n\n\treturn &clone\n}\n\nfunc (gc *GenericCommand) T() tig.T {\n\treturn gc.t\n}\n\nfunc (gc *GenericCommand) clear() TestableCommand {\n\tcomcopy := *gc\n\t// Reset internal command\n\tcomcopy.cmd = &com.Command{}\n\tcomcopy.rawStdErr = \"\"\n\tcomcopy.async = false\n\t// Clone Env\n\tcomcopy.Env = make(map[string]string, len(gc.Env))\n\t// Reset configuration\n\tcomcopy.Config = &config{}\n\t// Copy the env\n\tfor k, v := range gc.Env {\n\t\tcomcopy.Env[k] = v\n\t}\n\n\tcomcopy.cmd.Env = comcopy.Env\n\n\treturn &comcopy\n}\n\nfunc (gc *GenericCommand) withT(t tig.T) {\n\tt.Helper()\n\tgc.t = t\n}\n\nfunc (gc *GenericCommand) read(key ConfigKey) ConfigValue {\n\treturn gc.Config.Read(key)\n}\n\nfunc (gc *GenericCommand) write(key ConfigKey, value ConfigValue) {\n\tgc.Config.Write(key, value)\n}\n"
  },
  {
    "path": "mod/tigron/test/config.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\n// WithConfig returns a config object with a certain config property set.\nfunc WithConfig(key ConfigKey, value ConfigValue) Config {\n\tcfg := &config{}\n\tcfg.Write(key, value)\n\n\treturn cfg\n}\n\n// Contains the implementation of the Config interface.\nfunc configureConfig(cfg, parent Config) Config {\n\tif cfg == nil {\n\t\tcfg = &config{\n\t\t\tconfig: make(map[ConfigKey]ConfigValue),\n\t\t}\n\t}\n\n\tif parent != nil {\n\t\t// Note: implementation dependent\n\t\t//nolint:forcetypeassert\n\t\tcfg.(*config).adopt(parent)\n\t}\n\n\treturn cfg\n}\n\ntype config struct {\n\tconfig map[ConfigKey]ConfigValue\n}\n\nfunc (cfg *config) Write(key ConfigKey, value ConfigValue) Config {\n\tif cfg.config == nil {\n\t\tcfg.config = make(map[ConfigKey]ConfigValue)\n\t}\n\n\tcfg.config[key] = value\n\n\treturn cfg\n}\n\nfunc (cfg *config) Read(key ConfigKey) ConfigValue {\n\tif cfg.config == nil {\n\t\tcfg.config = make(map[ConfigKey]ConfigValue)\n\t}\n\n\tif val, ok := cfg.config[key]; ok {\n\t\treturn val\n\t}\n\n\treturn \"\"\n}\n\nfunc (cfg *config) adopt(parent Config) {\n\t// Note: implementation dependent\n\t//nolint:forcetypeassert\n\tfor k, v := range parent.(*config).config {\n\t\t// Only copy keys that are not set already\n\t\tif _, ok := cfg.config[k]; !ok {\n\t\t\tcfg.Write(k, v)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/test/config_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//revive:disable:add-constant\n//nolint:testpackage // We need to test some internals here\npackage test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n)\n\nfunc TestConfig(t *testing.T) {\n\tt.Parallel()\n\n\t// Create\n\tcfg := WithConfig(\"test\", \"something\")\n\n\tassertive.IsEqual(t, string(cfg.Read(\"test\")), \"something\")\n\n\t// Write\n\tcfg.Write(\"test-write\", \"else\")\n\n\t// Overwrite\n\tcfg.Write(\"test\", \"one\")\n\n\tassertive.IsEqual(t, string(cfg.Read(\"test\")), \"one\")\n\tassertive.IsEqual(t, string(cfg.Read(\"test-write\")), \"else\")\n\n\tassertive.IsEqual(t, string(cfg.Read(\"doesnotexist\")), \"\")\n\n\t// Test adoption\n\tcfg2 := WithConfig(\"test\", \"two\")\n\tcfg2.Write(\"adopt\", \"two\")\n\n\tcnf, ok := cfg.(*config)\n\n\tassertive.True(t, ok)\n\n\tcnf.adopt(cfg2)\n\n\tassertive.IsEqual(t, string(cfg.Read(\"test\")), \"one\")\n\tassertive.IsEqual(t, string(cfg.Read(\"adopt\")), \"two\")\n}\n"
  },
  {
    "path": "mod/tigron/test/consts.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nconst (\n\t// FilePermissionsDefault specifies the default creation mode for temporary files.\n\t// Note that umask will affect these.\n\tFilePermissionsDefault = 0o644\n\t// DirPermissionsDefault specifies the default creation mode for temporary directories.\n\t// Note that umask will affect these.\n\tDirPermissionsDefault = 0o755\n)\n"
  },
  {
    "path": "mod/tigron/test/data.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nconst (\n\tidentifierMaxLength       = 76\n\tidentifierSeparator       = \"-\"\n\tidentifierSignatureLength = 8\n)\n\n// WithLabels returns a Data object with specific key value labels set.\nfunc WithLabels(in map[string]string) Data {\n\tdat := &data{\n\t\tlabels: &labels{\n\t\t\tinMap: in,\n\t\t},\n\t\ttemp: &temp{},\n\t}\n\n\treturn dat\n}\n\ntype labels struct {\n\tinMap map[string]string\n}\n\nfunc (lb *labels) Get(key string) string {\n\treturn lb.inMap[key]\n}\n\nfunc (lb *labels) Set(key, value string) {\n\tlb.inMap[key] = value\n}\n\ntype temp struct {\n\ttempDir string\n\tt       tig.T\n}\n\nfunc (tp *temp) Load(key ...string) string {\n\ttp.t.Helper()\n\n\tpth := filepath.Join(append([]string{tp.tempDir}, key...)...)\n\n\t//nolint:gosec // Fine in the context of testing\n\tcontent, err := os.ReadFile(pth)\n\n\tassertive.ErrorIsNil(\n\t\tassertive.WithSilentSuccess(tp.t),\n\t\terr,\n\t\tfmt.Sprintf(\"Loading file %q must succeed\", pth),\n\t)\n\n\treturn string(content)\n}\n\nfunc (tp *temp) Exists(key ...string) {\n\ttp.t.Helper()\n\n\tpth := filepath.Join(append([]string{tp.tempDir}, key...)...)\n\n\t_, err := os.Stat(pth)\n\n\tassertive.ErrorIsNil(\n\t\tassertive.WithSilentSuccess(tp.t),\n\t\terr,\n\t\tfmt.Sprintf(\"File %q must exist\", pth),\n\t)\n}\n\nfunc (tp *temp) Save(value string, key ...string) string {\n\ttp.t.Helper()\n\n\ttp.Dir(key[:len(key)-1]...)\n\n\tpth := filepath.Join(append([]string{tp.tempDir}, key...)...)\n\n\terr := os.WriteFile(\n\t\tpth,\n\t\t[]byte(value),\n\t\tFilePermissionsDefault,\n\t)\n\n\tassertive.ErrorIsNil(\n\t\tassertive.WithSilentSuccess(tp.t),\n\t\terr,\n\t\tfmt.Sprintf(\"Saving file %q must succeed\", pth),\n\t)\n\n\treturn pth\n}\n\nfunc (tp *temp) SaveToWriter(writer func(file io.Writer) error, key ...string) string {\n\ttp.t.Helper()\n\n\ttp.Dir(key[:len(key)-1]...)\n\n\tpth := filepath.Join(append([]string{tp.tempDir}, key...)...)\n\tsilentT := assertive.WithSilentSuccess(tp.t)\n\n\t//nolint:gosec // it is fine\n\tfile, err := os.OpenFile(pth, os.O_CREATE|os.O_WRONLY, FilePermissionsDefault)\n\tassertive.ErrorIsNil(\n\t\tsilentT,\n\t\terr,\n\t\tfmt.Sprintf(\"Opening file %q must succeed\", pth),\n\t)\n\n\tdefer func() {\n\t\terr = file.Close()\n\t\tassertive.ErrorIsNil(\n\t\t\tsilentT,\n\t\t\terr,\n\t\t\tfmt.Sprintf(\"Closing file %q must succeed\", pth),\n\t\t)\n\t}()\n\n\terr = writer(file)\n\tassertive.ErrorIsNil(\n\t\tsilentT,\n\t\terr,\n\t\tfmt.Sprintf(\"Filewriter failed while attempting to write to %q\", pth),\n\t)\n\n\treturn pth\n}\n\nfunc (tp *temp) Dir(key ...string) string {\n\ttp.t.Helper()\n\n\tpth := filepath.Join(append([]string{tp.tempDir}, key...)...)\n\terr := os.MkdirAll(pth, DirPermissionsDefault)\n\n\tassertive.ErrorIsNil(\n\t\tassertive.WithSilentSuccess(tp.t),\n\t\terr,\n\t\tfmt.Sprintf(\"Creating directory %q must succeed\", pth),\n\t)\n\n\treturn pth\n}\n\nfunc (tp *temp) Path(key ...string) string {\n\ttp.t.Helper()\n\n\treturn filepath.Join(append([]string{tp.tempDir}, key...)...)\n}\n\ntype data struct {\n\ttemp   DataTemp\n\tlabels DataLabels\n\ttestID func(suffix ...string) string\n}\n\nfunc (dt *data) Identifier(suffix ...string) string {\n\treturn dt.testID(suffix...)\n}\n\nfunc (dt *data) Labels() DataLabels {\n\treturn dt.labels\n}\n\nfunc (dt *data) Temp() DataTemp {\n\treturn dt.temp\n}\n\n// Contains the implementation of the Data interface\n//\n//nolint:varnamelen\nfunc newData(t tig.T, seed, parent Data) Data {\n\tt.Helper()\n\n\tt = assertive.WithSilentSuccess(t)\n\n\tseedMap := map[string]string{}\n\n\tif seed != nil {\n\t\tif inLab, ok := seed.Labels().(*labels); ok {\n\t\t\tseedMap = inLab.inMap\n\t\t}\n\t}\n\n\tif parent != nil {\n\t\tfor k, v := range parent.Labels().(*labels).inMap {\n\t\t\t// Only copy keys that are not set already\n\t\t\tif _, ok := seedMap[k]; !ok {\n\t\t\t\tseedMap[k] = v\n\t\t\t}\n\t\t}\n\t}\n\n\t// NOTE: certain systems will use the path dirname to decide how they name resources.\n\t// t.TempDir() will always return /tmp/TestTempDir2153252249/001, meaning these systems will all\n\t// use the identical 001 part. This is true for compose specifically.\n\t// Appending the base test identifier here would guarantee better unicity.\n\t// Note though that Windows will barf if >256 characters, so, hashing...\n\t// Small caveat: identically named tests in different modules WILL still end-up with the same last segment.\n\ttempDir := filepath.Join(\n\t\tt.TempDir(),\n\t\tfmt.Sprintf(\"%x\", sha256.Sum256([]byte(t.Name())))[0:identifierSignatureLength],\n\t)\n\n\tassertive.ErrorIsNil(t, os.MkdirAll(tempDir, DirPermissionsDefault))\n\n\tdat := &data{\n\t\tlabels: &labels{\n\t\t\tinMap: seedMap,\n\t\t},\n\t\ttemp: &temp{\n\t\t\ttempDir: tempDir,\n\t\t\tt:       t,\n\t\t},\n\t\ttestID: func(suffix ...string) string {\n\t\t\tsuffix = append([]string{t.Name()}, suffix...)\n\n\t\t\treturn defaultIdentifierHashing(suffix...)\n\t\t},\n\t}\n\n\treturn dat\n}\n\nfunc defaultIdentifierHashing(names ...string) string {\n\t// Notes: identifier MAY be used for namespaces, image names, etc.\n\t// So, the rules are stringent on what it can contain.\n\treplaceWith := []byte(identifierSeparator)\n\tname := strings.ToLower(strings.Join(names, string(replaceWith)))\n\t// Ensure we have a unique identifier despite characters replacements\n\t// (well, as unique as the names collection being passed)\n\tsignature := fmt.Sprintf(\"%x\", sha256.Sum256([]byte(name)))[0:identifierSignatureLength]\n\t// Make sure we do not use any unsafe characters\n\tsafeName := regexp.MustCompile(`[^a-z0-9-]+`)\n\t// And we avoid repeats of the separator\n\tnoRepeat := regexp.MustCompile(fmt.Sprintf(`[%s]{2,}`, replaceWith))\n\tescapedName := safeName.ReplaceAll([]byte(name), replaceWith)\n\tescapedName = noRepeat.ReplaceAll(escapedName, replaceWith)\n\t// Do not allow trailing or leading dash (as that may stutter)\n\tname = strings.Trim(string(escapedName), string(replaceWith))\n\n\t// Ensure we will never go above 76 characters in length (with signature)\n\tif len(name) > (identifierMaxLength - len(signature)) {\n\t\tname = name[0 : identifierMaxLength-identifierSignatureLength-len(identifierSeparator)]\n\t}\n\n\tif name[len(name)-1:] != identifierSeparator {\n\t\tsignature = identifierSeparator + signature\n\t}\n\n\treturn name + signature\n}\n"
  },
  {
    "path": "mod/tigron/test/data_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//nolint:testpackage\n//revive:disable:add-constant\npackage test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n)\n\nfunc TestLabels(t *testing.T) {\n\tt.Parallel()\n\n\tdataLabels := WithLabels(map[string]string{\"test\": \"create\"}).Labels()\n\n\tassertive.IsEqual(t, dataLabels.Get(\"test\"), \"create\")\n\tassertive.IsEqual(t, dataLabels.Get(\"doesnotexist\"), \"\")\n\n\tdataLabels.Set(\"test\", \"set\")\n\tassertive.IsEqual(t, dataLabels.Get(\"test\"), \"set\")\n\n\tdataLabels.Set(\"test\", \"reset\")\n\tassertive.IsEqual(t, dataLabels.Get(\"test\"), \"reset\")\n}\n\nfunc TestTemp(t *testing.T) {\n\tt.Parallel()\n\n\tdataObj := newData(t, nil, nil)\n\n\tone := dataObj.Temp().Path()\n\ttwo := dataObj.Temp().Path()\n\n\tassertive.IsEqual(t, one, two)\n\tassertive.IsNotEqual(t, one, \"\")\n\n\tt.Run(\"verify that subtest has an independent TempDir\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tdataObj = newData(t, nil, nil)\n\t\tthree := dataObj.Temp().Path()\n\t\tassertive.IsNotEqual(t, one, three)\n\t})\n}\n\nfunc TestDataIdentifier(t *testing.T) {\n\tt.Parallel()\n\n\tdataObj := newData(t, nil, nil)\n\n\tone := dataObj.Identifier()\n\ttwo := dataObj.Identifier()\n\n\tassertive.IsEqual(t, one, two)\n\tassertive.HasPrefix(t, one, \"testdataidentifier\")\n\n\tthree := dataObj.Identifier(\"Some Add ∞ Funky∞Prefix\")\n\tassertive.HasPrefix(t, three, \"testdataidentifier-some-add-funky-prefix\")\n}\n\nfunc TestDataIdentifierThatIsReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLong(\n\tt *testing.T,\n) {\n\tt.Parallel()\n\n\tdataObj := newData(t, nil, nil)\n\n\tone := dataObj.Identifier()\n\ttwo := dataObj.Identifier()\n\n\tassertive.IsEqual(t, one, two)\n\tassertive.HasPrefix(t, one, \"testdataidentifier\")\n\tassertive.IsEqual(t, len(one), identifierMaxLength)\n\n\tthree := dataObj.Identifier(\"Add something\")\n\tassertive.IsNotEqual(t, three, one)\n}\n"
  },
  {
    "path": "mod/tigron/test/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package test is the main entrypoint for Tigron.\npackage test\n"
  },
  {
    "path": "mod/tigron/test/expected.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\n// Command is the simplest way to express a test.TestableCommand for very basic cases\n// where access to test data is not necessary.\nfunc Command(args ...string) Executor {\n\treturn func(_ Data, helpers Helpers) TestableCommand {\n\t\treturn helpers.Command(args...)\n\t}\n}\n\n// Expects is provided as a simple helper covering \"expectations\" for simple use-cases\n// where access to the test data is not necessary.\nfunc Expects(exitCode int, errors []error, output Comparator) Manager {\n\treturn func(_ Data, _ Helpers) *Expected {\n\t\treturn &Expected{\n\t\t\tExitCode: exitCode,\n\t\t\tErrors:   errors,\n\t\t\tOutput:   output,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/test/funct.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\n// An Evaluator is a function that decides whether a test should run or not.\ntype Evaluator func(data Data, helpers Helpers) (bool, string)\n\n// A Butler is the function signature meant to be attached to a Setup or Cleanup routine for a Case\n// or Requirement.\ntype Butler func(data Data, helpers Helpers)\n\n// TODO: when we will break API:\n// - remove the info parameter\n// - move to tig.T\n\n// A Comparator is the function signature to implement for the Output property of an Expected.\ntype Comparator func(stdout string, t tig.T)\n\n// A Manager is the function signature meant to produce expectations for a command.\ntype Manager func(data Data, helpers Helpers) *Expected\n\n// An Executor is the function signature meant to be attached to the Command property of a Case.\ntype Executor func(data Data, helpers Helpers) TestableCommand\n"
  },
  {
    "path": "mod/tigron/test/helpers.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/internal\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\n// This is the implementation of Helpers\n\ntype helpersInternal struct {\n\tcmdInternal CustomizableCommand\n\n\tt tig.T\n}\n\n// Ensure will run a command and make sure it is successful.\nfunc (help *helpersInternal) Ensure(args ...string) {\n\thelp.t.Helper()\n\thelp.Command(args...).Run(&Expected{\n\t\tExitCode: internal.ExitCodeSuccess,\n\t})\n}\n\n// Anyhow will run a command regardless of outcome (may or may not fail).\nfunc (help *helpersInternal) Anyhow(args ...string) {\n\thelp.t.Helper()\n\thelp.Command(args...).Run(&Expected{\n\t\tExitCode: internal.ExitCodeNoCheck,\n\t})\n}\n\n// Fail will run a command and make sure it does fail.\nfunc (help *helpersInternal) Fail(args ...string) {\n\thelp.t.Helper()\n\thelp.Command(args...).Run(&Expected{\n\t\tExitCode: internal.ExitCodeGenericFail,\n\t})\n}\n\n// Capture will run a command, ensure it is successful and return stdout.\nfunc (help *helpersInternal) Capture(args ...string) string {\n\tvar ret string\n\n\thelp.t.Helper()\n\thelp.Command(args...).Run(&Expected{\n\t\tOutput: func(stdout string, _ tig.T) {\n\t\t\tret = stdout\n\t\t},\n\t})\n\n\treturn ret\n}\n\n// Err will run a command with no expectation and return Stderr.\nfunc (help *helpersInternal) Err(args ...string) string {\n\thelp.t.Helper()\n\tcmd := help.Command(args...)\n\tcmd.Run(nil)\n\n\treturn cmd.Stderr()\n}\n\n// Command will return a clone of your base command without running it.\nfunc (help *helpersInternal) Command(args ...string) TestableCommand {\n\tcc := help.cmdInternal.Clone()\n\tcc.WithArgs(args...)\n\n\treturn cc\n}\n\n// Custom will return a command for the requested binary and args, with the environment of your test\n// (eg: Env, Cwd, etc.)\nfunc (help *helpersInternal) Custom(binary string, args ...string) TestableCommand {\n\tcc := help.cmdInternal.clear()\n\tcc.WithBinary(binary)\n\tcc.WithArgs(args...)\n\n\treturn cc\n}\n\nfunc (help *helpersInternal) Read(key ConfigKey) ConfigValue {\n\treturn help.cmdInternal.read(key)\n}\n\nfunc (help *helpersInternal) Write(key ConfigKey, value ConfigValue) {\n\thelp.cmdInternal.write(key, value)\n}\n\nfunc (help *helpersInternal) T() tig.T {\n\treturn help.t\n}\n"
  },
  {
    "path": "mod/tigron/test/interfaces.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\n// DataLabels holds key-value test information set by the test authors.\n// Note that retrieving a non-existent label will return the empty string.\ntype DataLabels interface {\n\t// Get returns the value of the requested label.\n\tGet(key string) string\n\t// Set will save the label `key` with `value`.\n\tSet(key, value string)\n}\n\n// DataTemp allows test authors to easily manipulate test fixtures / temporary files.\ntype DataTemp interface {\n\t// Load will retrieve the content stored in the file\n\t// Asserts on failure.\n\tLoad(key ...string) string\n\t// Save will store the content in the file, ensuring parent dir exists, and return the path.\n\t// Asserts on failure.\n\tSave(data string, key ...string) string\n\t// SaveToWriter allows to write to the file as a writer.\n\t// This is particularly useful for encoding functions like pem.Encode.\n\tSaveToWriter(writer func(file io.Writer) error, key ...string) string\n\t// Path will return the absolute path for the asset, whether it exists or not.\n\tPath(key ...string) string\n\t// Exists asserts that the object exist.\n\tExists(key ...string)\n\t// Dir ensures the directory under temp is created, and returns the path.\n\t// Asserts on failure.\n\tDir(key ...string) string\n}\n\n// Data is meant to hold information about a test:\n// - first, key-value data that the test implementer wants to carry around - this is Labels()\n// - second, some commonly useful immutable test properties (eg: a way to generate unique\n// identifiers for that test)\n// - third, ability to manipulate test files inside managed temporary directories\n// Note that Data Labels are inherited from parent test to subtest.\n// This is not true for Identifier and Temp().Dir(), which are bound to the test itself, though temporary files\n// can be accessed by subtests if their location is passed around (in Labels).\ntype Data interface {\n\tTemp() DataTemp\n\tLabels() DataLabels\n\tIdentifier(suffix ...string) string\n}\n\n// Helpers provides a set of helpers to run commands with simple expectations,\n// available at all stages of a test (Setup, Cleanup, etc...)\ntype Helpers interface {\n\t// Ensure runs a command and verifies it is succeeding.\n\tEnsure(args ...string)\n\t// Anyhow runs a command and ignores its result.\n\tAnyhow(args ...string)\n\t// Fail runs a command and verifies it failed.\n\tFail(args ...string)\n\t// Capture runs a command, verifies it succeeded, and returns stdout.\n\tCapture(args ...string) string\n\t// Err runs a command, and returns stderr regardless of its outcome.\n\t// This is mostly useful for debugging.\n\tErr(args ...string) string\n\n\t// Command will return a populated command from the default internal command, with the provided\n\t// arguments, ready to be Run or further configured.\n\tCommand(args ...string) TestableCommand\n\t// Custom will return a bare command, without configuration nor defaults (still has the Env).\n\tCustom(binary string, args ...string) TestableCommand\n\n\t// Read return the config value associated with a key.\n\tRead(key ConfigKey) ConfigValue\n\t// Write saves a value in the config.\n\tWrite(key ConfigKey, value ConfigValue)\n\n\t// T returns the current testing object.\n\tT() tig.T\n}\n\n// The TestableCommand interface represents a low-level command to execute, typically to be compared\n// with an Expected. A TestableCommand can be used as a Case Command obviously, but also as part of\n// a Setup or Cleanup routine, and as the basis of any type of helper.\n// For more powerful use-cases outside of test cases, see below CustomizableCommand.\ntype TestableCommand interface {\n\t// WithBinary specifies what binary to execute.\n\tWithBinary(binary string)\n\t// WithArgs specifies the args to pass to the binary. Note that WithArgs can be used multiple\n\t// times and is additive.\n\tWithArgs(args ...string)\n\t// WithWrapper allows wrapping a command with another command (for example: `time`).\n\tWithWrapper(binary string, args ...string)\n\t// WithPseudoTTY will allocate a new pty and set the command stdin and stdout to it.\n\tWithPseudoTTY()\n\t// WithCwd allows specifying the working directory for the command.\n\tWithCwd(path string)\n\t// WithTimeout defines the execution timeout for a command.\n\tWithTimeout(timeout time.Duration)\n\t// Setenv allows to override a specific env variable directly for a specific command instead of test-wide\n\tSetenv(key, value string)\n\t// WithFeeder allows passing a reader to be fed to the command stdin.\n\tWithFeeder(fun func() io.Reader)\n\t// Feed allows passing a reader to be fed to the command stdin.\n\tFeed(r io.Reader)\n\t// Clone returns a copy of the command.\n\tClone() TestableCommand\n\n\t// Run does execute the command, and compare the output with the provided expectation.\n\t// Passing nil for `Expected` will just run the command regardless of outcome.\n\t// An empty `&Expected{}` is (of course) equivalent to &Expected{Exit: 0}, meaning the command\n\t// is verified to be successful.\n\tRun(expect *Expected)\n\t// Background allows starting a command in the background.\n\tBackground()\n\t// Signal sends a signal to a backgrounded command.\n\tSignal(sig os.Signal) error\n\t// Stderr allows retrieving the raw stderr output of the command once it has been run.\n\tStderr() string\n}\n\n// Config is meant to hold information relevant to the binary (eg: flags defining certain behaviors,\n// etc.)\ntype Config interface {\n\tWrite(key ConfigKey, value ConfigValue) Config\n\tRead(key ConfigKey) ConfigValue\n}\n"
  },
  {
    "path": "mod/tigron/test/package_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/highk\"\n)\n\nfunc TestMain(m *testing.M) {\n\t// Prep exit code\n\texitCode := 0\n\tdefer func() { os.Exit(exitCode) }()\n\n\tvar (\n\t\tsnapFile      *os.File\n\t\tbefore, after []byte\n\t)\n\n\tif os.Getenv(\"HIGHK_EXPERIMENTAL_FD\") != \"\" {\n\t\tsnapFile, _ = os.CreateTemp(\"\", \"fileleaks\")\n\t\tbefore, _ = highk.SnapshotOpenFiles(snapFile)\n\t}\n\n\texitCode = m.Run()\n\n\tif exitCode != 0 {\n\t\treturn\n\t}\n\n\tif os.Getenv(\"HIGHK_EXPERIMENTAL_FD\") != \"\" {\n\t\tafter, _ = highk.SnapshotOpenFiles(snapFile)\n\t\tdiff := highk.Diff(string(before), string(after))\n\n\t\tif len(diff) != 0 {\n\t\t\tfmt.Fprintln(os.Stderr, \"Leaking file descriptors\")\n\n\t\t\tfor _, file := range diff {\n\t\t\t\tfmt.Fprintln(os.Stderr, file)\n\t\t\t}\n\n\t\t\texitCode = 1\n\t\t}\n\t}\n\n\tif err := highk.FindGoRoutines(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"Leaking go routines\")\n\t\tfmt.Fprintln(os.Stderr, os.Stderr, err.Error())\n\n\t\texitCode = 1\n\t}\n}\n"
  },
  {
    "path": "mod/tigron/test/test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\n// Testable TODO.\ntype Testable interface {\n\tCustomCommand(testCase *Case, t tig.T) CustomizableCommand\n\tAmbientRequirements(testCase *Case, t tig.T)\n}\n\n// FIXME\n//\n//nolint:gochecknoglobals\nvar registeredTestable Testable\n\n// Customize TODO.\nfunc Customize(testable Testable) {\n\tregisteredTestable = testable\n}\n"
  },
  {
    "path": "mod/tigron/test/types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage test\n\ntype (\n\t// ConfigKey FIXME consider getting rid of this?\n\tConfigKey string\n\t// ConfigValue FIXME consider getting rid of this?\n\tConfigValue string\n)\n\n// A Requirement offers a way to verify random conditions to decide if a test should be skipped or run.\n// It can also (optionally) provide custom Setup and Cleanup routines.\ntype Requirement struct {\n\t// Check is expected to verify if the requirement is fulfilled, and return a boolean and an\n\t// explanatory message.\n\tCheck Evaluator\n\t// Setup, if provided, will be run before any test-specific Setup routine, in the order that\n\t// requirements have been declared.\n\tSetup Butler\n\t// Cleanup, if provided, will be run after any test-specific Cleanup routine, in the reverse\n\t// order that requirements have been declared.\n\tCleanup Butler\n}\n\n// Expected expresses the expected output of a command.\ntype Expected struct {\n\t// ExitCode.\n\tExitCode int\n\t// Errors contains any error that (once serialized) should be seen in stderr.\n\tErrors []error\n\t// Output function to match against stdout.\n\tOutput Comparator\n}\n"
  },
  {
    "path": "mod/tigron/tig/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package tig defines interfaces for third-party packages that tigron needs to interact with.\n// The main upside of expressing our expectations instead of depending directly on concrete implementations is\n// evidently the ability to mock easily, which in turn makes testing much easier.\npackage tig\n"
  },
  {
    "path": "mod/tigron/tig/t.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage tig\n\n// T is what Tigron needs from a testing implementation (*testing.T obviously satisfies it).\n//\n// Expert note: using the testing.TB interface instead is tempting, but not possible, as the go authors made it\n// impossible to implement (by declaring a private method on it):\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/testing/testing.go;l=913-939\n// Generally speaking, interfaces in go should be defined by the consumer, not the producer.\n// Depending on producers' interfaces make them much harder to change for the producer, harder (or impossible) to mock,\n// and decreases modularity.\n// On the other hand, consumer defined interfaces allows to remove direct dependencies on implementation and encourages\n// depending on abstraction instead, and reduces the interface size of what has to be mocked to just what is actually\n// needed.\n// This is a fundamental difference compared to traditional compiled languages that forces code to declare which\n// interfaces it implements, while go interfaces are more a form of duck-typing.\n// See https://www.airs.com/blog/archives/277 for more.\ntype T interface {\n\tHelper()\n\tFailNow()\n\tFail()\n\tLog(args ...any)\n\tName() string\n\tTempDir() string\n\tSkip(args ...any)\n}\n"
  },
  {
    "path": "mod/tigron/utils/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package utils provides generic helpers that are regularly useful for a range of test authors.\n// TODO: question the usefulness of this and whether this should even be part of tigron.\npackage utils\n"
  },
  {
    "path": "mod/tigron/utils/testca/ca.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package testca provides helpers to create a self-signed CA certificate, and the ability to generate\n// signed certificates from it.\n// PLEASE NOTE THIS IS NOT A PRODUCTION SAFE NOR VERIFIED WAY TO MANAGE CERTIFICATES FOR SERVERS.\npackage testca\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"math/big\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/internal/assertive\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nconst (\n\tkeyLength    = 4096\n\tcaRoot       = \"ca\"\n\tcertsRoot    = \"certs\"\n\torganization = \"tigron volatile testing organization\"\n\tlifetime     = 24 * time.Hour\n\tserialSize   = 60\n)\n\n// NewX509 creates a new, self-signed, signing certificate under data.Temp()/ca\n// From that Cert as a CA, you can then generate signed certificates.\n// Note that the common name of the cert will be set to the test name.\nfunc NewX509(data test.Data, helpers test.Helpers) *Cert {\n\ttemplate := &x509.Certificate{\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{organization},\n\t\t\tCommonName:   helpers.T().Name(),\n\t\t},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(lifetime),\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t}\n\n\treturn (&Cert{}).GenerateCustomX509(data, helpers, caRoot, template)\n}\n\n// Cert allows the consumer to retrieve the cert and key path, to be used by other processes, like servers for example.\ntype Cert struct {\n\tKeyPath  string\n\tCertPath string\n\tkey      *rsa.PrivateKey\n\tcert     *x509.Certificate\n}\n\n// GenerateServerX509 produces a certificate usable by a server.\n// additional can be used to provide additional ips to be added to the certificate.\nfunc (ca *Cert) GenerateServerX509(data test.Data, helpers test.Helpers, host string, additional ...string) *Cert {\n\ttemplate := &x509.Certificate{\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{organization},\n\t\t\tCommonName:   host,\n\t\t},\n\t\tNotBefore:   time.Now(),\n\t\tNotAfter:    time.Now().Add(lifetime),\n\t\tKeyUsage:    x509.KeyUsageCRLSign,\n\t\tExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tDNSNames:    additional,\n\t}\n\n\tadditional = append([]string{host}, additional...)\n\tfor _, h := range additional {\n\t\tif ip := net.ParseIP(h); ip != nil {\n\t\t\ttemplate.IPAddresses = append(template.IPAddresses, ip)\n\t\t}\n\t}\n\n\treturn ca.GenerateCustomX509(data, helpers, certsRoot, template)\n}\n\n// GenerateCustomX509 signs a random x509 certificate template.\n// Note that if SerialNumber is specified, it must be safe to use on the filesystem as this will be used in the name\n// of the certificate file.\nfunc (ca *Cert) GenerateCustomX509(\n\tdata test.Data,\n\thelpers test.Helpers,\n\tunderDirectory string,\n\ttemplate *x509.Certificate,\n) *Cert {\n\tsilentT := assertive.WithSilentSuccess(helpers.T())\n\n\tvar (\n\t\tcert *x509.Certificate\n\t\tkey  *rsa.PrivateKey\n\t)\n\n\tif ca != nil {\n\t\tcert = ca.cert\n\t\tkey = ca.key\n\t}\n\n\tkey, certPath, keyPath := createCert(silentT, data, underDirectory, template, cert, key)\n\n\treturn &Cert{\n\t\tCertPath: certPath,\n\t\tKeyPath:  keyPath,\n\t\tkey:      key,\n\t\tcert:     template,\n\t}\n}\n\nfunc createCert(\n\ttesting tig.T,\n\tdata test.Data,\n\tdir string,\n\ttemplate, caCert *x509.Certificate,\n\tcaKey *rsa.PrivateKey,\n) (key *rsa.PrivateKey, certPath, keyPath string) {\n\tkey, err := rsa.GenerateKey(rand.Reader, keyLength)\n\tassertive.ErrorIsNil(testing, err, \"key generation should succeed\")\n\n\tif caKey == nil {\n\t\tcaKey = key\n\t}\n\n\tif caCert == nil {\n\t\tcaCert = template\n\t}\n\n\tsignedCert, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)\n\tassertive.ErrorIsNil(testing, err, \"certificate creation should succeed\")\n\n\tserial := template.SerialNumber\n\tif serial == nil {\n\t\tserial = serialNumber()\n\t}\n\n\tdata.Temp().Dir(dir)\n\n\tdata.Temp().SaveToWriter(func(writer io.Writer) error {\n\t\treturn pem.Encode(writer, &pem.Block{Type: \"RSA PRIVATE KEY\", Bytes: x509.MarshalPKCS1PrivateKey(key)})\n\t}, dir, serial.String()+\".key\")\n\n\tdata.Temp().SaveToWriter(func(writer io.Writer) error {\n\t\treturn pem.Encode(writer, &pem.Block{Type: \"CERTIFICATE\", Bytes: signedCert})\n\t}, dir, serial.String()+\".cert\")\n\n\tcertPath = data.Temp().Path(dir, serial.String()+\".cert\")\n\tkeyPath = data.Temp().Path(dir, serial.String()+\".key\")\n\n\treturn key, certPath, keyPath\n}\n\nfunc serialNumber() *big.Int {\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), serialSize)\n\n\tserial, err := rand.Int(rand.Reader, serialNumberLimit)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn serial\n}\n"
  },
  {
    "path": "mod/tigron/utils/utilities.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n)\n\n// RandomStringBase64 generates a base64 encoded random string.\nfunc RandomStringBase64(desiredLength int) string {\n\trandomBytes := make([]byte, desiredLength)\n\n\trandomLength, err := rand.Read(randomBytes)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif randomLength != desiredLength {\n\t\tpanic(\"rand failing\")\n\t}\n\n\treturn base64.URLEncoding.EncodeToString(randomBytes)\n}\n"
  },
  {
    "path": "pkg/annotations/annotations.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package annotations defines OCI annotations\npackage annotations\n\nconst (\n\t// Prefix is the common prefix of nerdctl annotations\n\tPrefix = \"nerdctl/\"\n\n\t// Bypass4netns is the flag for acceleration with bypass4netns\n\t// Boolean value which can be parsed with strconv.ParseBool() is required.\n\t// (like \"nerdctl/bypass4netns=true\" or \"nerdctl/bypass4netns=false\")\n\tBypass4netns = Prefix + \"bypass4netns\"\n\n\t// Bypass4netnsIgnoreSubnets is a JSON of []string that is appended to\n\t// the `bypass4netns --ignore` list.\n\tBypass4netnsIgnoreSubnets = Bypass4netns + \"-ignore-subnets\"\n\n\t// Bypass4netnsIgnoreBind disables acceleration for bind.\n\t// Boolean value which can be parsed with strconv.ParseBool() is required.\n\tBypass4netnsIgnoreBind = Bypass4netns + \"-ignore-bind\"\n)\n\nvar ShellCompletions = []string{\n\tBypass4netns + \"=true\",\n\tBypass4netns + \"=false\",\n\tBypass4netnsIgnoreSubnets + \"=\",\n\tBypass4netnsIgnoreBind + \"=true\",\n\tBypass4netnsIgnoreBind + \"=false\",\n}\n"
  },
  {
    "path": "pkg/api/types/apparmor_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// ApparmorListOptions specifies options for `nerdctl apparmor ls`.\ntype ApparmorListOptions struct {\n\tStdout io.Writer\n\t// Only display profile names\n\tQuiet bool\n\t// Format the output using the given go template\n\tFormat string\n}\n\n// ApparmorInspectOptions specifies options for `nerdctl apparmor inspect`\ntype ApparmorInspectOptions struct {\n\tStdout io.Writer\n}\n"
  },
  {
    "path": "pkg/api/types/builder_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// BuilderBuildOptions specifies options for `nerdctl (image/builder) build`.\ntype BuilderBuildOptions struct {\n\tStdin  io.Reader\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// BuildKitHost is the buildkit host\n\tBuildKitHost string\n\t// Tag is the tag of the image\n\tTag []string\n\t// File Name of the Dockerfile\n\tFile string\n\t// Target is the target of the build\n\tTarget string\n\t// BuildArgs is the build-time variables\n\tBuildArgs []string\n\t// NoCache disables cache\n\tNoCache bool\n\t// Output is the output destination\n\tOutput string\n\t// Progress Set type of progress output (auto, plain, tty). Use plain to show container output\n\tProgress string\n\t// Secret file to expose to the build: id=mysecret,src=/local/secret\n\tSecret []string\n\t// Allow extra privileged entitlement, e.g. network.host, security.insecure\n\tAllow []string\n\t// Attestation parameters (format: \"type=sbom,generator=image\")\"\n\tAttest []string\n\t// SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])\n\tSSH []string\n\t// Quiet suppress the build output and print image ID on success\n\tQuiet bool\n\t// CacheFrom external cache sources (eg. user/app:cache, type=local,src=path/to/dir)\n\tCacheFrom []string\n\t// CacheTo cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)\n\tCacheTo []string\n\t// Rm remove intermediate containers after a successful build\n\tRm bool\n\t// Platform set target platform for build (e.g., \"amd64\", \"arm64\")\n\tPlatform []string\n\t// IidFile write the image ID to the file\n\tIidFile string\n\t// Label is the metadata for an image\n\tLabel []string\n\t// BuildContext is the build context\n\tBuildContext string\n\t// ExtendedBuildContext is a pair of key=value (e.g. myorg/myapp=docker-image://path/to/image, dir2=/path/to/dir2)\n\tExtendedBuildContext []string\n\t// NetworkMode mode for the build context\n\tNetworkMode string\n\t// Pull determines if we should try to pull latest image from remote. Default is buildkit's default.\n\tPull *bool\n\t// ExtraHosts is a set of custom host-to-IP mappings.\n\tExtraHosts []string\n\t// SourcePolicyFile is the path to a BuildKit source policy file.\n\t// Passed through to buildctl as --source-policy-file.\n\tSourcePolicyFile string\n}\n\n// BuilderPruneOptions specifies options for `nerdctl builder prune`.\ntype BuilderPruneOptions struct {\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// BuildKitHost is the buildkit host\n\tBuildKitHost string\n\t// All will remove all unused images and all build cache, not just dangling ones\n\tAll bool\n\t// Force will not prompt for confirmation.\n\tForce bool\n}\n"
  },
  {
    "path": "pkg/api/types/checkpoint_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// CheckpointCreateOptions specifies options for `nerdctl checkpoint create`.\ntype CheckpointCreateOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Leave the container running after checkpointing\n\tLeaveRunning bool\n\t// Checkpoint directory\n\tCheckpointDir string\n}\n\ntype CheckpointListOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Checkpoint directory\n\tCheckpointDir string\n}\n\n// CheckpointRemoveOptions specifies options for `nerdctl checkpoint rm`.\ntype CheckpointRemoveOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Checkpoint directory\n\tCheckpointDir string\n}\ntype CheckpointSummary struct {\n\t// Name is the name of the checkpoint.\n\tName string\n}\n"
  },
  {
    "path": "pkg/api/types/container_network_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"github.com/containerd/go-cni\"\n)\n\n// NetworkOptions struct defining networking-related options.\ntype NetworkOptions struct {\n\t// NetworkSlice specifies the networking mode for the container, default is \"bridge\"\n\tNetworkSlice []string\n\t// MACAddress set container MAC address (e.g., 92:d0:c6:0a:29:33)\n\tMACAddress string\n\t// IPAddress set specific static IP address(es) to use\n\tIPAddress string\n\t// IP6Address set specific static IP6 address(es) to use\n\tIP6Address string\n\t// Hostname set container host name\n\tHostname string\n\t// Domainname specifies the container's domain name\n\tDomainname string\n\t// DNSServers set custom DNS servers\n\tDNSServers []string\n\t// DNSResolvConfOptions set DNS options\n\tDNSResolvConfOptions []string\n\t// DNSSearchDomains set custom DNS search domains\n\tDNSSearchDomains []string\n\t// AddHost add a custom host-to-IP mapping (host:ip)\n\tAddHost []string\n\t// UTS namespace to use\n\tUTSNamespace string\n\t// PortMappings specifies a list of ports to publish from the container to the host\n\tPortMappings []cni.PortMapping\n}\n"
  },
  {
    "path": "pkg/api/types/container_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"io\"\n\t\"time\"\n)\n\n// ContainerStartOptions specifies options for the `nerdctl (container) start`.\ntype ContainerStartOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Attach specifies whether to attach to the container's stdio.\n\tAttach bool\n\t// The key sequence for detaching a container.\n\tDetachKeys string\n\t// Attach stdin\n\tInteractive bool\n\t// Checkpoint is the name of the checkpoint to restore\n\tCheckpoint string\n\t// CheckpointDir is the directory to store checkpoints\n\tCheckpointDir string\n\t// NerdctlCmd is the command name of nerdctl\n\tNerdctlCmd string\n\t// NerdctlArgs is the arguments of nerdctl\n\tNerdctlArgs []string\n}\n\n// ContainerKillOptions specifies options for `nerdctl (container) kill`.\ntype ContainerKillOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// KillSignal is the signal to send to the container\n\tKillSignal string\n}\n\n// ContainerExportOptions specifies options for `nerdctl (container) export`.\ntype ContainerExportOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n}\n\n// ContainerCreateOptions specifies options for `nerdctl (container) create` and `nerdctl (container) run`.\ntype ContainerCreateOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\n\t// NerdctlCmd is the command name of nerdctl\n\tNerdctlCmd string\n\t// NerdctlArgs is the arguments of nerdctl\n\tNerdctlArgs []string\n\n\t// InRun is true when it's generated in the `run` command\n\tInRun bool\n\n\t// #region for basic flags\n\t// Interactive keep STDIN open even if not attached\n\tInteractive bool\n\t// TTY specifies whether to allocate a pseudo-TTY for the container\n\tTTY bool\n\t// SigProxy specifies whether to proxy all received signals to the process\n\tSigProxy bool\n\t// Detach runs container in background and print container ID\n\tDetach bool\n\t// The key sequence for detaching a container.\n\tDetachKeys string\n\t// Attach STDIN, STDOUT, or STDERR\n\tAttach []string\n\t// Restart specifies the policy to apply when a container exits\n\tRestart string\n\t// Rm specifies whether to remove the container automatically when it exits\n\tRm bool\n\t// Pull image before running, default is missing\n\tPull string\n\t// Pid namespace to use\n\tPid string\n\t// StopSignal signal to stop a container, default is SIGTERM\n\tStopSignal string\n\t// StopTimeout specifies the timeout (in seconds) to stop a container\n\tStopTimeout int\n\t// #endregion\n\n\t// #region for platform flags\n\t// Platform set target platform for build (e.g., \"amd64\", \"arm64\", \"windows\", \"freebsd\")\n\tPlatform string\n\t// #endregion\n\n\t// #region for init process flags\n\t// InitProcessFlag specifies to run an init inside the container that forwards signals and reaps processes\n\tInitProcessFlag bool\n\t// InitBinary specifies the custom init binary to use, default is tini\n\tInitBinary *string\n\t// #endregion\n\n\t// #region for isolation flags\n\t// Isolation specifies the container isolation technology\n\tIsolation string\n\t// #endregion\n\n\t// #region for resource flags\n\t// CPUs specifies the number of CPUs\n\tCPUs float64\n\t// CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota\n\tCPUQuota int64\n\t// CPUPeriod limits the CPU CFS (Completely Fair Scheduler) period\n\tCPUPeriod uint64\n\t// CPUShares specifies the CPU shares (relative weight)\n\tCPUShares uint64\n\t// CPUSetCPUs specifies the CPUs in which to allow execution (0-3, 0,1)\n\tCPUSetCPUs string\n\t// CPUSetMems specifies the memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.\n\tCPUSetMems string\n\t// Limit CPU real-time period in microseconds\n\tCPURealtimePeriod uint64\n\t// Limit CPU real-time runtime in microseconds\n\tCPURealtimeRuntime uint64\n\t// Memory specifies the memory limit\n\tMemory string\n\t// MemoryReservationChanged specifies whether the memory soft limit has been changed\n\tMemoryReservationChanged bool\n\t// MemoryReservation specifies the memory soft limit\n\tMemoryReservation string\n\t// MemorySwap specifies the swap limit equal to memory plus swap: '-1' to enable unlimited swap\n\tMemorySwap string\n\t// MemSwappinessChanged specifies whether the memory swappiness has been changed\n\tMemorySwappiness64Changed bool\n\t// MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1)\n\tMemorySwappiness64 int64\n\t// KernelMemoryChanged specifies whether the kernel memory limit has been changed\n\tKernelMemoryChanged bool\n\t// KernelMemory specifies the kernel memory limit(deprecated)\n\tKernelMemory string\n\t// OomKillDisable specifies whether to disable OOM Killer\n\tOomKillDisable bool\n\t// OomScoreAdjChanged specifies whether the OOM preferences has been changed\n\tOomScoreAdjChanged bool\n\t// OomScoreAdj specifies the tune container's OOM preferences (-1000 to 1000, rootless: 100 to 1000)\n\tOomScoreAdj int\n\t// PidsLimit specifies the tune container pids limit\n\tPidsLimit int64\n\t// CgroupConf specifies to configure cgroup v2 (key=value)\n\tCgroupConf []string\n\t// Cgroupns specifies the cgroup namespace to use\n\tCgroupns string\n\t// CgroupParent specifies the optional parent cgroup for the container\n\tCgroupParent string\n\t// Device specifies add a host device to the container\n\tDevice []string\n\t// CDIDevices specifies the CDI devices to add to the container\n\tCDIDevices []string\n\t// #endregion\n\n\t// #region for blkio related flags\n\t// BlkioWeight specifies the block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)\n\tBlkioWeight uint16\n\t// BlkioWeightDevice specifies the Block IO weight (relative device weight)\n\tBlkioWeightDevice []string\n\t// BlkioDeviceReadBps specifies the Block IO read rate limit(bytes per second) of a device\n\tBlkioDeviceReadBps []string\n\t// BlkioDeviceWriteBps specifies the Block IO write rate limit(bytes per second) of a device\n\tBlkioDeviceWriteBps []string\n\t// BlkioDeviceReadIOps specifies the Block IO read rate limit(IO per second) of a device\n\tBlkioDeviceReadIOps []string\n\t// BlkioDeviceWriteIOps specifies the Block IO read rate limit(IO per second) of a device\n\tBlkioDeviceWriteIOps []string\n\t// #endregion\n\n\t// #region for intel RDT flags\n\t// RDTClass specifies the Intel Resource Director Technology (RDT) class\n\tRDTClass string\n\t// #endregion\n\n\t// #region for user flags\n\t// User specifies the user to run the container as\n\tUser string\n\t// Umask specifies the umask to use for the container\n\tUmask string\n\t// GroupAdd specifies additional groups to join\n\tGroupAdd []string\n\t// #endregion\n\n\t// #region for security flags\n\t// SecurityOpt specifies security options\n\tSecurityOpt []string\n\t// CapAdd add Linux capabilities\n\tCapAdd []string\n\t// CapDrop drop Linux capabilities\n\tCapDrop []string\n\t// Privileged gives extended privileges to this container\n\tPrivileged bool\n\t// Systemd\n\tSystemd string\n\t// #endregion\n\n\t// #region for runtime flags\n\t// Runtime to use for this container, e.g. \"crun\", or \"io.containerd.runsc.v1\".\n\tRuntime string\n\t// Sysctl set sysctl options, e.g \"net.ipv4.ip_forward=1\"\n\tSysctl []string\n\t// #endregion\n\n\t// #region for volume flags\n\t// Volume specifies a list of volumes to mount\n\tVolume []string\n\t// Tmpfs specifies a list of tmpfs mounts\n\tTmpfs []string\n\t// Mount specifies a list of mounts to mount\n\tMount []string\n\t// VolumesFrom specifies a list of specified containers to mount from\n\tVolumesFrom []string\n\t// #endregion\n\n\t// #region for rootfs flags\n\t// ReadOnly mount the container's root filesystem as read only\n\tReadOnly bool\n\t// Rootfs specifies the first argument is not an image but the rootfs to the exploded container. Corresponds to Podman CLI.\n\tRootfs bool\n\t// #endregion\n\n\t// #region for env flags\n\t// EntrypointChanged specifies whether the entrypoint has been changed\n\tEntrypointChanged bool\n\t// Entrypoint overwrites the default ENTRYPOINT of the image\n\tEntrypoint []string\n\t// Workdir set the working directory for the container\n\tWorkdir string\n\t// Env set environment variables\n\tEnv []string\n\t// EnvFile set environment variables from file\n\tEnvFile []string\n\t// #endregion\n\n\t// #region for metadata flags\n\t// Name assign a name to the container\n\tName string\n\t// Label set meta data on a container\n\t// (not passed through to the OCI runtime since nerdctl v2.0, with an exception for \"nerdctl/bypass4netns\")\n\tLabel []string\n\t// LabelFile read in a line delimited file of labels\n\tLabelFile []string\n\t// Annotations set meta data on a container (passed through to the OCI runtime)\n\tAnnotations []string\n\t// CidFile write the container ID to the file\n\tCidFile string\n\t// PidFile specifies the file path to write the task's pid. The CLI syntax conforms to Podman convention.\n\tPidFile string\n\t// #endregion\n\n\t// #region for logging flags\n\t// LogDriver set the logging driver for the container\n\tLogDriver string\n\t// LogOpt set logging driver specific options\n\tLogOpt []string\n\t// #endregion\n\n\t// #region for shared memory flags\n\t// IPC namespace to use\n\tIPC string\n\t// ShmSize set the size of /dev/shm\n\tShmSize string\n\t// #endregion\n\n\t// #region for gpu flags\n\t// GPUs specifies GPU devices to add to the container ('all' to pass all GPUs). Please see also ./gpu.md for details.\n\tGPUs []string\n\t// #endregion\n\n\t// #region for ulimit flags\n\t// Ulimit set ulimits\n\tUlimit []string\n\t// #endregion\n\n\t// #region for ipfs flags\n\t// IPFSAddress specifies the multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\n\tIPFSAddress string\n\t// #endregion\n\n\t// ImagePullOpt specifies image pull options which holds the ImageVerifyOptions for verifying the image.\n\tImagePullOpt ImagePullOptions\n\n\t// Healthcheck related fields\n\tHealthCmd         string\n\tHealthInterval    time.Duration\n\tHealthTimeout     time.Duration\n\tHealthRetries     int\n\tHealthStartPeriod time.Duration\n\tNoHealthcheck     bool\n\n\t// UserNS name for user namespace mapping of container\n\tUserNS string\n}\n\n// ContainerStopOptions specifies options for `nerdctl (container) stop`.\ntype ContainerStopOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Timeout specifies how long to wait after sending a SIGTERM and before sending a SIGKILL.\n\t// If it's nil, the default is 10 seconds.\n\tTimeout *time.Duration\n\n\t// Signal to send to the container, before sending SIGKILL\n\tSignal string\n}\n\n// ContainerRestartOptions specifies options for `nerdctl (container) restart`.\ntype ContainerRestartOptions struct {\n\tStdout  io.Writer\n\tGOption GlobalCommandOptions\n\t// Time to wait after sending a SIGTERM and before sending a SIGKILL.\n\tTimeout *time.Duration\n\t// Signal to send to stop the container, before sending SIGKILL\n\tSignal string\n\t// NerdctlCmd is the command name of nerdctl\n\tNerdctlCmd string\n\t// NerdctlArgs is the arguments of nerdctl\n\tNerdctlArgs []string\n}\n\n// ContainerPauseOptions specifies options for `nerdctl (container) pause`.\ntype ContainerPauseOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n}\n\n// ContainerPruneOptions specifies options for `nerdctl (container) prune`.\ntype ContainerPruneOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n}\n\n// ContainerUnpauseOptions specifies options for `nerdctl (container) unpause`.\ntype ContainerUnpauseOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// NerdctlCmd is the command name of nerdctl\n\tNerdctlCmd string\n\t// NerdctlArgs is the arguments of nerdctl\n\tNerdctlArgs []string\n}\n\n// ContainerRemoveOptions specifies options for `nerdctl (container) rm`.\ntype ContainerRemoveOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Force enables to remove a running|paused|unknown container (uses SIGKILL)\n\tForce bool\n\t// Volumes removes anonymous volumes associated with the container\n\tVolumes bool\n}\n\n// ContainerRenameOptions specifies options for `nerdctl (container) rename`.\ntype ContainerRenameOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n}\n\n// ContainerTopOptions specifies options for `nerdctl top`.\ntype ContainerTopOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\n\t// Arguments to pass through to the ps command\n\tPsArgs string\n}\n\n// ContainerInspectOptions specifies options for `nerdctl container inspect`\ntype ContainerInspectOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Format of the output\n\tFormat string\n\t// Whether to report the size\n\tSize bool\n\t// Inspect mode, either dockercompat or native\n\tMode string\n}\n\n// ContainerCommitOptions specifies options for `nerdctl (container) commit`.\ntype ContainerCommitOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Author (e.g., \"nerdctl contributor <nerdctl-dev@example.com>\")\n\tAuthor string\n\t// Commit message\n\tMessage string\n\t// Apply Dockerfile instruction to the created image (supported directives: [CMD, ENTRYPOINT])\n\tChange []string\n\t// Pause container during commit\n\tPause bool\n\t// Compression is set commit compression algorithm\n\tCompression CompressionType\n\t// Format specifies the image format for the committed image (docker or oci)\n\tFormat ImageFormat\n\t// Embed EstargzOptions for eStargz conversion options\n\tEstargzOptions\n\t// Embed ZstdChunkedOptions for zstd:chunked conversion options\n\tZstdChunkedOptions\n}\n\ntype CompressionType string\n\nconst (\n\tZstd CompressionType = \"zstd\"\n\tGzip CompressionType = \"gzip\"\n)\n\ntype ImageFormat string\n\nconst (\n\t// ImageFormatDocker uses Docker Schema2 media types for compatibility\n\tImageFormatDocker ImageFormat = \"docker\"\n\t// ImageFormatOCI uses OCI Image Format media types\n\tImageFormatOCI ImageFormat = \"oci\"\n)\n\n// ContainerDiffOptions specifies options for `nerdctl (container) diff`.\ntype ContainerDiffOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n}\n\n// ContainerLogsOptions specifies options for `nerdctl (container) logs`.\ntype ContainerLogsOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// Follow specifies whether to stream the logs or just print the existing logs.\n\tFollow bool\n\t// Timestamps specifies whether to show the timestamps of the logs.\n\tTimestamps bool\n\t// Tail specifies the number of lines to show from the end of the logs.\n\t// Specify 0 to show all logs.\n\tTail uint\n\t// Show logs since timestamp (e.g., 2013-01-02T13:23:37Z) or relative (e.g., 42m for 42 minutes).\n\tSince string\n\t// Show logs before a timestamp (e.g., 2013-01-02T13:23:37Z) or relative (e.g., 42m for 42 minutes).\n\tUntil string\n\t// Details specifies whether to show extra details provided to logs\n\tDetails bool\n}\n\n// ContainerWaitOptions specifies options for `nerdctl (container) wait`.\ntype ContainerWaitOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n}\n\n// ContainerAttachOptions specifies options for `nerdctl (container) attach`.\ntype ContainerAttachOptions struct {\n\tStdin  io.Reader\n\tStdout io.Writer\n\tStderr io.Writer\n\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// DetachKeys is the key sequences to detach from the container.\n\tDetachKeys string\n}\n\n// ContainerExecOptions specifies options for `nerdctl (container) exec`\ntype ContainerExecOptions struct {\n\tGOptions GlobalCommandOptions\n\t// Allocate a pseudo-TTY\n\tTTY bool\n\t// Keep STDIN open even if not attached\n\tInteractive bool\n\t// Detached mode: run command in the background\n\tDetach bool\n\t// Working directory inside the container\n\tWorkdir string\n\t// Set environment variables\n\tEnv []string\n\t// Set environment variables from file\n\tEnvFile []string\n\t// Give extended privileges to the command\n\tPrivileged bool\n\t// Username or UID (format: <name|uid>[:<group|gid>])\n\tUser string\n}\n\n// ContainerListOptions specifies options for `nerdctl (container) list`.\ntype ContainerListOptions struct {\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// Show all containers (default shows just running).\n\tAll bool\n\t// Show n last created containers (includes all states). Non-positive values are ignored.\n\t// In other words, if LastN is positive, All will be set to true.\n\tLastN int\n\t// Truncate output (e.g., container ID, command of the container main process, etc.) or not.\n\tTruncate bool\n\t// Display total file sizes.\n\tSize bool\n\t// Filters matches containers based on given conditions.\n\tFilters []string\n}\n\n// ContainerCpOptions specifies options for `nerdctl (container) cp`\ntype ContainerCpOptions struct {\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// ContainerReq is name, short ID, or long ID of container to copy to/from.\n\tContainerReq   string\n\tContainer2Host bool\n\t// Destination path to copy file to.\n\tDestPath string\n\t// Source path to copy file from.\n\tSrcPath string\n\t// Follow symbolic links in SRC_PATH\n\tFollowSymLink bool\n\t// true if copying to container from tarball in stdin\n\tFromStdin bool\n\t// true if copying from container to stdout in tarball format\n\tToStdout bool\n}\n\n// ContainerStatsOptions specifies options for `nerdctl stats`.\ntype ContainerStatsOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// Show all containers (default shows just running).\n\tAll bool\n\t// Pretty-print images using a Go template, e.g., {{json .}}.\n\tFormat string\n\t// Disable streaming stats and only pull the first result.\n\tNoStream bool\n\t// Do not truncate output.\n\tNoTrunc bool\n}\n"
  },
  {
    "path": "pkg/api/types/cri/metadata_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Forked from https://github.com/containerd/containerd/blob/main/pkg/cri/store/container/metadata.go\n// Copyright The containerd Authors.\n// Licensed under the Apache License, Version 2.0\n// NOTE: we just want to get the key information of metadata( such as logpath), not all the metadata.\n\npackage cri\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\n// NOTE(random-liu):\n// 1) Metadata is immutable after created.\n// 2) Metadata is checkpointed as containerd container label.\n\n// metadataVersion is current version of container metadata.\nconst metadataVersion = \"v1\" // nolint\n\n// ContainerVersionedMetadata is the internal versioned container metadata.\n// nolint\ntype ContainerVersionedMetadata struct {\n\t// Version indicates the version of the versioned container metadata.\n\tVersion string\n\t// Metadata's type is criContainerMetadataInternal. If not there will be a recursive call in MarshalJSON.\n\tMetadata criContainerMetadataInternal\n}\n\n// criContainerMetadataInternal is for internal use.\ntype criContainerMetadataInternal ContainerMetadata\n\n// ContainerMetadata is the unversioned container metadata.\ntype ContainerMetadata struct {\n\t// LogPath is the container log path.\n\tLogPath string\n}\n\n// MarshalJSON encodes Metadata into bytes in json format.\nfunc (c *ContainerMetadata) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(&ContainerVersionedMetadata{\n\t\tVersion:  metadataVersion,\n\t\tMetadata: criContainerMetadataInternal(*c),\n\t})\n}\n\n// UnmarshalJSON decodes Metadata from bytes.\nfunc (c *ContainerMetadata) UnmarshalJSON(data []byte) error {\n\tversioned := &ContainerVersionedMetadata{}\n\tif err := json.Unmarshal(data, versioned); err != nil {\n\t\treturn err\n\t}\n\t// Handle old version after upgrade.\n\tswitch versioned.Version {\n\tcase metadataVersion:\n\t\t*c = ContainerMetadata(versioned.Metadata)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unsupported version: %q\", versioned.Version)\n}\n"
  },
  {
    "path": "pkg/api/types/global.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"github.com/containerd/nerdctl/v2/pkg/config\"\n\ntype GlobalCommandOptions config.Config\n"
  },
  {
    "path": "pkg/api/types/image_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"io\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n)\n\n// ImageListOptions specifies options for `nerdctl image list`.\ntype ImageListOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Quiet only show numeric IDs\n\tQuiet bool\n\t// NoTrunc don't truncate output\n\tNoTrunc bool\n\t// Format the output using the given Go template, e.g, '{{json .}}', 'wide'\n\tFormat string\n\t// Filter output based on conditions provided, for the --filter argument\n\tFilters []string\n\t// NameAndRefFilter filters images by name and reference\n\tNameAndRefFilter []string\n\t// Digests show digests (compatible with Docker, unlike ID)\n\tDigests bool\n\t// Names show image names\n\tNames bool\n\t// All (unimplemented yet, always true)\n\tAll bool\n}\n\n// ImageConvertOptions specifies options for `nerdctl image convert`.\ntype ImageConvertOptions struct {\n\tStdout         io.Writer\n\tProgressOutput io.Writer\n\tGOptions       GlobalCommandOptions\n\n\t// #region generic flags\n\t// Uncompress convert tar.gz layers to uncompressed tar layers\n\tUncompress bool\n\t// Oci convert Docker media types to OCI media types\n\tOci bool\n\t// #endregion\n\n\t// #region platform flags\n\t// Platforms convert content for a specific platform\n\tPlatforms []string\n\t// AllPlatforms convert content for all platforms\n\tAllPlatforms bool\n\t// #endregion\n\n\t// Format the output using the given Go template, e.g, 'json'\n\tFormat string\n\n\t// Embed image format options\n\tEstargzOptions\n\tZstdOptions\n\tZstdChunkedOptions\n\tNydusOptions\n\tOverlaybdOptions\n\tSociConvertOptions\n}\n\n// EstargzOptions contains eStargz conversion options\ntype EstargzOptions struct {\n\t// Estargz convert legacy tar(.gz) layers to eStargz for lazy pulling. Should be used in conjunction with '--oci'\n\tEstargz bool\n\t// EstargzRecordIn read 'ctr-remote optimize --record-out=<FILE>' record file (EXPERIMENTAL)\n\tEstargzRecordIn string\n\t// EstargzCompressionLevel eStargz compression level\n\tEstargzCompressionLevel int\n\t// EstargzChunkSize eStargz chunk size\n\tEstargzChunkSize int\n\t// EstargzMinChunkSize the minimal number of bytes of data must be written in one gzip stream. (requires stargz-snapshotter >= v0.13.0)\n\tEstargzMinChunkSize int\n\t// EstargzExternalToc separate TOC JSON into another image (called \"TOC image\"). The name of TOC image is the original + \"-esgztoc\" suffix. Both eStargz and the TOC image should be pushed to the same registry. (requires stargz-snapshotter >= v0.13.0) (EXPERIMENTAL)\n\tEstargzExternalToc bool\n\t// EstargzKeepDiffID convert to esgz without changing diffID (cannot be used in conjunction with '--estargz-record-in'. must be specified with '--estargz-external-toc')\n\tEstargzKeepDiffID bool\n\t// EstargzGzipHelper helper command for decompressing layers compressed with gzip. Options: pigz, igzip, or gzip\n\tEstargzGzipHelper string\n}\n\n// ZstdOptions contains zstd conversion options\ntype ZstdOptions struct {\n\t// Zstd convert legacy tar(.gz) layers to zstd. Should be used in conjunction with '--oci'\n\tZstd bool\n\t// ZstdCompressionLevel zstd compression level\n\tZstdCompressionLevel int\n}\n\n// ZstdChunkedOptions contains zstd:chunked conversion options\ntype ZstdChunkedOptions struct {\n\t// ZstdChunked convert legacy tar(.gz) layers to zstd:chunked for lazy pulling. Should be used in conjunction with '--oci'\n\tZstdChunked bool\n\t// ZstdChunkedCompressionLevel zstd compression level\n\tZstdChunkedCompressionLevel int\n\t// ZstdChunkedChunkSize zstd chunk size\n\tZstdChunkedChunkSize int\n\t// ZstdChunkedRecordIn read 'ctr-remote optimize --record-out=<FILE>' record file (EXPERIMENTAL)\n\tZstdChunkedRecordIn string\n}\n\n// NydusOptions contains nydus conversion options\ntype NydusOptions struct {\n\t// Nydus convert legacy tar(.gz) layers to nydus for lazy pulling. Should be used in conjunction with '--oci'\n\tNydus bool\n\t// NydusBuilderPath the nydus-image binary path, if unset, search in PATH environment\n\tNydusBuilderPath string\n\t// NydusWorkDir work directory path for image conversion, default is the nerdctl data root directory\n\tNydusWorkDir string\n\t// NydusPrefetchPatterns the file path pattern list want to prefetch\n\tNydusPrefetchPatterns string\n\t// NydusCompressor nydus blob compression algorithm, possible values: `none`, `lz4_block`, `zstd`, default is `lz4_block`\n\tNydusCompressor string\n}\n\n// OverlaybdOptions contains overlaybd conversion options\ntype OverlaybdOptions struct {\n\t// Overlaybd convert tar.gz layers to overlaybd layers\n\tOverlaybd bool\n\t// OverlayFsType filesystem type for overlaybd\n\tOverlayFsType string\n\t// OverlaydbDBStr database config string for overlaybd\n\tOverlaydbDBStr string\n\t// #endregion\n}\n\ntype SociConvertOptions struct {\n\t// Soci convert image to SOCI format.\n\tSoci bool\n\t// SociOptions contains SOCI-specific options\n\tSociOptions SociOptions\n\t// #endregion\n}\n\n// ImageCryptOptions specifies options for `nerdctl image encrypt` and `nerdctl image decrypt`.\ntype ImageCryptOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Platforms convert content for a specific platform\n\tPlatforms []string\n\t// AllPlatforms convert content for all platforms\n\tAllPlatforms bool\n\t// GpgHomeDir the GPG homedir to use; by default gpg uses ~/.gnupg\"\n\tGpgHomeDir string\n\t// GpgVersion the GPG version (\"v1\" or \"v2\"), default will make an educated guess\n\tGpgVersion string\n\t// Keys a secret key's filename and an optional password separated by colon;\n\tKeys []string\n\t// DecRecipients recipient of the image; used only for PKCS7 and must be an x509 certificate\n\tDecRecipients []string\n\t// Recipients of the image is the person who can decrypt it in the form specified above (i.e. jwe:/path/to/pubkey)\n\tRecipients []string\n}\n\n// ImageInspectOptions specifies options for `nerdctl image inspect`.\ntype ImageInspectOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Mode Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output\n\tMode string\n\t// Format the output using the given Go template, e.g, 'json'\n\tFormat string\n\t// Platform inspect content for a specific platform\n\tPlatform string\n}\n\n// ImagePushOptions specifies options for `nerdctl (image) push`.\ntype ImagePushOptions struct {\n\tStdout      io.Writer\n\tGOptions    GlobalCommandOptions\n\tSignOptions ImageSignOptions\n\tSociOptions SociOptions\n\t// Platforms convert content for a specific platform\n\tPlatforms []string\n\t// AllPlatforms convert content for all platforms\n\tAllPlatforms bool\n\n\t// Estargz convert image to sStargz\n\tEstargz bool\n\t// IpfsEnsureImage ensure image is pushed to IPFS\n\tIpfsEnsureImage bool\n\t// IpfsAddress multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\n\tIpfsAddress string\n\t// Suppress verbose output\n\tQuiet bool\n\t// AllowNondistributableArtifacts allow pushing non-distributable artifacts\n\tAllowNondistributableArtifacts bool\n}\n\n// RemoteSnapshotterFlags are used for pulling with remote snapshotters\n// e.g. SOCI, stargz, overlaybd\ntype RemoteSnapshotterFlags struct {\n\tSociIndexDigest string\n}\n\n// ImagePullOptions specifies options for `nerdctl (image) pull`.\ntype ImagePullOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// ProgressOutputToStdout directs progress output to stdout instead of stderr\n\tProgressOutputToStdout bool\n\n\tGOptions      GlobalCommandOptions\n\tVerifyOptions ImageVerifyOptions\n\t// Unpack the image for the current single platform.\n\t// If nil, it will unpack automatically if only 1 platform is specified.\n\tUnpack *bool\n\t// Content for specific platforms. Empty if `--all-platforms` is true\n\tOCISpecPlatform []ocispec.Platform\n\t// Pull mode\n\tMode string\n\t// Suppress verbose output\n\tQuiet bool\n\t// multiaddr of IPFS API (default uses $IPFS_PATH env variable if defined or local directory ~/.ipfs)\n\tIPFSAddress string\n\t// Flags to pass into remote snapshotters\n\tRFlags RemoteSnapshotterFlags\n}\n\n// ImageTagOptions specifies options for `nerdctl (image) tag`.\ntype ImageTagOptions struct {\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Source is the image to be referenced.\n\tSource string\n\t// Target is the image to be created.\n\tTarget string\n}\n\n// ImageRemoveOptions specifies options for `nerdctl rmi` and `nerdctl image rm`.\ntype ImageRemoveOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Force removal of the image\n\tForce bool\n\t// Async asynchronous mode or not\n\tAsync bool\n}\n\n// ImagePruneOptions specifies options for `nerdctl image prune` and `nerdctl image rm`.\ntype ImagePruneOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// All Remove all unused images, not just dangling ones.\n\tAll bool\n\t// Filters output based on conditions provided for the --filter argument\n\tFilters []string\n\t// Force will not prompt for confirmation.\n\tForce bool\n}\n\n// ImageSaveOptions specifies options for `nerdctl (image) save`.\ntype ImageSaveOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Export content for all platforms\n\tAllPlatforms bool\n\t// Export content for a specific platform\n\tPlatform []string\n}\n\n// ImageSignOptions contains options for signing an image. It contains options from\n// all providers. The `provider` field determines which provider is used.\ntype ImageSignOptions struct {\n\t// Provider used to sign the image (none|cosign|notation)\n\tProvider string\n\t// CosignKey Path to the private key file, KMS URI or Kubernetes Secret for --sign=cosign\n\tCosignKey string\n\t// NotationKeyName Signing key name for a key previously added to notation's key list for --sign=notation\n\tNotationKeyName string\n}\n\n// ImageVerifyOptions contains options for verifying an image. It contains options from\n// all providers. The `provider` field determines which provider is used.\ntype ImageVerifyOptions struct {\n\t// Provider used to verify the image (none|cosign|notation)\n\tProvider string\n\t// CosignKey Path to the public key file, KMS URI or Kubernetes Secret for --verify=cosign\n\tCosignKey string\n\t// CosignCertificateIdentity The identity expected in a valid Fulcio certificate for --verify=cosign. Valid values include email address, DNS names, IP addresses, and URIs. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\n\tCosignCertificateIdentity string\n\t// CosignCertificateIdentityRegexp A regular expression alternative to --cosign-certificate-identity for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-identity or --cosign-certificate-identity-regexp must be set for keyless flows\n\tCosignCertificateIdentityRegexp string\n\t// CosignCertificateOidcIssuer The OIDC issuer expected in a valid Fulcio certificate for --verify=cosign, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\n\tCosignCertificateOidcIssuer string\n\t// CosignCertificateOidcIssuerRegexp A regular expression alternative to --certificate-oidc-issuer for --verify=cosign. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows\n\tCosignCertificateOidcIssuerRegexp string\n}\n\n// SociOptions contains options for SOCI.\ntype SociOptions struct {\n\t// Span size that soci index uses to segment layer data. Default is 4 MiB.\n\tSpanSize int64\n\t// Minimum layer size to build zTOC for. Smaller layers won't have zTOC and not lazy pulled. Default is 10 MiB.\n\tMinLayerSize int64\n\t// Platforms convert content for a specific platform\n\tPlatforms []string\n\t// AllPlatforms convert content for all platforms\n\tAllPlatforms bool\n}\n"
  },
  {
    "path": "pkg/api/types/import_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// ImageImportOptions specifies options for `nerdctl (image) import`.\ntype ImageImportOptions struct {\n\tStdout   io.Writer\n\tStdin    io.Reader\n\tGOptions GlobalCommandOptions\n\n\tSource    string\n\tReference string\n\tMessage   string\n\tPlatform  string\n}\n"
  },
  {
    "path": "pkg/api/types/ipfs_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"time\"\n)\n\n// IPFSRegistryServeOptions specifies options for `nerdctl ipfs registry serve`.\ntype IPFSRegistryServeOptions struct {\n\t// ListenRegistry address to listen\n\tListenRegistry string\n\t// IPFSAddress multiaddr of IPFS API (default is pulled from $IPFS_PATH/api file. If $IPFS_PATH env var is not present, it defaults to ~/.ipfs)\n\tIPFSAddress string\n\t// ReadRetryNum times to retry query on IPFS. Zero or lower means no retry.\n\tReadRetryNum int\n\t// ReadTimeout timeout duration of a read request to IPFS. Zero means no timeout.\n\tReadTimeout time.Duration\n}\n"
  },
  {
    "path": "pkg/api/types/load_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// ImageLoadOptions specifies options for `nerdctl (image) load`.\ntype ImageLoadOptions struct {\n\tStdout   io.Writer\n\tStdin    io.Reader\n\tGOptions GlobalCommandOptions\n\t// Input read from tar archive file, instead of STDIN\n\tInput string\n\t// Platform import content for a specific platform\n\tPlatform []string\n\t// AllPlatforms import content for all platforms\n\tAllPlatforms bool\n\t// Quiet suppresses the load output.\n\tQuiet bool\n}\n"
  },
  {
    "path": "pkg/api/types/login_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\ntype LoginCommandOptions struct {\n\t// GOptions is the global options.\n\tGOptions GlobalCommandOptions\n\t// ServerAddress is the server address to log in to.\n\tServerAddress string\n\t// Username is the username to log in as.\n\t//\n\t// If it's empty, it will be inferred from the default auth config.\n\t// If nothing is in the auth config, the user will be prompted to provide it.\n\tUsername string\n\t// Password is the password of the user.\n\t//\n\t// If it's empty, the user will be prompted to provide it.\n\tPassword string\n}\n"
  },
  {
    "path": "pkg/api/types/manifest_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// ManifestAnnotateOptions specifies options for `nerdctl manifest annotate`.\ntype ManifestAnnotateOptions struct {\n\tStdout     io.Writer\n\tGOptions   GlobalCommandOptions\n\tOs         string\n\tArch       string\n\tOsVersion  string\n\tVariant    string\n\tOsFeatures []string\n}\n\n// ManifestCreateOptions specifies options for `nerdctl manifest create`.\ntype ManifestCreateOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Amend an existing manifest list\n\tAmend bool\n\t// Allow communication with an insecure registry\n\tInsecure bool\n}\n\n// ManifestInspectOptions specifies options for `nerdctl manifest inspect`.\ntype ManifestInspectOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Verbose output additional info including layers and platform\n\tVerbose bool\n\t// Allow communication with an insecure registry\n\tInsecure bool\n}\n\n// ManifestPushOptions specifies options for `nerdctl manifest push`.\ntype ManifestPushOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Allow communication with an insecure registry\n\tInsecure bool\n\t// Remove the manifest list after pushing\n\tPurge bool\n}\n"
  },
  {
    "path": "pkg/api/types/namespace_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// NamespaceCreateOptions specifies options for `nerdctl namespace create`.\ntype NamespaceCreateOptions struct {\n\tGOptions GlobalCommandOptions\n\t// Labels are the namespace labels\n\tLabels []string\n}\n\n// NamespaceUpdateOptions specifies options for `nerdctl namespace update`.\ntype NamespaceUpdateOptions NamespaceCreateOptions\n\n// NamespaceRemoveOptions specifies options for `nerdctl namespace rm`.\ntype NamespaceRemoveOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// CGroup delete the namespace's cgroup\n\tCGroup bool\n}\n\n// NamespaceInspectOptions specifies options for `nerdctl namespace inspect`.\ntype NamespaceInspectOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Format the output using the given Go template, e.g, '{{json .}}'\n\tFormat string\n}\n\n// NamespaceListOptions specifies options for `nerdctl namespace ls`.\ntype NamespaceListOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Format the output using the given Go template, e.g, '{{json .}}'\n\tFormat string\n\t// Quiet suppresses extra information and only prints namespace names\n\tQuiet bool\n}\n"
  },
  {
    "path": "pkg/api/types/network_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"io\"\n)\n\n// NetworkCreateOptions specifies options for `nerdctl network create`.\ntype NetworkCreateOptions struct {\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\n\tName        string\n\tDriver      string\n\tOptions     map[string]string\n\tIPAMDriver  string\n\tIPAMOptions map[string]string\n\tSubnets     []string\n\tGateway     string\n\tIPRange     string\n\tLabels      []string\n\tIPv6        bool\n\tInternal    bool\n}\n\n// NetworkInspectOptions specifies options for `nerdctl network inspect`.\ntype NetworkInspectOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output\n\tMode string\n\t// Format the output using the given Go template, e.g, '{{json .}}'\n\tFormat string\n\t// Networks are the networks to be inspected\n\tNetworks []string\n}\n\n// NetworkListOptions specifies options for `nerdctl network ls`.\ntype NetworkListOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Quiet only show numeric IDs\n\tQuiet bool\n\t// Format the output using the given Go template, e.g, '{{json .}}', 'wide'\n\tFormat string\n\t// Filter matches network based on given conditions\n\tFilters []string\n}\n\n// NetworkPruneOptions specifies options for `nerdctl network prune`.\ntype NetworkPruneOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Network drivers to keep while pruning\n\tNetworkDriversToKeep []string\n}\n\n// NetworkRemoveOptions specifies options for `nerdctl network rm`.\ntype NetworkRemoveOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Networks are the networks to be removed\n\tNetworks []string\n}\n"
  },
  {
    "path": "pkg/api/types/search_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport (\n\t\"io\"\n)\n\ntype SearchOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\n\t// NoTrunc don't truncate output\n\tNoTrunc bool\n\t// Limit the number of results\n\tLimit int\n\t// Filter output based on conditions provided, for the --filter argument\n\tFilters []string\n\t// Format the output using the given Go template, e.g, '{{json .}}'\n\tFormat string\n}\n"
  },
  {
    "path": "pkg/api/types/system_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// SystemInfoOptions specifies options for `nerdctl (system) info`.\ntype SystemInfoOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Information mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output\n\tMode string\n\t// Format the output using the given Go template, e.g, '{{json .}}\n\tFormat string\n}\n\n// SystemEventsOptions specifies options for `nerdctl (system) events`.\ntype SystemEventsOptions struct {\n\tStdout io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// Format the output using the given Go template, e.g, '{{json .}}\n\tFormat string\n\t// Filter events based on given conditions\n\tFilters []string\n}\n\n// SystemPruneOptions specifies options for `nerdctl system prune`.\ntype SystemPruneOptions struct {\n\tStdout io.Writer\n\tStderr io.Writer\n\t// GOptions is the global options\n\tGOptions GlobalCommandOptions\n\t// All remove all unused images, not just dangling ones\n\tAll bool\n\t// Volumes decide whether prune volumes or not\n\tVolumes bool\n\t// BuildKitHost the address of BuildKit host\n\tBuildKitHost string\n\t// NetworkDriversToKeep the network drivers which need to keep\n\tNetworkDriversToKeep []string\n}\n"
  },
  {
    "path": "pkg/api/types/volume_types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage types\n\nimport \"io\"\n\n// VolumeCreateOptions specifies options for `nerdctl volume create`.\ntype VolumeCreateOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Labels are the volume labels\n\tLabels []string\n}\n\n// VolumeInspectOptions specifies options for `nerdctl volume inspect`.\ntype VolumeInspectOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Format the output using the given go template\n\tFormat string\n\t// Display the disk usage of volumes. Can be slow with volumes having loads of directories.\n\tSize bool\n}\n\n// VolumeListOptions specifies options for `nerdctl volume ls`.\ntype VolumeListOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Only display volume names\n\tQuiet bool\n\t// Format the output using the given go template\n\tFormat string\n\t// Display the disk usage of volumes. Can be slow with volumes having loads of directories.\n\tSize bool\n\t// Filter matches volumes based on given conditions\n\tFilters []string\n}\n\n// VolumePruneOptions specifies options for `nerdctl volume prune`.\ntype VolumePruneOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t//Remove all unused volumes, not just anonymous ones\n\tAll bool\n\t// Do not prompt for confirmation\n\tForce bool\n}\n\n// VolumeRemoveOptions specifies options for `nerdctl volume rm`.\ntype VolumeRemoveOptions struct {\n\tStdout   io.Writer\n\tGOptions GlobalCommandOptions\n\t// Force the removal of one or more volumes\n\tForce bool\n}\n"
  },
  {
    "path": "pkg/apparmorutil/apparmorutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package apparmorutil provides utilities for AppArmor\npackage apparmorutil\n\n// This apparmor.go is split from apparmorutil_linux.go, to avoid\n// \"build constraints exclude all Go files\" error on non-Linux\n"
  },
  {
    "path": "pkg/apparmorutil/apparmorutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmorutil\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/moby/sys/userns\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nvar (\n\tappArmorSupported bool\n\tcheckAppArmor     sync.Once\n)\n\n// hostSupports returns true if apparmor is enabled for the host\n// We cannot use containerd implementation because it explicitly prevents it from working inside a container.\nfunc hostSupports() bool {\n\tcheckAppArmor.Do(func() {\n\t\tvar pth string\n\t\tif _, err := os.Stat(\"/sys/kernel/security/apparmor\"); err != nil {\n\t\t\tappArmorSupported = false\n\t\t\treturn\n\t\t}\n\t\t// In some rare circumstances, apparmor may be enabled, but the tooling could be missing\n\t\t// containerd implementation shells out to aa-parser, so, require it here.\n\t\t// See https://github.com/containerd/nerdctl/issues/3945 for details.\n\t\tpth, err := exec.LookPath(\"apparmor_parser\")\n\t\tif err != nil {\n\t\t\tappArmorSupported = false\n\t\t\treturn\n\t\t}\n\t\tif _, err = os.Stat(pth); err != nil {\n\t\t\tappArmorSupported = false\n\t\t\treturn\n\t\t}\n\t\tvar buf []byte\n\t\tbuf, err = filesystem.ReadFile(\"/sys/module/apparmor/parameters/enabled\")\n\t\tappArmorSupported = err == nil && len(buf) == 2 && string(buf) == \"Y\\n\"\n\t})\n\treturn appArmorSupported\n}\n\n// CanLoadNewProfile returns whether the current process can load a new AppArmor profile.\n//\n// CanLoadNewProfile needs root.\n//\n// CanLoadNewProfile checks both /sys/module/apparmor/parameters/enabled and /sys/kernel/security.\n//\n// Related: https://gitlab.com/apparmor/apparmor/-/blob/v3.0.3/libraries/libapparmor/src/kernel.c#L311\nfunc CanLoadNewProfile() bool {\n\treturn !userns.RunningInUserNS() && os.Geteuid() == 0 && hostSupports()\n}\n\nvar (\n\tparamEnabled     bool\n\tparamEnabledOnce sync.Once\n)\n\n// CanApplyExistingProfile returns whether the current process can apply an existing AppArmor profile\n// to processes.\n//\n// CanApplyExistingProfile does NOT need root.\n//\n// CanApplyExistingProfile checks /sys/module/apparmor/parameters/enabled ,but does NOT check /sys/kernel/security/apparmor ,\n// which might not be accessible from user namespaces (because securityfs cannot be mounted in a user namespace)\n//\n// Related: https://gitlab.com/apparmor/apparmor/-/blob/v3.0.3/libraries/libapparmor/src/kernel.c#L311\nfunc CanApplyExistingProfile() bool {\n\tparamEnabledOnce.Do(func() {\n\t\tbuf, err := filesystem.ReadFile(\"/sys/module/apparmor/parameters/enabled\")\n\t\tparamEnabled = err == nil && len(buf) == 2 && string(buf) == \"Y\\n\"\n\t})\n\treturn paramEnabled\n}\n\n// CanApplySpecificExistingProfile attempts to run `aa-exec -p <NAME> -- true` to check whether\n// the profile can be applied.\n//\n// CanApplySpecificExistingProfile does NOT depend on /sys/kernel/security/apparmor/profiles ,\n// which might not be accessible from user namespaces (because securityfs cannot be mounted in a user namespace)\nfunc CanApplySpecificExistingProfile(profileName string) bool {\n\tif !CanApplyExistingProfile() {\n\t\treturn false\n\t}\n\tcmd := exec.Command(\"aa-exec\", \"-p\", profileName, \"--\", \"true\")\n\tout, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tlog.L.WithError(err).Debugf(\"failed to run %v: %q\", cmd.Args, string(out))\n\t\treturn false\n\t}\n\treturn true\n}\n\ntype Profile struct {\n\tName string `json:\"Name\"`           // e.g., \"nerdctl-default\"\n\tMode string `json:\"Mode,omitempty\"` // e.g., \"enforce\"\n}\n\n// Profiles return profiles.\n//\n// Profiles does not need the root but needs access to /sys/kernel/security/apparmor/policy/profiles,\n// which might not be accessible from user namespaces (because securityfs cannot be mounted in a user namespace)\n//\n// So, Profiles cannot be called from rootless child.\nfunc Profiles() ([]Profile, error) {\n\tconst profilesPath = \"/sys/kernel/security/apparmor/policy/profiles\"\n\tents, err := os.ReadDir(profilesPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres := make([]Profile, len(ents))\n\tfor i, ent := range ents {\n\t\tnamePath := filepath.Join(profilesPath, ent.Name(), \"name\")\n\t\tb, err := filesystem.ReadFile(namePath)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"failed to read %q\", namePath)\n\t\t\tcontinue\n\t\t}\n\t\tprofile := Profile{\n\t\t\tName: strings.TrimSpace(string(b)),\n\t\t}\n\t\tmodePath := filepath.Join(profilesPath, ent.Name(), \"mode\")\n\t\tb, err = os.ReadFile(modePath)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"failed to read %q\", namePath)\n\t\t} else {\n\t\t\tprofile.Mode = strings.TrimSpace(string(b))\n\t\t}\n\t\tres[i] = profile\n\t}\n\treturn res, nil\n}\n\n// Unload unloads a profile. Needs access to /sys/kernel/security/apparmor/.remove .\nfunc Unload(target string) error {\n\tremover, err := os.OpenFile(\"/sys/kernel/security/apparmor/.remove\", os.O_RDWR|os.O_TRUNC, 0644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, err := remover.Write([]byte(target)); err != nil {\n\t\tremover.Close()\n\t\treturn err\n\t}\n\treturn remover.Close()\n}\n"
  },
  {
    "path": "pkg/buildkitutil/buildkitutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/docker/cli/blob/v20.10.9/cli/command/image/build/context.go\n   Copyright (C) Docker authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/docker/cli/blob/v20.10.9/NOTICE\n*/\n\npackage buildkitutil\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nconst (\n\t// DefaultDockerfileName is the Default filename, read by nerdctl build\n\tDefaultDockerfileName string = \"Dockerfile\"\n\tContainerfileName     string = \"Containerfile\"\n\n\tTempDockerfileName string = \"docker-build-tempdockerfile-\"\n)\n\nfunc BuildctlBinary() (string, error) {\n\treturn exec.LookPath(\"buildctl\")\n}\n\nfunc BuildctlBaseArgs(buildkitHost string) []string {\n\treturn []string{\"--addr=\" + buildkitHost}\n}\n\nfunc GetBuildkitHost(namespace string) (string, error) {\n\tif buildkitHost := os.Getenv(\"BUILDKIT_HOST\"); buildkitHost != \"\" {\n\t\tif _, err := pingBKDaemon(buildkitHost); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn buildkitHost, nil\n\t}\n\n\tpaths, err := getBuildkitHostCandidates(namespace)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar errs []error //nolint:prealloc\n\tfor _, buildkitHost := range paths {\n\t\tlog.L.Debugf(\"Choosing the buildkit host %q, candidates=%v\", buildkitHost, paths)\n\t\t_, err := pingBKDaemon(buildkitHost)\n\t\tif err == nil {\n\t\t\tlog.L.Debugf(\"Chosen buildkit host %q\", buildkitHost)\n\t\t\treturn buildkitHost, nil\n\t\t}\n\t\terrs = append(errs, fmt.Errorf(\"failed to ping to host %s: %w\", buildkitHost, err))\n\t}\n\tallErr := errors.Join(errs...)\n\tlog.L.WithError(allErr).Error(getHint())\n\treturn \"\", fmt.Errorf(\"no buildkit host is available, tried %d candidates: %w\", len(paths), allErr)\n}\n\nfunc GetWorkerLabels(buildkitHost string) (labels map[string]string, _ error) {\n\tbuildctlBinary, err := BuildctlBinary()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs := BuildctlBaseArgs(buildkitHost)\n\targs = append(args, \"debug\", \"workers\", \"--format\", \"{{json .}}\")\n\tbuildctlCheckCmd := exec.Command(buildctlBinary, args...)\n\tbuildctlCheckCmd.Env = os.Environ()\n\tout, err := buildctlCheckCmd.Output()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar workers []json.RawMessage\n\tif err := json.Unmarshal(out, &workers); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(workers) == 0 {\n\t\treturn nil, fmt.Errorf(\"no worker available\")\n\t}\n\tmetadata := map[string]json.RawMessage{}\n\tif err := json.Unmarshal(workers[0], &metadata); err != nil {\n\t\treturn nil, err\n\t}\n\tlabelsRaw, ok := metadata[\"labels\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"worker doesn't have labels\")\n\t}\n\tlabels = map[string]string{}\n\tif err := json.Unmarshal(labelsRaw, &labels); err != nil {\n\t\treturn nil, err\n\t}\n\treturn labels, nil\n}\n\nfunc getHint() string {\n\thint := \"`buildctl` needs to be installed and `buildkitd` needs to be running, see https://github.com/moby/buildkit\"\n\tif rootlessutil.IsRootless() {\n\t\thint += \" , and `containerd-rootless-setuptool.sh install-buildkit` for OCI worker or `containerd-rootless-setuptool.sh install-buildkit-containerd` for containerd worker\"\n\t}\n\treturn hint\n}\n\nfunc PingBKDaemon(buildkitHost string) error {\n\tif out, err := pingBKDaemon(buildkitHost); err != nil {\n\t\tif out != \"\" {\n\t\t\tlog.L.Error(out)\n\t\t}\n\t\treturn fmt.Errorf(getHint()+\": %w\", err)\n\t}\n\treturn nil\n}\n\nfunc pingBKDaemon(buildkitHost string) (output string, _ error) {\n\tsupportedOses := []string{\"linux\", \"freebsd\", \"windows\"}\n\tif !slices.Contains(supportedOses, runtime.GOOS) {\n\t\treturn \"\", fmt.Errorf(\"only %s are supported\", strings.Join(supportedOses, \", \"))\n\t}\n\tbuildctlBinary, err := BuildctlBinary()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\targs := BuildctlBaseArgs(buildkitHost)\n\targs = append(args, \"debug\", \"workers\")\n\tbuildctlCheckCmd := exec.Command(buildctlBinary, args...)\n\tbuildctlCheckCmd.Env = os.Environ()\n\tif out, err := buildctlCheckCmd.CombinedOutput(); err != nil {\n\t\treturn string(out), err\n\t}\n\treturn \"\", nil\n}\n\n// WriteTempDockerfile is from https://github.com/docker/cli/blob/v20.10.9/cli/command/image/build/context.go#L118\nfunc WriteTempDockerfile(rc io.Reader) (dockerfileDir string, err error) {\n\t// err is a named return value, due to the defer call below.\n\tdockerfileDir, err = os.MkdirTemp(\"\", TempDockerfileName)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"unable to create temporary context directory: %v\", err)\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tos.RemoveAll(dockerfileDir)\n\t\t}\n\t}()\n\n\tf, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer f.Close()\n\tif _, err := io.Copy(f, rc); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn dockerfileDir, nil\n}\n\n// BuildKitFile returns the values for the following buildctl args\n// --localfilename=dockerfile={absDir}\n// --opt=filename={file}\nfunc BuildKitFile(dir, inputfile string) (absDir string, file string, err error) {\n\tfile = inputfile\n\tif file == \"\" || file == \".\" {\n\t\tfile = DefaultDockerfileName\n\t}\n\tabsDir, err = filepath.Abs(dir)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tif file != DefaultDockerfileName {\n\t\tif _, err := os.Lstat(filepath.Join(absDir, file)); err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\t} else {\n\t\t_, dErr := os.Lstat(filepath.Join(absDir, file))\n\t\t_, cErr := os.Lstat(filepath.Join(absDir, ContainerfileName))\n\t\tif dErr == nil && cErr == nil {\n\t\t\t// both files exist, prefer Dockerfile.\n\t\t\tdockerfile, err := filesystem.ReadFile(filepath.Join(absDir, DefaultDockerfileName))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", \"\", err\n\t\t\t}\n\t\t\tcontainerfile, err := filesystem.ReadFile(filepath.Join(absDir, ContainerfileName))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", \"\", err\n\t\t\t}\n\t\t\tif !bytes.Equal(dockerfile, containerfile) {\n\t\t\t\tlog.L.Warnf(\"%s and %s have different contents, building with %s\", DefaultDockerfileName, ContainerfileName, DefaultDockerfileName)\n\t\t\t}\n\t\t}\n\t\tif dErr != nil {\n\t\t\tif errors.Is(dErr, fs.ErrNotExist) {\n\t\t\t\tfile = ContainerfileName\n\t\t\t} else {\n\t\t\t\treturn \"\", \"\", dErr\n\t\t\t}\n\t\t\tif cErr != nil {\n\t\t\t\treturn \"\", \"\", cErr\n\t\t\t}\n\t\t}\n\t}\n\treturn absDir, file, nil\n}\n"
  },
  {
    "path": "pkg/buildkitutil/buildkitutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage buildkitutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc getRuntimeVariableDataDir() (string, error) {\n\t// Per Linux Foundation \"Filesystem Hierarchy Standard\" version 3.0 section 3.15.\n\t// Under version 2.3, this was \"/var/run\".\n\trun := \"/run\"\n\tif rootlessutil.IsRootless() {\n\t\tvar err error\n\t\trun, err = rootlessutil.XDGRuntimeDir()\n\t\tif err != nil {\n\t\t\tif rootlessutil.IsRootlessChild() {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\trun = fmt.Sprintf(\"/run/user/%d\", os.Geteuid())\n\t\t}\n\t}\n\treturn run, nil\n}\n"
  },
  {
    "path": "pkg/buildkitutil/buildkitutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/docker/cli/blob/v20.10.9/cli/command/image/build/context.go\n   Copyright (C) Docker authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/docker/cli/blob/v20.10.9/NOTICE\n*/\n\npackage buildkitutil\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nfunc TestBuildKitFile(t *testing.T) {\n\tvar tmp = t.TempDir()\n\tvar wd, err = os.Getwd()\n\tassert.NilError(t, err)\n\ttmp, err = filepath.EvalSymlinks(tmp)\n\tassert.NilError(t, err)\n\terr = os.Chdir(tmp)\n\tassert.NilError(t, err)\n\tdefer os.Chdir(wd)\n\ttype args struct {\n\t\tdir       string\n\t\tinputfile string\n\t}\n\ttests := []struct {\n\t\tname       string\n\t\targs       args\n\t\tprepare    func(t *testing.T) error\n\t\twantAbsDir string\n\t\twantFile   string\n\t\twantErr    bool\n\t}{\n\t\t{\n\t\t\tname: \"only Dockerfile is present\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, DefaultDockerfileName), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{\".\", \"\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   DefaultDockerfileName,\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"only Containerfile is present\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, \"Containerfile\"), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{\".\", \"\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   ContainerfileName,\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"both Dockerfile and Containerfile are present\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\tvar err = filesystem.WriteFile(filepath.Join(tmp, \"Dockerfile\"), []byte{}, 0644)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, \"Containerfile\"), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{\".\", \"\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   DefaultDockerfileName,\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Dockerfile and Containerfile have different contents\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\tvar err = filesystem.WriteFile(filepath.Join(tmp, \"Dockerfile\"), []byte{'d'}, 0644)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, \"Containerfile\"), []byte{'c'}, 0644)\n\t\t\t},\n\t\t\targs:       args{\".\", \"\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   DefaultDockerfileName,\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Custom file is specfied\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, \"CustomFile\"), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{\".\", \"CustomFile\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   \"CustomFile\",\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Absolute path is specified along with custom file\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, \"CustomFile\"), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{tmp, \"CustomFile\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   \"CustomFile\",\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Absolute path is specified along with Docker file\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, \"Dockerfile\"), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{tmp, \".\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   DefaultDockerfileName,\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"Absolute path is specified with Container file in the path\",\n\t\t\tprepare: func(t *testing.T) error {\n\t\t\t\treturn filesystem.WriteFile(filepath.Join(tmp, ContainerfileName), []byte{}, 0644)\n\t\t\t},\n\t\t\targs:       args{tmp, \".\"},\n\t\t\twantAbsDir: tmp,\n\t\t\twantFile:   ContainerfileName,\n\t\t\twantErr:    false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.prepare(t)\n\t\t\tgotAbsDir, gotFile, err := BuildKitFile(tt.args.dir, tt.args.inputfile)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BuildKitFile() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotAbsDir != tt.wantAbsDir {\n\t\t\t\tt.Errorf(\"BuildKitFile() gotAbsDir = %v, want %v\", gotAbsDir, tt.wantAbsDir)\n\t\t\t}\n\t\t\tif gotFile != tt.wantFile {\n\t\t\t\tt.Errorf(\"BuildKitFile() gotFile = %v, want %v\", gotFile, tt.wantFile)\n\t\t\t}\n\n\t\t\tentry, err := os.ReadDir(tmp)\n\t\t\tassert.NilError(t, err)\n\t\t\tfor _, f := range entry {\n\t\t\t\terr = os.Remove(f.Name())\n\t\t\t\tassert.NilError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/buildkitutil/buildkitutil_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage buildkitutil\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n)\n\nfunc getBuildkitHostCandidates(namespace string) ([]string, error) {\n\tif namespace == \"\" {\n\t\treturn []string{}, fmt.Errorf(\"namespace must be specified\")\n\t}\n\t// Try candidate locations of the current containerd namespace.\n\trun, err := getRuntimeVariableDataDir()\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\tvar candidates []string\n\tif namespace != \"default\" {\n\t\tcandidates = append(candidates, \"unix://\"+filepath.Join(run, fmt.Sprintf(\"buildkit-%s/buildkitd.sock\", namespace)))\n\t}\n\tcandidates = append(candidates, \"unix://\"+filepath.Join(run, \"buildkit-default/buildkitd.sock\"), \"unix://\"+filepath.Join(run, \"buildkit/buildkitd.sock\"))\n\n\treturn candidates, nil\n}\n"
  },
  {
    "path": "pkg/buildkitutil/buildkitutil_unix_nolinux.go",
    "content": "//go:build unix && !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage buildkitutil\n\nfunc getRuntimeVariableDataDir() (string, error) {\n\t// Per hier(7) dated July 6, 2023.\n\treturn \"/var/run\", nil\n}\n"
  },
  {
    "path": "pkg/buildkitutil/buildkitutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage buildkitutil\n\nfunc getBuildkitHostCandidates(namespace string) ([]string, error) {\n\treturn []string{\"npipe:////./pipe/buildkitd\"}, nil\n}\n"
  },
  {
    "path": "pkg/buildkitutil/types.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/buildkit/blob/v0.11.0/client/diskusage.go\n   Copyright (C) The BuildKit authors.\n   Licensed under the Apache License, Version 2.0\n*/\n\npackage buildkitutil\n\nimport \"time\"\n\n// UsageInfo is from https://github.com/moby/buildkit/blob/v0.11.0/client/diskusage.go#L12-L25\ntype UsageInfo struct {\n\tID      string `json:\"id\"`\n\tMutable bool   `json:\"mutable\"`\n\tInUse   bool   `json:\"inUse\"`\n\tSize    int64  `json:\"size\"`\n\n\tCreatedAt   time.Time       `json:\"createdAt\"`\n\tLastUsedAt  *time.Time      `json:\"lastUsedAt\"`\n\tUsageCount  int             `json:\"usageCount\"`\n\tParents     []string        `json:\"parents\"`\n\tDescription string          `json:\"description\"`\n\tRecordType  UsageRecordType `json:\"recordType\"`\n\tShared      bool            `json:\"shared\"`\n}\n\n// UsageRecordType is from https://github.com/moby/buildkit/blob/v0.11.0/client/diskusage.go#L75\ntype UsageRecordType string\n"
  },
  {
    "path": "pkg/bypass4netnsutil/bypass.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage bypass4netnsutil\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\n\tb4nnapi \"github.com/rootless-containers/bypass4netns/pkg/api\"\n\t\"github.com/rootless-containers/bypass4netns/pkg/api/daemon/client\"\n\trlkclient \"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/annotations\"\n)\n\nfunc NewBypass4netnsCNIBypassManager(client client.Client, rlkClient rlkclient.Client, annotationsMap map[string]string) (*Bypass4netnsCNIBypassManager, error) {\n\tif client == nil || rlkClient == nil {\n\t\treturn nil, errdefs.ErrInvalidArgument\n\t}\n\tenabled, bindEnabled, err := IsBypass4netnsEnabled(annotationsMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !enabled {\n\t\treturn nil, errdefs.ErrInvalidArgument\n\t}\n\tvar ignoreSubnets []string\n\tif v := annotationsMap[annotations.Bypass4netnsIgnoreSubnets]; v != \"\" {\n\t\tif err := json.Unmarshal([]byte(v), &ignoreSubnets); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to unmarshal annotation %q: %q: %w\", annotations.Bypass4netnsIgnoreSubnets, v, err)\n\t\t}\n\t}\n\tpm := &Bypass4netnsCNIBypassManager{\n\t\tClient:        client,\n\t\trlkClient:     rlkClient,\n\t\tignoreSubnets: ignoreSubnets,\n\t\tignoreBind:    !bindEnabled,\n\t}\n\treturn pm, nil\n}\n\ntype Bypass4netnsCNIBypassManager struct {\n\tclient.Client\n\trlkClient     rlkclient.Client\n\tignoreSubnets []string\n\tignoreBind    bool\n}\n\nfunc (b4nnm *Bypass4netnsCNIBypassManager) StartBypass(ctx context.Context, ports []cni.PortMapping, id, stateDir string) error {\n\tsocketPath, err := GetSocketPathByID(id)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpidFilePath, err := GetPidFilePathByID(id)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogFilePath := filepath.Join(stateDir, \"bypass4netns.log\")\n\n\trlkInfo, err := b4nnm.rlkClient.Info(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif rlkInfo.NetworkDriver == nil {\n\t\treturn fmt.Errorf(\"no network driver is set in RootlessKit info: %+v\", rlkInfo)\n\t}\n\trlkIP := rlkInfo.NetworkDriver.ChildIP\n\tconst mask = 24 // currently hard-coded\n\trlkCIDR := fmt.Sprintf(\"%s/%d\", rlkIP.Mask(net.CIDRMask(mask, 32)), mask)\n\n\tspec := b4nnapi.BypassSpec{\n\t\tID:          id,\n\t\tSocketPath:  socketPath,\n\t\tPidFilePath: pidFilePath,\n\t\tLogFilePath: logFilePath,\n\t\t// \"auto\" can detect CNI CIDRs automatically\n\t\tIgnoreSubnets: append([]string{\"127.0.0.0/8\", rlkCIDR, \"auto\"}, b4nnm.ignoreSubnets...),\n\t\tIgnoreBind:    b4nnm.ignoreBind,\n\t}\n\tif !b4nnm.ignoreBind {\n\t\tportMap := []b4nnapi.PortSpec{}\n\t\tfor _, p := range ports {\n\t\t\tportMap = append(portMap, b4nnapi.PortSpec{\n\t\t\t\tParentIP:   p.HostIP,\n\t\t\t\tParentPort: int(p.HostPort),\n\t\t\t\tChildPort:  int(p.ContainerPort),\n\t\t\t\tProtos:     []string{p.Protocol},\n\t\t\t})\n\t\t}\n\t\tspec.PortMapping = portMap\n\t}\n\t_, err = b4nnm.BypassManager().StartBypass(ctx, spec)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (b4nnm *Bypass4netnsCNIBypassManager) StopBypass(ctx context.Context, id string) error {\n\terr := b4nnm.BypassManager().StopBypass(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/bypass4netnsutil/bypass4netnsutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage bypass4netnsutil\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\tb4nnoci \"github.com/rootless-containers/bypass4netns/pkg/oci\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/annotations\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc generateSecurityOpt(listenerPath string) (oci.SpecOpts, error) {\n\topt := func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Seccomp == nil {\n\t\t\ts.Linux.Seccomp = b4nnoci.GetDefaultSeccompProfile(listenerPath)\n\t\t} else {\n\t\t\tsc, err := b4nnoci.TranslateSeccompProfile(*s.Linux.Seccomp, listenerPath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ts.Linux.Seccomp = sc\n\t\t}\n\t\treturn nil\n\t}\n\treturn opt, nil\n}\n\nfunc GenerateBypass4netnsOpts(securityOptsMaps map[string]string, annotationsMap map[string]string, id string) ([]oci.SpecOpts, error) {\n\tb4nn, ok := annotationsMap[annotations.Bypass4netns]\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\n\tb4nnEnable, err := strconv.ParseBool(b4nn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !b4nnEnable {\n\t\treturn nil, nil\n\t}\n\n\tsocketPath, err := GetSocketPathByID(id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = CreateSocketDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\topts := []oci.SpecOpts{}\n\topt, err := generateSecurityOpt(socketPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, opt)\n\n\treturn opts, nil\n}\n\nfunc CreateSocketDir() error {\n\txdgRuntimeDir, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdirPath := filepath.Join(xdgRuntimeDir, \"bypass4netns\")\n\tif _, err := os.Stat(dirPath); os.IsNotExist(err) {\n\t\terr = os.MkdirAll(dirPath, 0775)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc GetBypass4NetnsdDefaultSocketPath() (string, error) {\n\txdgRuntimeDir, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.Join(xdgRuntimeDir, \"bypass4netnsd.sock\"), nil\n}\n\nfunc GetSocketPathByID(id string) (string, error) {\n\txdgRuntimeDir, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsocketPath := filepath.Join(xdgRuntimeDir, \"bypass4netns\", id[0:15]+\".sock\")\n\treturn socketPath, nil\n}\n\nfunc GetPidFilePathByID(id string) (string, error) {\n\txdgRuntimeDir, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpidPath := filepath.Join(xdgRuntimeDir, \"bypass4netns\", id[0:15]+\".pid\")\n\n\terr = os.MkdirAll(filepath.Join(xdgRuntimeDir, \"bypass4netns\"), 0o700)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn pidPath, nil\n}\n\nfunc IsBypass4netnsEnabled(annotationsMap map[string]string) (enabled, bindEnabled bool, err error) {\n\tif b4nn, ok := annotationsMap[annotations.Bypass4netns]; ok {\n\t\tenabled, err = strconv.ParseBool(b4nn)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tbindEnabled = enabled\n\t\tif s, ok := annotationsMap[annotations.Bypass4netnsIgnoreBind]; ok {\n\t\t\tvar bindDisabled bool\n\t\t\tbindDisabled, err = strconv.ParseBool(s)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbindEnabled = !bindDisabled\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/checkpointutil/checkpointutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpointutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc GetCheckpointDir(checkpointDir, checkpointID, containerID string, create bool) (string, error) {\n\tcheckpointAbsDir := filepath.Join(checkpointDir, checkpointID)\n\tstat, err := os.Stat(checkpointAbsDir)\n\tif create {\n\t\tswitch {\n\t\tcase err == nil && stat.IsDir():\n\t\t\terr = fmt.Errorf(\"checkpoint with name %s already exists for container %s\", checkpointID, containerID)\n\t\tcase err != nil && os.IsNotExist(err):\n\t\t\terr = os.MkdirAll(checkpointAbsDir, 0o700)\n\t\tcase err != nil:\n\t\t\terr = fmt.Errorf(\"%s exists and is not a directory\", checkpointAbsDir)\n\t\t}\n\t} else {\n\t\tswitch {\n\t\tcase err != nil:\n\t\t\terr = fmt.Errorf(\"checkpoint %s does not exist for container %s\", checkpointID, containerID)\n\t\tcase stat.IsDir():\n\t\t\terr = nil\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"%s exists and is not a directory\", checkpointAbsDir)\n\t\t}\n\t}\n\treturn checkpointAbsDir, err\n}\n"
  },
  {
    "path": "pkg/cioutil/container_io.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage cioutil\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/containerd/containerd/v2/cmd/containerd-shim-runc-v2/process\"\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n)\n\nconst binaryIOProcTermTimeout = 12 * time.Second // Give logger process 10 seconds for cleanup\n\n// ncio is a basic container IO implementation.\ntype ncio struct {\n\tcmd     *exec.Cmd\n\tconfig  cio.Config\n\twg      *sync.WaitGroup\n\tclosers []io.Closer\n\tcancel  context.CancelFunc\n}\n\nvar bufPool = sync.Pool{\n\tNew: func() interface{} {\n\t\tbuffer := make([]byte, 32<<10)\n\t\treturn &buffer\n\t},\n}\n\nfunc (c *ncio) Config() cio.Config {\n\treturn c.config\n}\n\nfunc (c *ncio) Wait() {\n\tif c.wg != nil {\n\t\tc.wg.Wait()\n\t}\n}\n\nfunc (c *ncio) Close() error {\n\n\tvar lastErr error\n\n\tif c.cmd != nil && c.cmd.Process != nil {\n\n\t\t// Send SIGTERM first, so logger process has a chance to flush and exit properly\n\t\tif err := c.cmd.Process.Signal(syscall.SIGTERM); err != nil {\n\t\t\tlastErr = fmt.Errorf(\"failed to send SIGTERM: %w\", err)\n\n\t\t\tif err := c.cmd.Process.Kill(); err != nil {\n\t\t\t\tlastErr = errors.Join(lastErr, fmt.Errorf(\"failed to kill process after faulty SIGTERM: %w\", err))\n\t\t\t}\n\n\t\t}\n\n\t\tdone := make(chan error, 1)\n\t\tgo func() {\n\t\t\tdone <- c.cmd.Wait()\n\t\t}()\n\n\t\tselect {\n\t\tcase err := <-done:\n\t\t\tif err != nil {\n\t\t\t\tlastErr = fmt.Errorf(\"faied to run cmd.wait: %w\", err)\n\t\t\t}\n\t\tcase <-time.After(binaryIOProcTermTimeout):\n\n\t\t\terr := c.cmd.Process.Kill()\n\t\t\tif err != nil {\n\t\t\t\tlastErr = fmt.Errorf(\"failed to kill shim logger process: %w\", err)\n\t\t\t}\n\n\t\t}\n\t}\n\n\tfor _, closer := range c.closers {\n\t\tif closer == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif err := closer.Close(); err != nil {\n\t\t\tlastErr = err\n\t\t}\n\t}\n\treturn lastErr\n}\n\nfunc (c *ncio) Cancel() {\n\tif c.cancel != nil {\n\t\tc.cancel()\n\t}\n}\n\nfunc NewContainerIO(namespace string, logURI string, tty bool, stdin io.Reader, stdout, stderr io.Writer) cio.Creator {\n\treturn func(id string) (_ cio.IO, err error) {\n\t\tvar (\n\t\t\tcmd     *exec.Cmd\n\t\t\tclosers []func() error\n\t\t\tstreams = &cio.Streams{\n\t\t\t\tTerminal: tty,\n\t\t\t}\n\t\t)\n\n\t\tdefer func() {\n\t\t\tif err == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresult := []error{err}\n\t\t\tfor _, fn := range closers {\n\t\t\t\tresult = append(result, fn())\n\t\t\t}\n\t\t\terr = errors.Join(result...)\n\t\t}()\n\n\t\tif stdin != nil {\n\t\t\tstreams.Stdin = stdin\n\t\t}\n\n\t\tvar stdoutWriters []io.Writer\n\t\tif stdout != nil {\n\t\t\tstdoutWriters = append(stdoutWriters, stdout)\n\t\t}\n\n\t\tvar stderrWriters []io.Writer\n\t\tif stderr != nil {\n\t\t\tstderrWriters = append(stderrWriters, stderr)\n\t\t}\n\n\t\tif runtime.GOOS != \"windows\" && logURI != \"\" && logURI != \"none\" {\n\t\t\t// starting logging binary logic is from https://github.com/containerd/containerd/blob/194a1fdd2cde35bc019ef138f30485e27fe0913e/cmd/containerd-shim-runc-v2/process/io.go#L247\n\t\t\tstdoutr, stdoutw, err := os.Pipe()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tclosers = append(closers, stdoutr.Close, stdoutw.Close)\n\n\t\t\tstderrr, stderrw, err := os.Pipe()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tclosers = append(closers, stderrr.Close, stderrw.Close)\n\n\t\t\tr, w, err := os.Pipe()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tclosers = append(closers, r.Close, w.Close)\n\n\t\t\tu, err := url.Parse(logURI)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcmd = process.NewBinaryCmd(u, id, namespace)\n\t\t\tcmd.ExtraFiles = append(cmd.ExtraFiles, stdoutr, stderrr, w)\n\n\t\t\tif err := cmd.Start(); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to start binary process with cmdArgs %v (logURI: %s): %w\", cmd.Args, logURI, err)\n\t\t\t}\n\n\t\t\tclosers = append(closers, func() error { return cmd.Process.Kill() })\n\n\t\t\t// close our side of the pipe after start\n\t\t\tif err := w.Close(); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to close write pipe after start: %w\", err)\n\t\t\t}\n\n\t\t\t// wait for the logging binary to be ready\n\t\t\tb := make([]byte, 1)\n\t\t\tif _, err := r.Read(b); err != nil && err != io.EOF {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to read from logging binary: %w\", err)\n\t\t\t}\n\n\t\t\tstdoutWriters = append(stdoutWriters, stdoutw)\n\t\t\tstderrWriters = append(stderrWriters, stderrw)\n\t\t}\n\n\t\tstreams.Stdout = io.MultiWriter(stdoutWriters...)\n\t\tstreams.Stderr = io.MultiWriter(stderrWriters...)\n\n\t\tif streams.FIFODir == \"\" {\n\t\t\tstreams.FIFODir = defaults.DefaultFIFODir\n\t\t}\n\t\tfifos, err := cio.NewFIFOSetInDir(streams.FIFODir, id, streams.Terminal)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif streams.Stdin == nil {\n\t\t\tfifos.Stdin = \"\"\n\t\t}\n\t\tif streams.Stdout == nil {\n\t\t\tfifos.Stdout = \"\"\n\t\t}\n\t\tif streams.Stderr == nil {\n\t\t\tfifos.Stderr = \"\"\n\t\t}\n\t\treturn copyIO(cmd, fifos, streams)\n\t}\n}\n"
  },
  {
    "path": "pkg/cioutil/container_io_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage cioutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/fifo\"\n)\n\ntype pipes struct {\n\tStdin  io.WriteCloser\n\tStdout io.ReadCloser\n\tStderr io.ReadCloser\n}\n\nfunc (p *pipes) closers() []io.Closer {\n\treturn []io.Closer{p.Stdin, p.Stdout, p.Stderr}\n}\n\n// copyIO is from https://github.com/containerd/containerd/blob/148d21b1ae0718b75718a09ecb307bb874270f59/cio/io_unix.go#L55\nfunc copyIO(cmd *exec.Cmd, fifos *cio.FIFOSet, ioset *cio.Streams) (*ncio, error) {\n\tvar ctx, cancel = context.WithCancel(context.Background())\n\tpipes, err := openFifos(ctx, fifos)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\n\tif fifos.Stdin != \"\" {\n\t\tgo func() {\n\t\t\tp := bufPool.Get().(*[]byte)\n\t\t\tdefer bufPool.Put(p)\n\n\t\t\tio.CopyBuffer(pipes.Stdin, ioset.Stdin, *p)\n\t\t\tpipes.Stdin.Close()\n\t\t}()\n\t}\n\n\tvar wg = &sync.WaitGroup{}\n\tif fifos.Stdout != \"\" {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tp := bufPool.Get().(*[]byte)\n\t\t\tdefer bufPool.Put(p)\n\n\t\t\tio.CopyBuffer(ioset.Stdout, pipes.Stdout, *p)\n\t\t\tpipes.Stdout.Close()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\tif !fifos.Terminal && fifos.Stderr != \"\" {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tp := bufPool.Get().(*[]byte)\n\t\t\tdefer bufPool.Put(p)\n\n\t\t\tio.CopyBuffer(ioset.Stderr, pipes.Stderr, *p)\n\t\t\tpipes.Stderr.Close()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\treturn &ncio{\n\t\tcmd:     cmd,\n\t\tconfig:  fifos.Config,\n\t\twg:      wg,\n\t\tclosers: append(pipes.closers(), fifos),\n\t\tcancel: func() {\n\t\t\tcancel()\n\t\t\tfor _, c := range pipes.closers() {\n\t\t\t\tif c != nil {\n\t\t\t\t\tc.Close()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}, nil\n}\n\nfunc openFifos(ctx context.Context, fifos *cio.FIFOSet) (f pipes, retErr error) {\n\tdefer func() {\n\t\tif retErr != nil {\n\t\t\tfifos.Close()\n\t\t}\n\t}()\n\n\tif fifos.Stdin != \"\" {\n\t\tif f.Stdin, retErr = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {\n\t\t\treturn f, fmt.Errorf(\"failed to open stdin fifo: %w\", retErr)\n\t\t}\n\t\tdefer func() {\n\t\t\tif retErr != nil && f.Stdin != nil {\n\t\t\t\tf.Stdin.Close()\n\t\t\t}\n\t\t}()\n\t}\n\tif fifos.Stdout != \"\" {\n\t\tif f.Stdout, retErr = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {\n\t\t\treturn f, fmt.Errorf(\"failed to open stdout fifo: %w\", retErr)\n\t\t}\n\t\tdefer func() {\n\t\t\tif retErr != nil && f.Stdout != nil {\n\t\t\t\tf.Stdout.Close()\n\t\t\t}\n\t\t}()\n\t}\n\tif !fifos.Terminal && fifos.Stderr != \"\" {\n\t\tif f.Stderr, retErr = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {\n\t\t\treturn f, fmt.Errorf(\"failed to open stderr fifo: %w\", retErr)\n\t\t}\n\t}\n\treturn f, nil\n}\n"
  },
  {
    "path": "pkg/cioutil/container_io_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage cioutil\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\n\t\"github.com/Microsoft/go-winio\"\n\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/log\"\n)\n\n// copyIO is from https://github.com/containerd/containerd/blob/148d21b1ae0718b75718a09ecb307bb874270f59/cio/io_windows.go#L44\nfunc copyIO(_ *exec.Cmd, fifos *cio.FIFOSet, ioset *cio.Streams) (_ *ncio, retErr error) {\n\tncios := &ncio{cmd: nil, config: fifos.Config}\n\n\tdefer func() {\n\t\tif retErr != nil {\n\t\t\t_ = ncios.Close()\n\t\t}\n\t}()\n\n\tif fifos.Stdin != \"\" {\n\t\tl, err := winio.ListenPipe(fifos.Stdin, nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create stdin pipe %s: %w\", fifos.Stdin, err)\n\t\t}\n\t\tncios.closers = append(ncios.closers, l)\n\n\t\tgo func() {\n\t\t\tc, err := l.Accept()\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed to accept stdin connection on %s\", fifos.Stdin)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tp := bufPool.Get().(*[]byte)\n\t\t\tdefer bufPool.Put(p)\n\n\t\t\tio.CopyBuffer(c, ioset.Stdin, *p)\n\t\t\tc.Close()\n\t\t\tl.Close()\n\t\t}()\n\t}\n\n\tif fifos.Stdout != \"\" {\n\t\tl, err := winio.ListenPipe(fifos.Stdout, nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create stdout pipe %s: %w\", fifos.Stdout, err)\n\t\t}\n\t\tncios.closers = append(ncios.closers, l)\n\n\t\tgo func() {\n\t\t\tc, err := l.Accept()\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed to accept stdout connection on %s\", fifos.Stdout)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tp := bufPool.Get().(*[]byte)\n\t\t\tdefer bufPool.Put(p)\n\n\t\t\tio.CopyBuffer(ioset.Stdout, c, *p)\n\t\t\tc.Close()\n\t\t\tl.Close()\n\t\t}()\n\t}\n\n\tif fifos.Stderr != \"\" {\n\t\tl, err := winio.ListenPipe(fifos.Stderr, nil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create stderr pipe %s: %w\", fifos.Stderr, err)\n\t\t}\n\t\tncios.closers = append(ncios.closers, l)\n\n\t\tgo func() {\n\t\t\tc, err := l.Accept()\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed to accept stderr connection on %s\", fifos.Stderr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tp := bufPool.Get().(*[]byte)\n\t\t\tdefer bufPool.Put(p)\n\n\t\t\tio.CopyBuffer(ioset.Stderr, c, *p)\n\t\t\tc.Close()\n\t\t\tl.Close()\n\t\t}()\n\t}\n\n\treturn ncios, nil\n}\n"
  },
  {
    "path": "pkg/clientutil/client.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage clientutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/go-digest\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/systemutil\"\n)\n\nfunc NewClient(ctx context.Context, namespace, address string, opts ...containerd.Opt) (*containerd.Client, context.Context, context.CancelFunc, error) {\n\tctx = namespaces.WithNamespace(ctx, namespace)\n\n\taddress = strings.TrimPrefix(address, \"unix://\")\n\tconst dockerContainerdaddress = \"/var/run/docker/containerd/containerd.sock\"\n\tif err := systemutil.IsSocketAccessible(address); err != nil {\n\t\tif systemutil.IsSocketAccessible(dockerContainerdaddress) == nil {\n\t\t\terr = fmt.Errorf(\"cannot access containerd socket %q (hint: try running with `--address %s` to connect to Docker-managed containerd): %w\", address, dockerContainerdaddress, err)\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"cannot access containerd socket %q: %w\", address, err)\n\t\t}\n\t\treturn nil, nil, nil, err\n\t}\n\tclient, err := containerd.New(address, opts...)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\tvar cancel context.CancelFunc\n\tctx, cancel = context.WithCancel(ctx)\n\treturn client, ctx, cancel, nil\n}\n\nfunc NewClientWithPlatform(ctx context.Context, namespace, address, platform string, clientOpts ...containerd.Opt) (*containerd.Client, context.Context, context.CancelFunc, error) {\n\tif platform != \"\" {\n\t\tif canExec, canExecErr := platformutil.CanExecProbably(platform); !canExec {\n\t\t\twarn := fmt.Sprintf(\"Platform %q seems incompatible with the host platform %q. If you see \\\"exec format error\\\", see https://github.com/containerd/nerdctl/blob/main/docs/multi-platform.md\",\n\t\t\t\tplatform, platforms.DefaultString())\n\t\t\tif canExecErr != nil {\n\t\t\t\tlog.L.WithError(canExecErr).Warn(warn)\n\t\t\t} else {\n\t\t\t\tlog.L.Warn(warn)\n\t\t\t}\n\t\t}\n\t\tplatformParsed, err := platforms.Parse(platform)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\tplatformM := platforms.Only(platformParsed)\n\t\tclientOpts = append(clientOpts, containerd.WithDefaultPlatform(platformM))\n\t}\n\treturn NewClient(ctx, namespace, address, clientOpts...)\n}\n\n// DataStore returns a string like \"/var/lib/nerdctl/1935db59\".\n// \"1935db9\" is from `$(echo -n \"/run/containerd/containerd.sock\" | sha256sum | cut -c1-8)`\n// on Windows it will return \"%PROGRAMFILES%/nerdctl/1935db59\"\nfunc DataStore(dataRoot, address string) (string, error) {\n\tif err := os.MkdirAll(dataRoot, 0700); err != nil {\n\t\treturn \"\", err\n\t}\n\taddrHash, err := getAddrHash(address)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdataStore := filepath.Join(dataRoot, addrHash)\n\tif err := os.MkdirAll(dataStore, 0700); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn dataStore, nil\n}\n\nfunc getAddrHash(addr string) (string, error) {\n\tconst addrHashLen = 8\n\n\tif runtime.GOOS != \"windows\" {\n\t\taddr = strings.TrimPrefix(addr, \"unix://\")\n\n\t\tvar err error\n\t\taddr, err = filepath.EvalSymlinks(addr)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\td := digest.SHA256.FromString(addr)\n\th := d.Encoded()[0:addrHashLen]\n\treturn h, nil\n}\n"
  },
  {
    "path": "pkg/cmd/apparmor/inspect_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/containerd/containerd/v2/contrib/apparmor\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\nfunc Inspect(options types.ApparmorInspectOptions) error {\n\tb, err := apparmor.DumpDefaultProfile(defaults.AppArmorProfileName)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = fmt.Fprint(options.Stdout, b)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/cmd/apparmor/list_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\nfunc List(options types.ApparmorListOptions) error {\n\tquiet := options.Quiet\n\tw := options.Stdout\n\tvar tmpl *template.Template\n\tformat := options.Format\n\tswitch format {\n\tcase \"\", \"table\", \"wide\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !quiet {\n\t\t\tfmt.Fprintln(w, \"NAME\\tMODE\")\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\tif quiet {\n\t\t\treturn errors.New(\"format and quiet must not be specified together\")\n\t\t}\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tprofiles, err := apparmorutil.Profiles()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, f := range profiles {\n\t\tif tmpl != nil {\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, f); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err = fmt.Fprintln(w, b.String()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if quiet {\n\t\t\tfmt.Fprintln(w, f.Name)\n\t\t} else {\n\t\t\tfmt.Fprintf(w, \"%s\\t%s\\n\", f.Name, f.Mode)\n\t\t}\n\t}\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/apparmor/load_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"github.com/containerd/containerd/v2/contrib/apparmor\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\nfunc Load() error {\n\tlog.L.Infof(\"Loading profile %q\", defaults.AppArmorProfileName)\n\treturn apparmor.LoadDefaultProfile(defaults.AppArmorProfileName)\n}\n"
  },
  {
    "path": "pkg/cmd/apparmor/unload_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage apparmor\n\nimport (\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n)\n\nfunc Unload(target string) error {\n\tlog.L.Infof(\"Unloading profile %q\", target)\n\treturn apparmorutil.Unload(target)\n}\n"
  },
  {
    "path": "pkg/cmd/builder/build.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/images/archive\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\ntype PlatformParser interface {\n\tParse(platform string) (platforms.Platform, error)\n\tDefaultSpec() platforms.Platform\n}\n\ntype platformParser struct{}\n\nfunc (p platformParser) Parse(platform string) (platforms.Platform, error) {\n\treturn platforms.Parse(platform)\n}\n\nfunc (p platformParser) DefaultSpec() platforms.Platform {\n\treturn platforms.DefaultSpec()\n}\n\nfunc Build(ctx context.Context, client *containerd.Client, options types.BuilderBuildOptions) error {\n\tbuildctlBinary, buildctlArgs, needsLoading, metaFile, tags, cleanup, err := generateBuildctlArgs(ctx, client, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif cleanup != nil {\n\t\tdefer cleanup()\n\t}\n\n\tlog.L.Debugf(\"running %s %v\", buildctlBinary, buildctlArgs)\n\tbuildctlCmd := exec.Command(buildctlBinary, buildctlArgs...)\n\tbuildctlCmd.Env = os.Environ()\n\n\tvar buildctlStdout io.Reader\n\tif needsLoading {\n\t\tbuildctlStdout, err = buildctlCmd.StdoutPipe()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tbuildctlCmd.Stdout = options.Stdout\n\t}\n\tif !options.Quiet {\n\t\tbuildctlCmd.Stderr = options.Stderr\n\t}\n\n\tif err := buildctlCmd.Start(); err != nil {\n\t\treturn err\n\t}\n\n\tif needsLoading {\n\t\tplatMC, err := platformutil.NewMatchComparer(false, options.Platform)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = loadImage(ctx, buildctlStdout, options.GOptions.Namespace, options.GOptions.Address, options.GOptions.Snapshotter, options.Stdout, platMC, options.Quiet); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err = buildctlCmd.Wait(); err != nil {\n\t\treturn err\n\t}\n\n\tif options.IidFile != \"\" {\n\t\tid, err := getDigestFromMetaFile(metaFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := filesystem.WriteFile(options.IidFile, []byte(id), 0644); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif len(tags) > 1 {\n\t\tlog.L.Debug(\"Found more than 1 tag\")\n\t\timageService := client.ImageService()\n\t\timage, err := imageService.Get(ctx, tags[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to tag image: %w\", err)\n\t\t}\n\t\tfor _, targetRef := range tags[1:] {\n\t\t\timage.Name = targetRef\n\t\t\tif _, err := imageService.Create(ctx, image); err != nil {\n\t\t\t\t// if already exists; skip.\n\t\t\t\tif errors.Is(err, errdefs.ErrAlreadyExists) {\n\t\t\t\t\tif err = imageService.Delete(ctx, targetRef, images.SynchronousDelete()); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif _, err = imageService.Create(ctx, image); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"unable to tag image: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// TODO: This struct and `loadImage` are duplicated with the code in `cmd/load.go`, remove it after `load.go` has been refactor\ntype readCounter struct {\n\tio.Reader\n\tN int\n}\n\nfunc loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotter string, output io.Writer, platMC platforms.MatchComparer, quiet bool) error {\n\t// In addition to passing WithImagePlatform() to client.Import(), we also need to pass WithDefaultPlatform() to NewClient().\n\t// Otherwise unpacking may fail.\n\tclient, ctx, cancel, err := clientutil.NewClient(ctx, namespace, address, containerd.WithDefaultPlatform(platMC))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tcancel()\n\t\tclient.Close()\n\t}()\n\tr := &readCounter{Reader: in}\n\timgs, err := client.Import(ctx, r, containerd.WithDigestRef(archive.DigestTranslator(snapshotter)), containerd.WithSkipDigestRef(func(name string) bool { return name != \"\" }), containerd.WithImportPlatform(platMC))\n\tif err != nil {\n\t\tif r.N == 0 {\n\t\t\t// Avoid confusing \"unrecognized image format\"\n\t\t\treturn errors.New(\"no image was built\")\n\t\t}\n\t\tif errors.Is(err, images.ErrEmptyWalk) {\n\t\t\terr = fmt.Errorf(\"%w (Hint: set `--platform=PLATFORM` or `--all-platforms`)\", err)\n\t\t}\n\t\treturn err\n\t}\n\tfor _, img := range imgs {\n\t\timage := containerd.NewImageWithPlatform(client, img, platMC)\n\n\t\t// TODO: Show unpack status\n\t\tif !quiet {\n\t\t\tfmt.Fprintf(output, \"unpacking %s (%s)...\\n\", img.Name, img.Target.Digest)\n\t\t}\n\t\terr = image.Unpack(ctx, snapshotter)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif quiet {\n\t\t\tfmt.Fprintln(output, img.Target.Digest)\n\t\t} else {\n\t\t\tfmt.Fprintf(output, \"Loaded image: %s\\n\", img.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// GetEffectiveSourcePolicyFile returns the effective source policy file path.\n// If optionValue is set, it takes precedence. Otherwise, the EXPERIMENTAL_BUILDKIT_SOURCE_POLICY\n// environment variable is used for Docker Buildx compatibility.\nfunc GetEffectiveSourcePolicyFile(optionValue string) string {\n\tif optionValue != \"\" {\n\t\treturn optionValue\n\t}\n\treturn os.Getenv(\"EXPERIMENTAL_BUILDKIT_SOURCE_POLICY\")\n}\n\nfunc generateBuildctlArgs(ctx context.Context, client *containerd.Client, options types.BuilderBuildOptions) (buildCtlBinary string,\n\tbuildctlArgs []string, needsLoading bool, metaFile string, tags []string, cleanup func(), err error) {\n\n\tbuildctlBinary, err := buildkitutil.BuildctlBinary()\n\tif err != nil {\n\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t}\n\n\toutput := options.Output\n\tif output == \"\" {\n\t\tinfo, err := client.Server(ctx)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t}\n\t\tsharable, err := isImageSharable(options.BuildKitHost, options.GOptions.Namespace, info.UUID, options.GOptions.Snapshotter, options.Platform)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t}\n\t\tif sharable {\n\t\t\toutput = \"type=image,unpack=true\" // ensure the target stage is unlazied (needed for any snapshotters)\n\t\t} else {\n\t\t\toutput = \"type=docker\"\n\t\t\tif len(options.Platform) > 1 {\n\t\t\t\t// For avoiding `error: failed to solve: docker exporter does not currently support exporting manifest lists`\n\t\t\t\t// TODO: consider using type=oci for single-options.Platform build too\n\t\t\t\toutput = \"type=oci\"\n\t\t\t}\n\t\t\tneedsLoading = true\n\t\t}\n\t} else {\n\t\tif !strings.Contains(output, \"type=\") {\n\t\t\t// should accept --output <DIR> as an alias of --output\n\t\t\t// type=local,dest=<DIR>\n\t\t\toutput = fmt.Sprintf(\"type=local,dest=%s\", output)\n\t\t}\n\t\tif strings.Contains(output, \"type=docker\") || strings.Contains(output, \"type=oci\") {\n\t\t\tif !strings.Contains(output, \"dest=\") {\n\t\t\t\tneedsLoading = true\n\t\t\t}\n\t\t}\n\t}\n\tif tags = strutil.DedupeStrSlice(options.Tag); len(tags) > 0 {\n\t\tref := tags[0]\n\t\tparsedReference, err := referenceutil.Parse(ref)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t}\n\t\toutput += \",name=\" + parsedReference.String()\n\n\t\t// pick the first tag and add it to output\n\t\tfor idx, tag := range tags {\n\t\t\tparsedReference, err = referenceutil.Parse(tag)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t\t}\n\t\t\ttags[idx] = parsedReference.String()\n\t\t}\n\t} else if len(tags) == 0 {\n\t\toutput = output + \",dangling-name-prefix=<none>\"\n\t}\n\n\tbuildctlArgs = buildkitutil.BuildctlBaseArgs(options.BuildKitHost)\n\n\tbuildctlArgs = append(buildctlArgs, []string{\n\t\t\"build\",\n\t\t\"--progress=\" + options.Progress,\n\t\t\"--frontend=dockerfile.v0\",\n\t\t\"--local=context=\" + options.BuildContext,\n\t\t\"--output=\" + output,\n\t}...)\n\n\tdir := options.BuildContext\n\tfile := buildkitutil.DefaultDockerfileName\n\tif options.File != \"\" {\n\t\tif options.File == \"-\" {\n\t\t\t// Super Warning: this is a special trick to update the dir variable, Don't move this line!!!!!!\n\t\t\tvar err error\n\t\t\tdir, err = buildkitutil.WriteTempDockerfile(options.Stdin)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t\t}\n\t\t\tcleanup = func() {\n\t\t\t\tos.RemoveAll(dir)\n\t\t\t}\n\t\t} else {\n\t\t\tdir, file = filepath.Split(options.File)\n\t\t}\n\n\t\tif dir == \"\" {\n\t\t\tdir = \".\"\n\t\t}\n\t}\n\tdir, file, err = buildkitutil.BuildKitFile(dir, file)\n\tif err != nil {\n\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t}\n\n\tbuildCtx, err := parseContextNames(options.ExtendedBuildContext)\n\tif err != nil {\n\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t}\n\n\tfor k, v := range buildCtx {\n\t\tisURL := strings.HasPrefix(v, \"https://\") || strings.HasPrefix(v, \"http://\")\n\t\tisDockerImage := strings.HasPrefix(v, \"docker-image://\") || strings.HasPrefix(v, \"target:\")\n\n\t\tif isURL || isDockerImage {\n\t\t\tbuildctlArgs = append(buildctlArgs, fmt.Sprintf(\"--opt=context:%s=%s\", k, v))\n\t\t\tcontinue\n\t\t}\n\n\t\tif isOCILayout := strings.HasPrefix(v, \"oci-layout://\"); isOCILayout {\n\t\t\targs, err := parseBuildContextFromOCILayout(k, v)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t\t}\n\n\t\t\tbuildctlArgs = append(buildctlArgs, args...)\n\t\t\tcontinue\n\t\t}\n\n\t\tpath, err := filepath.Abs(v)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t}\n\t\tbuildctlArgs = append(buildctlArgs, fmt.Sprintf(\"--local=%s=%s\", k, path))\n\t\tbuildctlArgs = append(buildctlArgs, fmt.Sprintf(\"--opt=context:%s=local:%s\", k, k))\n\t}\n\n\tbuildctlArgs = append(buildctlArgs, \"--local=dockerfile=\"+dir)\n\tbuildctlArgs = append(buildctlArgs, \"--opt=filename=\"+file)\n\n\tif options.Target != \"\" {\n\t\tbuildctlArgs = append(buildctlArgs, \"--opt=target=\"+options.Target)\n\t}\n\n\tif len(options.Platform) > 0 {\n\t\tbuildctlArgs = append(buildctlArgs, \"--opt=platform=\"+strings.Join(options.Platform, \",\"))\n\t}\n\n\tseenBuildArgs := make(map[string]struct{})\n\tfor _, ba := range strutil.DedupeStrSlice(options.BuildArgs) {\n\t\tarr := strings.Split(ba, \"=\")\n\t\tseenBuildArgs[arr[0]] = struct{}{}\n\t\tif len(arr) == 1 && len(arr[0]) > 0 {\n\t\t\t// Avoid masking default build arg value from Dockerfile if environment variable is not set\n\t\t\t// https://github.com/moby/moby/issues/24101\n\t\t\tval, ok := os.LookupEnv(arr[0])\n\t\t\tif ok {\n\t\t\t\tbuildctlArgs = append(buildctlArgs, fmt.Sprintf(\"--opt=build-arg:%s=%s\", ba, val))\n\t\t\t} else {\n\t\t\t\tlog.L.Debugf(\"ignoring unset build arg %q\", ba)\n\t\t\t}\n\t\t} else if len(arr) > 1 && len(arr[0]) > 0 {\n\t\t\tbuildctlArgs = append(buildctlArgs, \"--opt=build-arg:\"+ba)\n\n\t\t\t// Support `--build-arg BUILDKIT_INLINE_CACHE=1` for compatibility with `docker buildx build`\n\t\t\t// https://github.com/docker/buildx/blob/v0.6.3/docs/reference/buildx_build.md#-export-build-cache-to-an-external-cache-destination---cache-to\n\t\t\tif strings.HasPrefix(ba, \"BUILDKIT_INLINE_CACHE=\") {\n\t\t\t\tbic := strings.TrimPrefix(ba, \"BUILDKIT_INLINE_CACHE=\")\n\t\t\t\tbicParsed, err := strconv.ParseBool(bic)\n\t\t\t\tif err == nil {\n\t\t\t\t\tif bicParsed {\n\t\t\t\t\t\tbuildctlArgs = append(buildctlArgs, \"--export-cache=type=inline\")\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlog.L.WithError(err).Warnf(\"invalid BUILDKIT_INLINE_CACHE: %q\", bic)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, fmt.Errorf(\"invalid build arg %q\", ba)\n\t\t}\n\t}\n\n\t// Propagate SOURCE_DATE_EPOCH from the client env\n\t// https://github.com/docker/buildx/pull/1482\n\tif v := os.Getenv(\"SOURCE_DATE_EPOCH\"); v != \"\" {\n\t\tif _, ok := seenBuildArgs[\"SOURCE_DATE_EPOCH\"]; !ok {\n\t\t\tbuildctlArgs = append(buildctlArgs, \"--opt=build-arg:SOURCE_DATE_EPOCH=\"+v)\n\t\t}\n\t}\n\n\tfor _, l := range strutil.DedupeStrSlice(options.Label) {\n\t\tbuildctlArgs = append(buildctlArgs, \"--opt=label:\"+l)\n\t}\n\n\tif options.NoCache {\n\t\tbuildctlArgs = append(buildctlArgs, \"--no-cache\")\n\t}\n\n\tif options.Pull != nil {\n\t\tswitch *options.Pull {\n\t\tcase true:\n\t\t\tbuildctlArgs = append(buildctlArgs, \"--opt=image-resolve-mode=pull\")\n\t\tcase false:\n\t\t\tbuildctlArgs = append(buildctlArgs, \"--opt=image-resolve-mode=local\")\n\t\t}\n\t}\n\n\tfor _, s := range strutil.DedupeStrSlice(options.Secret) {\n\t\tbuildctlArgs = append(buildctlArgs, \"--secret=\"+s)\n\t}\n\n\tfor _, s := range strutil.DedupeStrSlice(options.Allow) {\n\t\tbuildctlArgs = append(buildctlArgs, \"--allow=\"+s)\n\t}\n\n\tfor _, s := range strutil.DedupeStrSlice(options.Attest) {\n\t\toptAttestType, optAttestAttrs, _ := strings.Cut(s, \",\")\n\t\tif strings.HasPrefix(optAttestType, \"type=\") {\n\t\t\tif strings.HasPrefix(optAttestAttrs, \"disabled=\") {\n\t\t\t\tdisabled, err := strconv.ParseBool(strings.TrimPrefix(optAttestAttrs, \"disabled=\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", nil, false, \"\", nil, nil, fmt.Errorf(\"invalid value for attribute \\\"disabled\\\"\")\n\t\t\t\t}\n\t\t\t\tif disabled {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\toptAttestType := strings.TrimPrefix(optAttestType, \"type=\")\n\t\t\tbuildctlArgs = append(buildctlArgs, fmt.Sprintf(\"--opt=attest:%s=%s\", optAttestType, optAttestAttrs))\n\t\t} else {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, fmt.Errorf(\"attestation type not specified\")\n\t\t}\n\t}\n\n\tfor _, s := range strutil.DedupeStrSlice(options.SSH) {\n\t\tbuildctlArgs = append(buildctlArgs, \"--ssh=\"+s)\n\t}\n\n\tfor _, s := range strutil.DedupeStrSlice(options.CacheFrom) {\n\t\tif !strings.Contains(s, \"type=\") {\n\t\t\ts = \"type=registry,ref=\" + s\n\t\t}\n\t\tbuildctlArgs = append(buildctlArgs, \"--import-cache=\"+s)\n\t}\n\n\tfor _, s := range strutil.DedupeStrSlice(options.CacheTo) {\n\t\tif !strings.Contains(s, \"type=\") {\n\t\t\ts = \"type=registry,ref=\" + s\n\t\t}\n\t\tbuildctlArgs = append(buildctlArgs, \"--export-cache=\"+s)\n\t}\n\n\tif !options.Rm {\n\t\tlog.L.Warn(\"ignoring deprecated flag: '--rm=false'\")\n\t}\n\n\tif options.IidFile != \"\" {\n\t\tfile, err := os.CreateTemp(\"\", \"buildkit-meta-*\")\n\t\tif err != nil {\n\t\t\treturn \"\", nil, false, \"\", nil, cleanup, err\n\t\t}\n\t\tdefer file.Close()\n\t\tmetaFile = file.Name()\n\t\tbuildctlArgs = append(buildctlArgs, \"--metadata-file=\"+metaFile)\n\t}\n\n\tif options.NetworkMode != \"\" {\n\t\tswitch options.NetworkMode {\n\t\tcase \"none\":\n\t\t\tbuildctlArgs = append(buildctlArgs, \"--opt=force-network-mode=\"+options.NetworkMode)\n\t\tcase \"host\":\n\t\t\tbuildctlArgs = append(buildctlArgs, \"--opt=force-network-mode=\"+options.NetworkMode, \"--allow=network.host\", \"--allow=security.insecure\")\n\t\tcase \"\", \"default\":\n\t\tdefault:\n\t\t\tlog.L.Debugf(\"ignoring network build arg %s\", options.NetworkMode)\n\t\t}\n\t}\n\n\tif len(options.ExtraHosts) > 0 {\n\t\textraHosts, err := containerutil.ParseExtraHosts(options.ExtraHosts, options.GOptions.HostGatewayIP, \"=\")\n\t\tif err != nil {\n\t\t\treturn \"\", nil, false, \"\", nil, nil, err\n\t\t}\n\t\tbuildctlArgs = append(buildctlArgs, \"--opt=add-hosts=\"+strings.Join(extraHosts, \",\"))\n\t}\n\n\t// Source policy file: use explicit option if set, otherwise fallback to env var for Buildx compatibility\n\tif sourcePolicyFile := GetEffectiveSourcePolicyFile(options.SourcePolicyFile); sourcePolicyFile != \"\" {\n\t\tbuildctlArgs = append(buildctlArgs, \"--source-policy-file=\"+sourcePolicyFile)\n\t}\n\n\treturn buildctlBinary, buildctlArgs, needsLoading, metaFile, tags, cleanup, nil\n}\n\nfunc getDigestFromMetaFile(path string) (string, error) {\n\tdata, err := filesystem.ReadFile(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer os.Remove(path)\n\n\tmetadata := map[string]json.RawMessage{}\n\tif err := json.Unmarshal(data, &metadata); err != nil {\n\t\tlog.L.WithError(err).Errorf(\"failed to unmarshal metadata file %s\", path)\n\t\treturn \"\", err\n\t}\n\tdigestRaw, ok := metadata[\"containerimage.digest\"]\n\tif !ok {\n\t\treturn \"\", errors.New(\"failed to find containerimage.digest in metadata file\")\n\t}\n\tvar digest string\n\tif err := json.Unmarshal(digestRaw, &digest); err != nil {\n\t\tlog.L.WithError(err).Errorf(\"failed to unmarshal digset\")\n\t\treturn \"\", err\n\t}\n\treturn digest, nil\n}\n\nfunc isMatchingRuntimePlatform(platform string, parser PlatformParser) bool {\n\tp, err := parser.Parse(platform)\n\tif err != nil {\n\t\treturn false\n\t}\n\td := parser.DefaultSpec()\n\n\tif p.OS == d.OS && p.Architecture == d.Architecture && (p.Variant == \"\" || p.Variant == d.Variant) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc isBuildPlatformDefault(platform []string, parser PlatformParser) bool {\n\tif len(platform) == 0 {\n\t\treturn true\n\t} else if len(platform) == 1 {\n\t\treturn isMatchingRuntimePlatform(platform[0], parser)\n\t}\n\treturn false\n}\n\nfunc isImageSharable(buildkitHost, namespace, uuid, snapshotter string, platform []string) (bool, error) {\n\tlabels, err := buildkitutil.GetWorkerLabels(buildkitHost)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tlog.L.Debugf(\"worker labels: %+v\", labels)\n\texecutor, ok := labels[\"org.mobyproject.buildkit.worker.executor\"]\n\tif !ok {\n\t\treturn false, nil\n\t}\n\tcontainerdUUID, ok := labels[\"org.mobyproject.buildkit.worker.containerd.uuid\"]\n\tif !ok {\n\t\treturn false, nil\n\t}\n\tcontainerdNamespace, ok := labels[\"org.mobyproject.buildkit.worker.containerd.namespace\"]\n\tif !ok {\n\t\treturn false, nil\n\t}\n\tworkerSnapshotter, ok := labels[\"org.mobyproject.buildkit.worker.snapshotter\"]\n\tif !ok {\n\t\treturn false, nil\n\t}\n\t// NOTE: It's possible that BuildKit doesn't download the base image of non-default platform (e.g. when the provided\n\t//       Dockerfile doesn't contain instructions require base images like RUN) even if `--output type=image,unpack=true`\n\t//       is passed to BuildKit. Thus, we need to use `type=docker` or `type=oci` when nerdctl builds non-default platform\n\t//       image using `platform` option.\n\tparser := new(platformParser)\n\treturn executor == \"containerd\" && containerdUUID == uuid && containerdNamespace == namespace && workerSnapshotter == snapshotter && isBuildPlatformDefault(platform, parser), nil\n}\n\nfunc parseContextNames(values []string) (map[string]string, error) {\n\tif len(values) == 0 {\n\t\treturn nil, nil\n\t}\n\tresult := make(map[string]string, len(values))\n\tfor _, value := range values {\n\t\tkv := strings.SplitN(value, \"=\", 2)\n\t\tif len(kv) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid context value: %s, expected key=value\", value)\n\t\t}\n\t\tresult[kv[0]] = kv[1]\n\t}\n\treturn result, nil\n}\n\nvar (\n\tErrOCILayoutPrefixNotFound = errors.New(\"OCI layout prefix not found\")\n\tErrOCILayoutEmptyDigest    = errors.New(\"OCI layout cannot have empty digest\")\n)\n\nfunc parseBuildContextFromOCILayout(name, path string) ([]string, error) {\n\tpath, found := strings.CutPrefix(path, \"oci-layout://\")\n\tif !found {\n\t\treturn []string{}, ErrOCILayoutPrefixNotFound\n\t}\n\n\tabspath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\n\tociIndex, err := readOCIIndexFromPath(abspath)\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\n\tvar digest string\n\tfor _, manifest := range ociIndex.Manifests {\n\t\tif images.IsManifestType(manifest.MediaType) {\n\t\t\tdigest = manifest.Digest.String()\n\t\t}\n\t}\n\n\tif digest == \"\" {\n\t\treturn []string{}, ErrOCILayoutEmptyDigest\n\t}\n\n\treturn []string{\n\t\tfmt.Sprintf(\"--oci-layout=parent-image-key=%s\", abspath),\n\t\tfmt.Sprintf(\"--opt=context:%s=oci-layout:parent-image-key@%s\", name, digest),\n\t}, nil\n}\n\nfunc readOCIIndexFromPath(path string) (*ocispec.Index, error) {\n\tociIndexJSONFile, err := os.Open(filepath.Join(path, \"index.json\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer ociIndexJSONFile.Close()\n\n\trawBytes, err := io.ReadAll(ociIndexJSONFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar ociIndex *ocispec.Index\n\terr = json.Unmarshal(rawBytes, &ociIndex)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ociIndex, nil\n}\n"
  },
  {
    "path": "pkg/cmd/builder/build_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\n\tspecs \"github.com/opencontainers/image-spec/specs-go/v1\"\n\t\"go.uber.org/mock/gomock\"\n\t\"gotest.tools/v3/assert\"\n)\n\ntype MockParse struct {\n\tctrl     *gomock.Controller\n\trecorder *MockParseRecorder\n}\n\ntype MockParseRecorder struct {\n\tmock *MockParse\n}\n\nfunc newMockParser(ctrl *gomock.Controller) *MockParse {\n\tmock := &MockParse{ctrl: ctrl}\n\tmock.recorder = &MockParseRecorder{mock}\n\treturn mock\n}\n\nfunc (m *MockParse) EXPECT() *MockParseRecorder {\n\treturn m.recorder\n}\n\nfunc (m *MockParse) Parse(platform string) (specs.Platform, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"Parse\")\n\tret0, _ := ret[0].(specs.Platform)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\nfunc (m *MockParseRecorder) Parse(platform string) *gomock.Call {\n\tm.mock.ctrl.T.Helper()\n\treturn m.mock.ctrl.RecordCallWithMethodType(m.mock, \"Parse\", reflect.TypeOf((*MockParse)(nil).Parse))\n}\n\nfunc (m *MockParse) DefaultSpec() specs.Platform {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"DefaultSpec\")\n\tret0, _ := ret[0].(specs.Platform)\n\treturn ret0\n}\n\nfunc (m *MockParseRecorder) DefaultSpec() *gomock.Call {\n\tm.mock.ctrl.T.Helper()\n\treturn m.mock.ctrl.RecordCallWithMethodType(m.mock, \"DefaultSpec\", reflect.TypeOf((*MockParse)(nil).DefaultSpec))\n}\n\nfunc TestIsMatchingRuntimePlatform(t *testing.T) {\n\tt.Parallel()\n\n\ttestCases := []struct {\n\t\tname string\n\t\tmock func(*MockParse)\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"Image is shareable when Runtime and build platform match for os, arch and variant\",\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Image is shareable when Runtime and build platform match for os, arch. Variant is not defined\",\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Image is not shareable when Runtime and build platform donot math OS\",\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"OS\", Architecture: \"mockArch\", Variant: \"\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Image is not shareable when Runtime and build platform donot math Arch\",\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"mockOS\", Architecture: \"Arch\", Variant: \"\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Image is not shareable when Runtime and build platform donot math Variant\",\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"Variant\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tmockParser := newMockParser(ctrl)\n\t\t\ttc.mock(mockParser)\n\t\t\tr := isMatchingRuntimePlatform(\"test\", mockParser)\n\t\t\tassert.Equal(t, r, tc.want, tc.name)\n\t\t})\n\t}\n}\n\nfunc TestIsBuildPlatformDefault(t *testing.T) {\n\tt.Parallel()\n\n\ttestCases := []struct {\n\t\tname     string\n\t\tmock     func(*MockParse)\n\t\tplatform []string\n\t\twant     bool\n\t}{\n\t\t{\n\t\t\tname:     \"Image is shreable when len of platform is 0\",\n\t\t\tplatform: make([]string, 0),\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Image is shareable when Runtime and build platform match for os, arch and variant\",\n\t\t\tplatform: []string{\"test\"},\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Image is not shareable when Runtime build platform dont match\",\n\t\t\tplatform: []string{\"test\"},\n\t\t\tmock: func(mockParser *MockParse) {\n\t\t\t\tmockParser.EXPECT().Parse(\"test\").Return(specs.Platform{OS: \"OS\", Architecture: \"mockArch\", Variant: \"mockVariant\"}, nil)\n\t\t\t\tmockParser.EXPECT().DefaultSpec().Return(specs.Platform{OS: \"mockOS\", Architecture: \"mockArch\", Variant: \"mockVariant\"})\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Image is not shareable when more than 2 platforms are passed\",\n\t\t\tplatform: []string{\"test1\", \"test2\"},\n\t\t\twant:     false,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tctrl := gomock.NewController(t)\n\t\t\tmockParser := newMockParser(ctrl)\n\t\t\tif len(tc.platform) == 1 {\n\t\t\t\ttc.mock(mockParser)\n\t\t\t}\n\t\t\tr := isBuildPlatformDefault(tc.platform, mockParser)\n\t\t\tassert.Equal(t, r, tc.want, tc.name)\n\t\t})\n\t}\n}\n\nfunc TestParseBuildctlArgsForOCILayout(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tociLayoutName string\n\t\tociLayoutPath string\n\t\texpectedArgs  []string\n\t\terrorIsNil    bool\n\t\texpectedErr   string\n\t}{\n\t\t{\n\t\t\tname:          \"PrefixNotFoundError\",\n\t\t\tociLayoutName: \"unit-test\",\n\t\t\tociLayoutPath: \"/tmp/oci-layout/\",\n\t\t\texpectedArgs:  []string{},\n\t\t\texpectedErr:   ErrOCILayoutPrefixNotFound.Error(),\n\t\t},\n\t\t{\n\t\t\tname:          \"DirectoryNotFoundError\",\n\t\t\tociLayoutName: \"unit-test\",\n\t\t\tociLayoutPath: \"oci-layout:///tmp/oci-layout\",\n\t\t\texpectedArgs:  []string{},\n\t\t\texpectedErr:   \"open /tmp/oci-layout/index.json: no such file or directory\",\n\t\t},\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\tabspath, err := filepath.Abs(\"/tmp/oci-layout\")\n\t\tassert.NilError(t, err)\n\t\ttests[1].expectedErr = fmt.Sprintf(\n\t\t\t\"open %s\\\\index.json: The system cannot find the path specified.\",\n\t\t\tabspath,\n\t\t)\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\targs, err := parseBuildContextFromOCILayout(test.ociLayoutName, test.ociLayoutPath)\n\t\t\tif test.errorIsNil {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.Error(t, err, test.expectedErr)\n\t\t\t}\n\t\t\tassert.Equal(t, len(args), len(test.expectedArgs))\n\t\t\tassert.DeepEqual(t, args, test.expectedArgs)\n\t\t})\n\t}\n}\n\nfunc TestGetEffectiveSourcePolicyFile(t *testing.T) {\n\t// Cannot use t.Parallel() since subtests modify environment variables\n\n\ttests := []struct {\n\t\tname        string\n\t\toptionValue string\n\t\tenvValue    string\n\t\texpected    string\n\t}{\n\t\t{\n\t\t\tname:        \"option value takes precedence over env var\",\n\t\t\toptionValue: \"/path/from/flag.json\",\n\t\t\tenvValue:    \"/path/from/env.json\",\n\t\t\texpected:    \"/path/from/flag.json\",\n\t\t},\n\t\t{\n\t\t\tname:        \"env var is used when option is empty\",\n\t\t\toptionValue: \"\",\n\t\t\tenvValue:    \"/path/from/env.json\",\n\t\t\texpected:    \"/path/from/env.json\",\n\t\t},\n\t\t{\n\t\t\tname:        \"empty when both are unset\",\n\t\t\toptionValue: \"\",\n\t\t\tenvValue:    \"\",\n\t\t\texpected:    \"\",\n\t\t},\n\t\t{\n\t\t\tname:        \"option value used when env var is empty\",\n\t\t\toptionValue: \"/path/from/flag.json\",\n\t\t\tenvValue:    \"\",\n\t\t\texpected:    \"/path/from/flag.json\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Set up the environment variable for this test\n\t\t\tt.Setenv(\"EXPERIMENTAL_BUILDKIT_SOURCE_POLICY\", tc.envValue)\n\n\t\t\tresult := GetEffectiveSourcePolicyFile(tc.optionValue)\n\t\t\tassert.Equal(t, result, tc.expected)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/builder/prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage builder\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n)\n\n// Prune will prune all build cache.\nfunc Prune(ctx context.Context, options types.BuilderPruneOptions) ([]buildkitutil.UsageInfo, error) {\n\tbuildctlBinary, err := buildkitutil.BuildctlBinary()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbuildctlArgs := buildkitutil.BuildctlBaseArgs(options.BuildKitHost)\n\tbuildctlArgs = append(buildctlArgs, \"prune\", \"--format={{json .}}\")\n\tif options.All {\n\t\tbuildctlArgs = append(buildctlArgs, \"--all\")\n\t}\n\tbuildctlCmd := exec.Command(buildctlBinary, buildctlArgs...)\n\tlog.G(ctx).Debugf(\"running %v\", buildctlCmd.Args)\n\tbuildctlCmd.Stderr = options.Stderr\n\tstdout, err := buildctlCmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"faild to get stdout piper for %v: %w\", buildctlCmd.Args, err)\n\t}\n\tdefer stdout.Close()\n\tif err = buildctlCmd.Start(); err != nil {\n\t\treturn nil, fmt.Errorf(\"faild to start %v: %w\", buildctlCmd.Args, err)\n\t}\n\tdec := json.NewDecoder(stdout)\n\tresult := make([]buildkitutil.UsageInfo, 0)\n\tfor {\n\t\tvar v buildkitutil.UsageInfo\n\t\tif err := dec.Decode(&v); err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn nil, fmt.Errorf(\"faild to decode output from %v: %w\", buildctlCmd.Args, err)\n\t\t}\n\t\tresult = append(result, v)\n\t}\n\tif err = buildctlCmd.Wait(); err != nil {\n\t\treturn nil, fmt.Errorf(\"faild to wait for %v to complete: %w\", buildctlCmd.Args, err)\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/cmd/checkpoint/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/api/types/runc/options\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/pkg/archive\"\n\t\"github.com/containerd/containerd/v2/plugins\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/checkpointutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\nfunc Create(ctx context.Context, client *containerd.Client, containerID string, checkpointName string, options types.CheckpointCreateOptions) error {\n\tvar container containerd.Container\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple containers found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tcontainer = found.Container\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tn, err := walker.Walk(ctx, containerID)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"error creating checkpoint for container: %s, no such container\", containerID)\n\t}\n\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get info for container %q: %w\", containerID, err)\n\t}\n\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get task for container %q: %w\", containerID, err)\n\t}\n\n\timg, err := task.Checkpoint(ctx, withCheckpointOpts(info.Runtime.Name, !options.LeaveRunning))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer client.ImageService().Delete(ctx, img.Name())\n\n\tcs := client.ContentStore()\n\n\trawIndex, err := content.ReadBlob(ctx, cs, img.Target())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve checkpoint data: %w\", err)\n\t}\n\n\tvar index ocispec.Index\n\tif err := json.Unmarshal(rawIndex, &index); err != nil {\n\t\treturn fmt.Errorf(\"failed to decode checkpoint data: %w\", err)\n\t}\n\n\tvar cpDesc *ocispec.Descriptor\n\tfor _, m := range index.Manifests {\n\t\tif m.MediaType == images.MediaTypeContainerd1Checkpoint {\n\t\t\tcpDesc = &m //nolint:gosec\n\t\t\tbreak\n\t\t}\n\t}\n\tif cpDesc == nil {\n\t\treturn errors.New(\"invalid checkpoint\")\n\t}\n\n\tif options.CheckpointDir == \"\" {\n\t\toptions.CheckpointDir = filepath.Join(options.GOptions.DataRoot, \"checkpoints\")\n\t}\n\ttargetPath, err := checkpointutil.GetCheckpointDir(options.CheckpointDir, checkpointName, container.ID(), true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trat, err := cs.ReaderAt(ctx, *cpDesc)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get checkpoint reader: %w\", err)\n\t}\n\tdefer rat.Close()\n\n\t_, err = archive.Apply(ctx, targetPath, content.NewReader(rat))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read checkpoint reader: %w\", err)\n\t}\n\n\tfmt.Fprintf(options.Stdout, \"%s\\n\", checkpointName)\n\n\treturn nil\n}\n\nfunc withCheckpointOpts(rt string, exit bool) containerd.CheckpointTaskOpts {\n\treturn func(r *containerd.CheckpointTaskInfo) error {\n\n\t\tswitch rt {\n\t\tcase plugins.RuntimeRuncV2:\n\t\t\tif r.Options == nil {\n\t\t\t\tr.Options = &options.CheckpointOptions{}\n\t\t\t}\n\t\t\topts, _ := r.Options.(*options.CheckpointOptions)\n\n\t\t\topts.Exit = exit\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/checkpoint/list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/checkpointutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\nfunc List(ctx context.Context, client *containerd.Client, containerID string, options types.CheckpointListOptions) ([]types.CheckpointSummary, error) {\n\tvar container containerd.Container\n\tvar out []types.CheckpointSummary\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple containers found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tcontainer = found.Container\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tn, err := walker.Walk(ctx, containerID)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if n == 0 {\n\t\treturn nil, fmt.Errorf(\"error list checkpoint for container: %s, no such container\", containerID)\n\t}\n\n\tcheckpointDir, err := checkpointutil.GetCheckpointDir(options.CheckpointDir, \"\", container.ID(), false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdirs, err := os.ReadDir(checkpointDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, d := range dirs {\n\t\tif !d.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tout = append(out, types.CheckpointSummary{Name: d.Name()})\n\t}\n\n\treturn out, nil\n}\n"
  },
  {
    "path": "pkg/cmd/checkpoint/remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage checkpoint\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/checkpointutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\nfunc Remove(ctx context.Context, client *containerd.Client, containerID string, checkpointName string, options types.CheckpointRemoveOptions) error {\n\tvar container containerd.Container\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple containers found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tcontainer = found.Container\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tn, err := walker.Walk(ctx, containerID)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"error removing checkpoint for container: %s, no such container\", containerID)\n\t}\n\n\ttargetPath, err := checkpointutil.GetCheckpointDir(options.CheckpointDir, checkpointName, container.ID(), false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn os.RemoveAll(targetPath)\n}\n"
  },
  {
    "path": "pkg/cmd/compose/compose.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage compose\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipfs\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n//nolint:unused\nvar locked *os.File\n\n// New returns a new *composer.Composer.\nfunc New(client *containerd.Client, globalOptions types.GlobalCommandOptions, options composer.Options, stdout, stderr io.Writer) (*composer.Composer, error) {\n\tif err := composer.Lock(globalOptions.DataRoot, globalOptions.Address); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace), netutil.WithDefaultNetwork(globalOptions.BridgeIP))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetworkConfigs, err := cniEnv.NetworkList()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toptions.NetworkExists = func(netName string) (bool, error) {\n\t\tfor _, f := range networkConfigs {\n\t\t\tif f.Name == netName {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t\treturn false, nil\n\t}\n\n\toptions.NetworkInUse = func(ctx context.Context, netName string) (bool, error) {\n\t\tnetworkUsedByNsMap, err := netutil.UsedNetworks(ctx, client)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tfor _, v := range networkUsedByNsMap {\n\t\t\tif strutil.InStringSlice(v, netName) {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tvolStore, err := volume.Store(globalOptions.Namespace, globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// FIXME: this is racy. See note in up_volume.go\n\toptions.VolumeExists = volStore.Exists\n\n\toptions.ImageExists = func(ctx context.Context, rawRef string) (bool, error) {\n\t\tparsedReference, err := referenceutil.Parse(rawRef)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tref := parsedReference.String()\n\t\tif _, err := client.ImageService().Get(ctx, ref); err != nil {\n\t\t\tif errors.Is(err, errdefs.ErrNotFound) {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn false, err\n\t\t}\n\t\treturn true, nil\n\t}\n\n\toptions.EnsureImage = func(ctx context.Context, imageName, pullMode, platform string, ps *serviceparser.Service, quiet bool) error {\n\t\tocispecPlatforms := []ocispec.Platform{platforms.DefaultSpec()}\n\t\tif platform != \"\" {\n\t\t\tparsed, err := platforms.Parse(platform)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tocispecPlatforms = []ocispec.Platform{parsed} // no append\n\t\t}\n\n\t\timgPullOpts := types.ImagePullOptions{\n\t\t\tGOptions:        globalOptions,\n\t\t\tOCISpecPlatform: ocispecPlatforms,\n\t\t\tUnpack:          nil,\n\t\t\tMode:            pullMode,\n\t\t\tQuiet:           quiet,\n\t\t\tRFlags:          types.RemoteSnapshotterFlags{},\n\t\t\tStdout:          stdout,\n\t\t\tStderr:          stderr,\n\t\t}\n\n\t\tparsedReference, err := referenceutil.Parse(imageName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif parsedReference.Protocol != \"\" {\n\t\t\tvar ipfsPath string\n\t\t\tif ipfsAddress := options.IPFSAddress; ipfsAddress != \"\" {\n\t\t\t\tdir, err := os.MkdirTemp(\"\", \"apidirtmp\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tdefer os.RemoveAll(dir)\n\t\t\t\tif err := filesystem.WriteFile(filepath.Join(dir, \"api\"), []byte(ipfsAddress), 0600); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tipfsPath = dir\n\t\t\t}\n\t\t\t_, err = ipfs.EnsureImage(ctx, client, string(parsedReference.Protocol), parsedReference.String(), ipfsPath, imgPullOpts)\n\t\t\treturn err\n\t\t}\n\n\t\timageVerifyOptions := imageVerifyOptionsFromCompose(ps)\n\t\tref, err := signutil.Verify(ctx, imageName, globalOptions.HostsDir, globalOptions.Experimental, imageVerifyOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = imgutil.EnsureImage(ctx, client, ref, imgPullOpts)\n\t\treturn err\n\t}\n\n\treturn composer.New(options, client, (*config.Config)(&globalOptions))\n}\n\nfunc imageVerifyOptionsFromCompose(ps *serviceparser.Service) types.ImageVerifyOptions {\n\tvar opt types.ImageVerifyOptions\n\tif verifier, ok := ps.Unparsed.Extensions[serviceparser.ComposeVerify]; ok {\n\t\topt.Provider = verifier.(string)\n\t} else {\n\t\topt.Provider = \"none\"\n\t}\n\n\t// for cosign, if key is given, use key mode, otherwise use keyless mode.\n\tif keyVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]; ok {\n\t\topt.CosignKey = keyVal.(string)\n\t}\n\tif ciVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateIdentity]; ok {\n\t\topt.CosignCertificateIdentity = ciVal.(string)\n\t}\n\tif cirVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateIdentityRegexp]; ok {\n\t\topt.CosignCertificateIdentityRegexp = cirVal.(string)\n\t}\n\tif coiVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateOidcIssuer]; ok {\n\t\topt.CosignCertificateOidcIssuer = coiVal.(string)\n\t}\n\tif coirVal, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateOidcIssuerRegexp]; ok {\n\t\topt.CosignCertificateOidcIssuerRegexp = coirVal.(string)\n\t}\n\treturn opt\n}\n"
  },
  {
    "path": "pkg/cmd/container/attach.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/console\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signalutil\"\n)\n\n// Attach attaches stdin, stdout, and stderr to a running container.\nfunc Attach(ctx context.Context, client *containerd.Client, req string, options types.ContainerAttachOptions) error {\n\t// Find the container.\n\tvar container containerd.Container\n\tvar cStatus containerd.Status\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tcontainer = found.Container\n\t\t\treturn nil\n\t\t},\n\t}\n\tn, err := walker.Walk(ctx, req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error when trying to find the container: %w\", err)\n\t}\n\tif n == 0 {\n\t\treturn fmt.Errorf(\"no container is found given the string: %s\", req)\n\t} else if n > 1 {\n\t\treturn fmt.Errorf(\"more than one containers are found given the string: %s\", req)\n\t}\n\n\tdefer func() {\n\t\tcontainerLabels, err := container.Labels(ctx)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Errorf(\"failed to getting container labels: %s\", err)\n\t\t\treturn\n\t\t}\n\t\trm, err := containerutil.DecodeContainerRmOptLabel(containerLabels[labels.ContainerAutoRemove])\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Errorf(\"failed to decode string to bool value: %s\", err)\n\t\t\treturn\n\t\t}\n\t\tif rm && cStatus.Status == containerd.Stopped {\n\t\t\tif err = RemoveContainer(ctx, container, options.GOptions, true, true, client); err != nil {\n\t\t\t\tlog.L.WithError(err).Warnf(\"failed to remove container %s: %s\", req, err)\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Attach to the container.\n\tvar task containerd.Task\n\tdetachC := make(chan struct{})\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get the OCI runtime spec for the container: %w\", err)\n\t}\n\tvar (\n\t\topt cio.Opt\n\t\tcon console.Console\n\t)\n\tif spec.Process.Terminal {\n\t\tcon, err = consoleutil.Current()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer con.Reset()\n\n\t\tif _, err := term.MakeRaw(int(con.Fd())); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set the console to raw mode: %w\", err)\n\t\t}\n\t\tcloser := func() {\n\t\t\tdetachC <- struct{}{}\n\t\t\t// task will be set by container.Task later.\n\t\t\t//\n\t\t\t// We cannot use container.Task(ctx, cio.Load) to get the IO here\n\t\t\t// because the `cancel` field of the returned `*cio` is nil. [1]\n\t\t\t//\n\t\t\t// [1] https://github.com/containerd/containerd/blob/8f756bc8c26465bd93e78d9cd42082b66f276e10/cio/io.go#L358-L359\n\t\t\tio := task.IO()\n\t\t\tif io == nil {\n\t\t\t\tlog.G(ctx).Errorf(\"got a nil io\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tio.Cancel()\n\t\t}\n\t\tvar in io.Reader\n\t\tif options.Stdin != nil {\n\t\t\tin, err = consoleutil.NewDetachableStdin(con, options.DetachKeys, closer)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\topt = cio.WithStreams(in, con, nil)\n\t} else {\n\t\topt = cio.WithStreams(options.Stdin, options.Stdout, options.Stderr)\n\t}\n\ttask, err = container.Task(ctx, cio.NewAttach(opt))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to attach to the container: %w\", err)\n\t}\n\tif spec.Process.Terminal {\n\t\tif err := consoleutil.HandleConsoleResize(ctx, task, con); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Error(\"console resize\")\n\t\t}\n\t}\n\tsigC := signalutil.ForwardAllSignals(ctx, task)\n\tdefer signalutil.StopCatch(sigC)\n\n\t// Wait for the container to exit.\n\tstatusC, err := task.Wait(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to init an async wait for the container to exit: %w\", err)\n\t}\n\tselect {\n\t// io.Wait() would return when either 1) the user detaches from the container OR 2) the container is about to exit.\n\t//\n\t// If we replace the `select` block with io.Wait() and\n\t// directly use task.Status() to check the status of the container after io.Wait() returns,\n\t// it can still be running even though the container is about to exit (somehow especially for Windows).\n\t//\n\t// As a result, we need a separate detachC to distinguish from the 2 cases mentioned above.\n\tcase <-detachC:\n\t\tio := task.IO()\n\t\tif io == nil {\n\t\t\treturn errors.New(\"got a nil IO from the task\")\n\t\t}\n\t\tio.Wait()\n\tcase status := <-statusC:\n\t\tcStatus, err = task.Status(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcode, _, err := status.Result()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif code != 0 {\n\t\t\treturn errutil.NewExitCoderErr(int(code))\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/commit.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/commit\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\n// Commit will commit a container’s file changes or settings into a new image.\nfunc Commit(ctx context.Context, client *containerd.Client, rawRef string, req string, options types.ContainerCommitOptions) error {\n\tparsedReference, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tchanges, err := parseChanges(options.Change)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := &commit.Opts{\n\t\tAuthor:             options.Author,\n\t\tMessage:            options.Message,\n\t\tRef:                parsedReference.String(),\n\t\tPause:              options.Pause,\n\t\tChanges:            changes,\n\t\tCompression:        options.Compression,\n\t\tFormat:             options.Format,\n\t\tEstargzOptions:     options.EstargzOptions,\n\t\tZstdChunkedOptions: options.ZstdChunkedOptions,\n\t}\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\timageID, err := commit.Commit(ctx, client, found.Container, opts, options.GOptions)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = fmt.Fprintln(options.Stdout, imageID)\n\t\t\treturn err\n\t\t},\n\t}\n\tn, err := walker.Walk(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", req)\n\t}\n\treturn nil\n}\n\nfunc parseChanges(userChanges []string) (commit.Changes, error) {\n\tconst (\n\t\t// XXX: Where can I get a constants for this?\n\t\tcommandDirective    = \"CMD\"\n\t\tentrypointDirective = \"ENTRYPOINT\"\n\t)\n\tif userChanges == nil {\n\t\treturn commit.Changes{}, nil\n\t}\n\tvar changes commit.Changes\n\tfor _, change := range userChanges {\n\t\tif change == \"\" {\n\t\t\treturn commit.Changes{}, fmt.Errorf(\"received an empty value in change flag\")\n\t\t}\n\t\tchangeFields := strings.Fields(change)\n\n\t\tswitch changeFields[0] {\n\t\tcase commandDirective:\n\t\t\tvar overrideCMD []string\n\t\t\tif err := json.Unmarshal([]byte(change[len(changeFields[0]):]), &overrideCMD); err != nil {\n\t\t\t\treturn commit.Changes{}, fmt.Errorf(\"malformed json in change flag value %q\", change)\n\t\t\t}\n\t\t\tif changes.CMD != nil {\n\t\t\t\tlog.L.Warn(\"multiple change flags supplied for the CMD directive, overriding with last supplied\")\n\t\t\t}\n\t\t\tchanges.CMD = overrideCMD\n\t\tcase entrypointDirective:\n\t\t\tvar overrideEntrypoint []string\n\t\t\tif err := json.Unmarshal([]byte(change[len(changeFields[0]):]), &overrideEntrypoint); err != nil {\n\t\t\t\treturn commit.Changes{}, fmt.Errorf(\"malformed json in change flag value %q\", change)\n\t\t\t}\n\t\t\tif changes.Entrypoint != nil {\n\t\t\t\tlog.L.Warnf(\"multiple change flags supplied for the Entrypoint directive, overriding with last supplied\")\n\t\t\t}\n\t\t\tchanges.Entrypoint = overrideEntrypoint\n\t\tdefault: // TODO: Support the rest of the change directives\n\t\t\treturn commit.Changes{}, fmt.Errorf(\"unknown change directive %q\", changeFields[0])\n\t\t}\n\t}\n\treturn changes, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/cp_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Cp copies files/folders between a running container and the local filesystem.\nfunc Cp(ctx context.Context, client *containerd.Client, options types.ContainerCpOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\treturn containerutil.CopyFiles(\n\t\t\t\tctx,\n\t\t\t\tclient,\n\t\t\t\tfound.Container,\n\t\t\t\toptions)\n\t\t},\n\t}\n\tcount, err := walker.Walk(ctx, options.ContainerReq)\n\n\tif count == -1 {\n\t\tif err == nil {\n\t\t\tpanic(\"nil error and count == -1 from ContainerWalker.Walk should never happen\")\n\t\t}\n\t\terr = fmt.Errorf(\"unable to copy: %w\", err)\n\t} else if count == 0 {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"unable to retrieve containers with error: %w\", err)\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"no container found for: %s\", options.ContainerReq)\n\t\t}\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/cmd/container/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\tdockercliopts \"github.com/docker/cli/opts\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/annotations\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/flagutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/load\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipcutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging\"\n\t\"github.com/containerd/nerdctl/v2/pkg/maputil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/namestore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/networkstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// Create will create a container.\nfunc Create(ctx context.Context, client *containerd.Client, args []string, netManager containerutil.NetworkOptionsManager, options types.ContainerCreateOptions) (containerd.Container, func(), error) {\n\t// Acquire an exclusive lock on the volume store until we are done to avoid being raced by any other\n\t// volume operations (or any other operation involving volume manipulation)\n\tvolStore, err := volume.Store(options.GOptions.Namespace, options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\terr = volStore.Lock()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tdefer volStore.Release()\n\n\t// simulate the behavior of double dash\n\tnewArg := []string{}\n\tif len(args) >= 2 && args[1] == \"--\" {\n\t\tnewArg = append(newArg, args[:1]...)\n\t\tnewArg = append(newArg, args[2:]...)\n\t\targs = newArg\n\t}\n\tvar internalLabels internalLabels\n\tinternalLabels.platform = options.Platform\n\tinternalLabels.namespace = options.GOptions.Namespace\n\n\tvar (\n\t\tid    = idgen.GenerateID()\n\t\topts  []oci.SpecOpts\n\t\tcOpts []containerd.NewContainerOpts\n\t)\n\n\tif options.CidFile != \"\" {\n\t\tif err := writeCIDFile(options.CidFile, id); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tinternalLabels.cidFile = options.CidFile\n\t}\n\tdataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tinternalLabels.stateDir, err = containerutil.ContainerStateDirPath(options.GOptions.Namespace, dataStore, id)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif err := os.MkdirAll(internalLabels.stateDir, 0700); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\topts = append(opts,\n\t\toci.WithDefaultSpec(),\n\t)\n\n\tplatformOpts, err := setPlatformOptions(ctx, client, id, netManager.NetworkOptions().UTSNamespace, &internalLabels, options)\n\tif err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t}\n\topts = append(opts, platformOpts...)\n\n\tif len(options.CDIDevices) > 0 || len(options.GPUs) > 0 {\n\t\topts = append(opts, withStaticCDIRegistry(options.GOptions.CDISpecDirs))\n\t}\n\n\topts = append(opts,\n\t\twithGPUs(options.GPUs...),\n\t\twithCDIDevices(options.CDIDevices...),\n\t)\n\n\tif _, err := referenceutil.Parse(args[0]); errors.Is(err, referenceutil.ErrLoadOCIArchiveRequired) {\n\t\timageRef := args[0]\n\n\t\t// Load and create the platform specified by the user.\n\t\t// If none specified, fallback to the default platform.\n\t\tplatform := []string{}\n\t\tif options.Platform != \"\" {\n\t\t\tplatform = append(platform, options.Platform)\n\t\t}\n\n\t\timages, err := load.FromOCIArchive(ctx, client, imageRef, types.ImageLoadOptions{\n\t\t\tStdout:       options.Stdout,\n\t\t\tGOptions:     options.GOptions,\n\t\t\tPlatform:     platform,\n\t\t\tAllPlatforms: false,\n\t\t\tQuiet:        options.ImagePullOpt.Quiet,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t} else if len(images) == 0 {\n\t\t\t// This is a regression and should not occur.\n\t\t\treturn nil, nil, errors.New(\"OCI archive did not contain any images\")\n\t\t}\n\n\t\timage := images[0].Name\n\t\t// Multiple images loaded from the provided archive. Default to the first image found.\n\t\tif len(images) != 1 {\n\t\t\tlog.L.Warnf(\"multiple images are found for the platform, defaulting to image %s...\", image)\n\t\t}\n\n\t\targs[0] = image\n\t}\n\n\tvar ensuredImage *imgutil.EnsuredImage\n\tif !options.Rootfs {\n\t\tvar platformSS []string // len: 0 or 1\n\t\tif options.Platform != \"\" {\n\t\t\tplatformSS = append(platformSS, options.Platform)\n\t\t}\n\t\tocispecPlatforms, err := platformutil.NewOCISpecPlatformSlice(false, platformSS)\n\t\tif err != nil {\n\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t\t}\n\t\trawRef := args[0]\n\n\t\toptions.ImagePullOpt.Mode = options.Pull\n\t\toptions.ImagePullOpt.OCISpecPlatform = ocispecPlatforms\n\t\toptions.ImagePullOpt.Unpack = nil\n\n\t\tensuredImage, err = image.EnsureImage(ctx, client, rawRef, options.ImagePullOpt)\n\t\tif err != nil {\n\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t\t}\n\t}\n\n\tif ensuredImage != nil && ensuredImage.ImageConfig.User != \"\" {\n\t\tinternalLabels.user = ensuredImage.ImageConfig.User\n\t}\n\n\t// Override it if User is passed\n\tif options.User != \"\" {\n\t\tinternalLabels.user = options.User\n\t}\n\n\trootfsOpts, rootfsCOpts, err := generateRootfsOpts(args, id, ensuredImage, options)\n\tif err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t}\n\topts = append(opts, rootfsOpts...)\n\tcOpts = append(cOpts, rootfsCOpts...)\n\tif options.UserNS != \"\" {\n\t\tif !options.Rootfs {\n\t\t\tif runtime.GOOS != \"linux\" {\n\t\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), errors.New(\"UserNS is only supported on Rootful Linux\")\n\n\t\t\t} else if rootlessutil.IsRootless() {\n\t\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), errors.New(\"UserNS is only supported in Rootful Linux\")\n\t\t\t}\n\t\t\tuserNameSpaceOpts, userNameSpaceCOpts, err := getUserNamespaceOpts(ctx, client, &options, *ensuredImage, id)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t\t\t}\n\t\t\topts = append(opts, userNameSpaceOpts...)\n\t\t\tcOpts = append(cOpts, userNameSpaceCOpts...)\n\n\t\t\tuserNsOpts, err := getContainerUserNamespaceNetOpts(ctx, client, netManager)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t\t\t}\n\t\t\topts = append(opts, userNsOpts...)\n\t\t} else {\n\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), errors.New(\"UserNS is not supported with rootfs images\")\n\t\t}\n\t} else {\n\t\tif !options.Rootfs {\n\t\t\t// UserNS not set and its a normal image\n\t\t\tcOpts = append(cOpts, containerd.WithNewSnapshot(id, ensuredImage.Image))\n\t\t}\n\t}\n\n\tif options.Workdir != \"\" {\n\t\topts = append(opts, oci.WithProcessCwd(options.Workdir))\n\t}\n\n\tenvs, err := flagutil.MergeEnvFileAndOSEnv(options.EnvFile, options.Env)\n\tif err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t}\n\n\tif options.Interactive {\n\t\tif options.Detach {\n\t\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), errors.New(\"currently flag -i and -d cannot be specified together (FIXME)\")\n\t\t}\n\t}\n\n\tif options.TTY {\n\t\topts = append(opts, oci.WithTTY)\n\t}\n\n\tvar mountOpts []oci.SpecOpts\n\tmountOpts, internalLabels.anonVolumes, internalLabels.mountPoints, err = generateMountOpts(ctx, client, ensuredImage, volStore, options)\n\tif err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t}\n\topts = append(opts, mountOpts...)\n\n\t// Always set internalLabels.logURI\n\t// to support restart the container that run with \"-it\", like\n\t//\n\t// 1, nerdctl run --name demo -it imagename\n\t// 2, ctrl + c to stop demo container\n\t// 3, nerdctl start/restart demo\n\tlogConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace, options.GOptions.Address)\n\tif err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t}\n\tinternalLabels.logURI = logConfig.LogURI\n\tinternalLabels.logConfig = logConfig\n\tif logConfig.Driver == \"\" && logConfig.Address == options.GOptions.Address {\n\t\tinternalLabels.logConfig.Driver = \"json-file\"\n\t}\n\n\trestartOpts, err := generateRestartOpts(ctx, client, options.Restart, logConfig.LogURI, options.InRun)\n\tif err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err\n\t}\n\tcOpts = append(cOpts, restartOpts...)\n\n\tif err = netManager.VerifyNetworkOptions(ctx); err != nil {\n\t\treturn nil, generateRemoveStateDirFunc(ctx, id, internalLabels), fmt.Errorf(\"failed to verify networking settings: %w\", err)\n\t}\n\n\tnetOpts, netNewContainerOpts, err := netManager.ContainerNetworkingOpts(ctx, id)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), fmt.Errorf(\"failed to generate networking spec options: %w\", err)\n\t}\n\topts = append(opts, netOpts...)\n\tcOpts = append(cOpts, netNewContainerOpts...)\n\n\tnetLabelOpts, err := netManager.InternalNetworkingOptionLabels(ctx)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), fmt.Errorf(\"failed to generate internal networking labels: %w\", err)\n\t}\n\n\tenvs = append(envs, \"HOSTNAME=\"+netLabelOpts.Hostname)\n\topts = append(opts, oci.WithEnv(envs))\n\n\tinternalLabels.loadNetOpts(netLabelOpts)\n\n\t// NOTE: OCI hooks are currently not supported on Windows so we skip setting them altogether.\n\t// The OCI hooks we define (whose logic can be found in pkg/ocihook) primarily\n\t// perform network setup and teardown when using CNI networking.\n\t// On Windows, we are forced to set up and tear down the networking from within nerdctl.\n\tif runtime.GOOS != \"windows\" {\n\t\thookOpt, err := withNerdctlOCIHook(options.NerdctlCmd, options.NerdctlArgs)\n\t\tif err != nil {\n\t\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t\t}\n\t\topts = append(opts, hookOpt)\n\t}\n\n\tuOpts, err := generateUserOpts(options.User)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\n\topts = append(opts, uOpts...)\n\tgOpts, err := generateGroupsOpts(options.GroupAdd)\n\tinternalLabels.groupAdd = options.GroupAdd\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\topts = append(opts, gOpts...)\n\n\tumaskOpts, err := generateUmaskOpts(options.Umask)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\topts = append(opts, umaskOpts...)\n\n\tif !isHostNetwork(netLabelOpts) {\n\t\topts = append(opts, withDefaultUnprivilegedPortSysctl())\n\t}\n\n\trtCOpts, err := generateRuntimeCOpts(options.GOptions.CgroupManager, options.Runtime)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\tcOpts = append(cOpts, rtCOpts...)\n\n\t// Generate health check config based on CLI flags and image.\n\thealthcheckConfig, err := withHealthcheck(options, ensuredImage)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\tif healthcheckConfig != \"\" {\n\t\tinternalLabels.healthcheck = healthcheckConfig\n\t}\n\n\tlCOpts, err := withContainerLabels(options.Label, options.LabelFile, ensuredImage)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\tcOpts = append(cOpts, lCOpts...)\n\n\tvar containerNameStore namestore.NameStore\n\tif options.Name == \"\" {\n\t\tvar imageRef string\n\t\tif ensuredImage != nil {\n\t\t\timageRef = ensuredImage.Ref\n\t\t}\n\t\tparsedReference, err := referenceutil.Parse(imageRef)\n\t\t// Ignore cases where the imageRef is \"\"\n\t\tif err != nil && imageRef != \"\" {\n\t\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t\t}\n\t\toptions.Name = parsedReference.SuggestContainerName(id)\n\t}\n\n\tcontainerNameStore, err = namestore.New(dataStore, options.GOptions.Namespace)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\tif err := containerNameStore.Acquire(options.Name, id); err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\n\tinternalLabels.name = options.Name\n\tinternalLabels.pidFile = options.PidFile\n\n\textraHosts, err := containerutil.ParseExtraHosts(netManager.NetworkOptions().AddHost, options.GOptions.HostGatewayIP, \":\")\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\tinternalLabels.extraHosts = extraHosts\n\n\tinternalLabels.rm = containerutil.EncodeContainerRmOptLabel(options.Rm)\n\n\t// TODO: abolish internal labels and only use annotations\n\tilOpt, err := withInternalLabels(internalLabels)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err\n\t}\n\tcOpts = append(cOpts, ilOpt)\n\n\tnetConf := networkstore.NetworkConfig{\n\t\tPortMappings: netLabelOpts.PortMappings,\n\t}\n\terr = portutil.StoreNetworkConfig(dataStore, options.GOptions.Namespace, id, netConf)\n\tif err != nil {\n\t\treturn nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), fmt.Errorf(\"Error writing to network-config.json: %v\", err)\n\t}\n\n\topts = append(opts, propagateInternalContainerdLabelsToOCIAnnotations(),\n\t\toci.WithAnnotations(strutil.ConvertKVStringsToMap(options.Annotations)))\n\n\tvar s specs.Spec\n\tspec := containerd.WithSpec(&s, opts...)\n\n\tcOpts = append(cOpts, spec)\n\n\tc, containerErr := client.NewContainer(ctx, id, cOpts...)\n\tvar netSetupErr error\n\tif containerErr == nil {\n\t\tnetSetupErr = netManager.SetupNetworking(ctx, id)\n\t\tif netSetupErr != nil {\n\t\t\tlog.G(ctx).WithError(netSetupErr).Warnf(\"networking setup error has occurred\")\n\t\t}\n\t}\n\n\tif containerErr != nil || netSetupErr != nil {\n\t\treturnedError := containerErr\n\t\tif netSetupErr != nil {\n\t\t\treturnedError = netSetupErr // mutually exclusive\n\t\t}\n\t\treturn nil, generateGcFunc(ctx, c, options.GOptions.Namespace, id, options.Name, dataStore, containerErr, containerNameStore, netManager, internalLabels), returnedError\n\t}\n\n\treturn c, nil, nil\n}\n\nfunc generateRootfsOpts(args []string, id string, ensured *imgutil.EnsuredImage, options types.ContainerCreateOptions) (opts []oci.SpecOpts, cOpts []containerd.NewContainerOpts, err error) {\n\tif !options.Rootfs {\n\t\tcOpts = append(cOpts,\n\t\t\tcontainerd.WithImage(ensured.Image),\n\t\t\tcontainerd.WithSnapshotter(ensured.Snapshotter),\n\t\t\tcontainerd.WithImageStopSignal(ensured.Image, \"SIGTERM\"),\n\t\t)\n\n\t\tif len(ensured.ImageConfig.Env) == 0 {\n\t\t\topts = append(opts, oci.WithDefaultPathEnv)\n\t\t}\n\t\tfor ind, env := range ensured.ImageConfig.Env {\n\t\t\tif strings.HasPrefix(env, \"PATH=\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif ind == len(ensured.ImageConfig.Env)-1 {\n\t\t\t\topts = append(opts, oci.WithDefaultPathEnv)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tabsRootfs, err := filepath.Abs(args[0])\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\topts = append(opts, oci.WithRootFSPath(absRootfs), oci.WithDefaultPathEnv)\n\t}\n\n\tentrypointPath := \"\"\n\tif ensured != nil {\n\t\tif len(ensured.ImageConfig.Entrypoint) > 0 {\n\t\t\tentrypointPath = ensured.ImageConfig.Entrypoint[0]\n\t\t} else if len(ensured.ImageConfig.Cmd) > 0 {\n\t\t\tentrypointPath = ensured.ImageConfig.Cmd[0]\n\t\t}\n\t}\n\n\tif !options.Rootfs && !options.EntrypointChanged {\n\t\topts = append(opts, oci.WithImageConfigArgs(ensured.Image, args[1:]))\n\t} else {\n\t\tif !options.Rootfs {\n\t\t\topts = append(opts, oci.WithImageConfig(ensured.Image))\n\t\t}\n\t\tvar processArgs []string\n\t\tif len(options.Entrypoint) != 0 {\n\t\t\tprocessArgs = append(processArgs, options.Entrypoint...)\n\t\t}\n\t\tif len(args) > 1 {\n\t\t\tprocessArgs = append(processArgs, args[1:]...)\n\t\t}\n\t\tif len(processArgs) == 0 {\n\t\t\t// error message is from Podman\n\t\t\treturn nil, nil, errors.New(\"no command or entrypoint provided, and no CMD or ENTRYPOINT from image\")\n\t\t}\n\n\t\tentrypointPath = processArgs[0]\n\n\t\topts = append(opts, oci.WithProcessArgs(processArgs...))\n\t}\n\n\tisEntryPointSystemd := (entrypointPath == \"/sbin/init\" ||\n\t\tentrypointPath == \"/usr/sbin/init\" ||\n\t\tentrypointPath == \"/usr/local/sbin/init\")\n\n\tstopSignal := options.StopSignal\n\n\tif options.Systemd == \"always\" || (options.Systemd == \"true\" && isEntryPointSystemd) {\n\t\tif options.Privileged {\n\t\t\tsecurityOptsMap := strutil.ConvertKVStringsToMap(strutil.DedupeStrSlice(options.SecurityOpt))\n\t\t\tprivilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, \"privileged-without-host-devices\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\n\t\t\t// See: https://github.com/containers/podman/issues/15878\n\t\t\tif !privilegedWithoutHostDevices {\n\t\t\t\treturn nil, nil, errors.New(\"if --privileged is used with systemd `--security-opt privileged-without-host-devices` must also be used\")\n\t\t\t}\n\t\t}\n\n\t\topts = append(opts,\n\t\t\toci.WithoutMounts(\"/sys/fs/cgroup\"),\n\t\t\toci.WithMounts([]specs.Mount{\n\t\t\t\t{Type: \"cgroup\", Source: \"cgroup\", Destination: \"/sys/fs/cgroup\", Options: []string{\"rw\"}},\n\t\t\t\t{Type: \"tmpfs\", Source: \"tmpfs\", Destination: \"/run\"},\n\t\t\t\t{Type: \"tmpfs\", Source: \"tmpfs\", Destination: \"/run/lock\"},\n\t\t\t\t{Type: \"tmpfs\", Source: \"tmpfs\", Destination: \"/tmp\"},\n\t\t\t\t{Type: \"tmpfs\", Source: \"tmpfs\", Destination: \"/var/lib/journal\"},\n\t\t\t}),\n\t\t)\n\t\tstopSignal = \"SIGRTMIN+3\"\n\t}\n\n\tcOpts = append(cOpts, withStop(stopSignal, options.StopTimeout, ensured))\n\n\tif options.InitBinary != nil {\n\t\toptions.InitProcessFlag = true\n\t}\n\tif options.InitProcessFlag {\n\t\tbinaryPath, err := exec.LookPath(*options.InitBinary)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, exec.ErrNotFound) {\n\t\t\t\treturn nil, nil, fmt.Errorf(`init binary %q not found`, *options.InitBinary)\n\t\t\t}\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tinContainerPath := filepath.Join(\"/sbin\", filepath.Base(*options.InitBinary))\n\t\topts = append(opts, func(_ context.Context, _ oci.Client, _ *containers.Container, spec *oci.Spec) error {\n\t\t\tspec.Process.Args = append([]string{inContainerPath, \"--\"}, spec.Process.Args...)\n\t\t\tspec.Mounts = append([]specs.Mount{{\n\t\t\t\tDestination: inContainerPath,\n\t\t\t\tType:        \"bind\",\n\t\t\t\tSource:      binaryPath,\n\t\t\t\tOptions:     []string{\"bind\", \"ro\"},\n\t\t\t}}, spec.Mounts...)\n\t\t\treturn nil\n\t\t})\n\t}\n\tif options.ReadOnly {\n\t\topts = append(opts, oci.WithRootFSReadonly())\n\t}\n\treturn opts, cOpts, nil\n}\n\n// GenerateLogURI generates a log URI for the current container store\nfunc GenerateLogURI(dataStore string) (*url.URL, error) {\n\tselfExe, err := os.Executable()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs := map[string]string{\n\t\tlogging.MagicArgv1: dataStore,\n\t}\n\n\treturn cio.LogURIGenerator(\"binary\", selfExe, args)\n}\n\nfunc isHostNetwork(netOpts types.NetworkOptions) bool {\n\treturn slices.Contains(netOpts.NetworkSlice, \"host\")\n}\n\n// withDefaultUnprivilegedPortSysctl ensures that containers can bind to\n// privileged ports (<1024) without requiring CAP_NET_BIND_SERVICE inside\n// the container by defaulting net.ipv4.ip_unprivileged_port_start to 0\n// in the container's network namespace.\nfunc withDefaultUnprivilegedPortSysctl() oci.SpecOpts {\n\tconst key = \"net.ipv4.ip_unprivileged_port_start\"\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\tif s.Linux == nil {\n\t\t\t// NOP, as the target platform is not Linux\n\t\t\treturn nil\n\t\t}\n\t\tif s.Linux.Sysctl == nil {\n\t\t\ts.Linux.Sysctl = make(map[string]string)\n\t\t}\n\n\t\tif _, exists := s.Linux.Sysctl[key]; !exists {\n\t\t\ts.Linux.Sysctl[key] = \"0\"\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc withNerdctlOCIHook(cmd string, args []string) (oci.SpecOpts, error) {\n\tif rootlessutil.IsRootless() {\n\t\tdetachedNetNS, err := rootlessutil.DetachedNetNS()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to check whether RootlessKit is running with --detach-netns: %w\", err)\n\t\t}\n\t\tif detachedNetNS != \"\" {\n\t\t\t// Rewrite {cmd, args} if RootlessKit is running with --detach-netns, so that the hook can gain\n\t\t\t// CAP_NET_ADMIN in the namespaces.\n\t\t\t//   - Old:\n\t\t\t//     - cmd:  \"/usr/local/bin/nerdctl\"\n\t\t\t//     - args: {\"--data-root=/foo\", \"internal\", \"oci-hook\"}\n\t\t\t//   - New:\n\t\t\t//     - cmd:  \"/usr/bin/nsenter\"\n\t\t\t//     - args: {\"-n/run/user/1000/containerd-rootless/netns\", \"-F\", \"--\", \"/usr/local/bin/nerdctl\", \"--data-root=/foo\", \"internal\", \"oci-hook\"}\n\t\t\toldCmd, oldArgs := cmd, args\n\t\t\tcmd, err = exec.LookPath(\"nsenter\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\targs = append([]string{\"-n\" + detachedNetNS, \"-F\", \"--\", oldCmd}, oldArgs...)\n\t\t}\n\t}\n\n\targs = append([]string{cmd}, append(args, \"internal\", \"oci-hook\")...)\n\t// sbin is appended for iptables https://github.com/containerd/nerdctl/discussions/1536\n\tenv := append(os.Environ(), \"PATH=\"+os.Getenv(\"PATH\")+\":/usr/sbin:/sbin\")\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Hooks == nil {\n\t\t\ts.Hooks = &specs.Hooks{}\n\t\t}\n\t\tcrArgs := append(args, \"createRuntime\")\n\t\ts.Hooks.CreateRuntime = append(s.Hooks.CreateRuntime, specs.Hook{\n\t\t\tPath: cmd,\n\t\t\tArgs: crArgs,\n\t\t\tEnv:  env,\n\t\t})\n\t\targsCopy := append([]string(nil), args...)\n\t\tpsArgs := append(argsCopy, \"postStop\")\n\t\ts.Hooks.Poststop = append(s.Hooks.Poststop, specs.Hook{\n\t\t\tPath: cmd,\n\t\t\tArgs: psArgs,\n\t\t\tEnv:  env,\n\t\t})\n\t\treturn nil\n\t}, nil\n}\n\nfunc withContainerLabels(label, labelFile []string, ensuredImage *imgutil.EnsuredImage) ([]containerd.NewContainerOpts, error) {\n\tvar opts []containerd.NewContainerOpts\n\n\t// add labels defined by image\n\tif ensuredImage != nil {\n\t\timageLabelOpts := containerd.WithAdditionalContainerLabels(ensuredImage.ImageConfig.Labels)\n\t\topts = append(opts, imageLabelOpts)\n\t}\n\n\tlabelMap, err := readKVStringsMapfFromLabel(label, labelFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k := range labelMap {\n\t\tif strings.HasPrefix(k, annotations.Bypass4netns) {\n\t\t\tlog.L.Warnf(\"Label %q is deprecated, use an annotation instead\", k)\n\t\t} else if strings.HasPrefix(k, labels.Prefix) {\n\t\t\treturn nil, fmt.Errorf(\"internal label %q must not be specified manually\", k)\n\t\t}\n\t}\n\to := containerd.WithAdditionalContainerLabels(labelMap)\n\topts = append(opts, o)\n\n\treturn opts, nil\n}\n\nfunc readKVStringsMapfFromLabel(label, labelFile []string) (map[string]string, error) {\n\tlabelsMap := strutil.DedupeStrSlice(label)\n\tlabelsFilePath := strutil.DedupeStrSlice(labelFile)\n\tkvStrings, err := dockercliopts.ReadKVStrings(labelsFilePath, labelsMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn strutil.ConvertKVStringsToMap(kvStrings), nil\n}\n\n// parseKVStringsMapFromLogOpt parse log options KV entries and convert to Map\nfunc parseKVStringsMapFromLogOpt(logOpt []string, logDriver string) (map[string]string, error) {\n\tlogOptArray := strutil.DedupeStrSlice(logOpt)\n\tlogOptMap := strutil.ConvertKVStringsToMap(logOptArray)\n\tif logDriver == \"json-file\" {\n\t\tif _, ok := logOptMap[logging.MaxSize]; !ok {\n\t\t\tdelete(logOptMap, logging.MaxFile)\n\t\t}\n\t}\n\tif err := logging.ValidateLogOpts(logDriver, logOptMap); err != nil {\n\t\treturn nil, err\n\t}\n\treturn logOptMap, nil\n}\n\nfunc withStop(stopSignal string, stopTimeout int, ensuredImage *imgutil.EnsuredImage) containerd.NewContainerOpts {\n\treturn func(ctx context.Context, _ *containerd.Client, c *containers.Container) error {\n\t\tif c.Labels == nil {\n\t\t\tc.Labels = make(map[string]string)\n\t\t}\n\t\tvar err error\n\t\tif ensuredImage != nil {\n\t\t\tstopSignal, err = containerd.GetOCIStopSignal(ctx, ensuredImage.Image, stopSignal)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tc.Labels[containerd.StopSignalLabel] = stopSignal\n\t\tif stopTimeout != 0 {\n\t\t\tc.Labels[labels.StopTimeout] = strconv.Itoa(stopTimeout)\n\t\t}\n\t\treturn nil\n\t}\n}\n\ntype internalLabels struct {\n\t// labels from cmd options\n\tnamespace  string\n\tplatform   string\n\textraHosts []string\n\tpidFile    string\n\t// labels from cmd options or automatically set\n\tname       string\n\thostname   string\n\tdomainname string\n\t// automatically generated\n\tstateDir string\n\t// network\n\tnetworks             []string\n\tipAddress            string\n\tip6Address           string\n\tmacAddress           string\n\tdnsServers           []string\n\tdnsSearchDomains     []string\n\tdnsResolvConfOptions []string\n\t// volume\n\tmountPoints []*mountutil.Processed\n\tanonVolumes []string\n\t// pid namespace\n\tpidContainer string\n\t// ipc namespace & dev/shm\n\tipc string\n\t// log\n\tlogURI string\n\t// a label to check whether the --rm option is specified.\n\trm        string\n\tlogConfig logging.LogConfig\n\n\t// a label to chek if --cidfile is set\n\tcidFile string\n\n\t// label to check if --group-add is set\n\tgroupAdd []string\n\n\t// label for device mapping set by the --device flag\n\tdeviceMapping []dockercompat.DeviceMapping\n\n\tuser string\n\n\thealthcheck string\n}\n\n// WithInternalLabels sets the internal labels for a container.\nfunc withInternalLabels(internalLabels internalLabels) (containerd.NewContainerOpts, error) {\n\tm := make(map[string]string)\n\tvar hostConfigLabel dockercompat.HostConfigLabel\n\tvar dnsSettings dockercompat.DNSSettings\n\tm[labels.Namespace] = internalLabels.namespace\n\tm[labels.Name] = internalLabels.name\n\tm[labels.Hostname] = internalLabels.hostname\n\tm[labels.Domainname] = internalLabels.domainname\n\textraHostsJSON, err := json.Marshal(internalLabels.extraHosts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm[labels.ExtraHosts] = string(extraHostsJSON)\n\tm[labels.StateDir] = internalLabels.stateDir\n\tnetworksJSON, err := json.Marshal(internalLabels.networks)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm[labels.Networks] = string(networksJSON)\n\tif internalLabels.logURI != \"\" {\n\t\tm[labels.LogURI] = internalLabels.logURI\n\t\tlogConfigJSON, err := json.Marshal(internalLabels.logConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[labels.LogConfig] = string(logConfigJSON)\n\t}\n\tif len(internalLabels.anonVolumes) > 0 {\n\t\tanonVolumeJSON, err := json.Marshal(internalLabels.anonVolumes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[labels.AnonymousVolumes] = string(anonVolumeJSON)\n\t}\n\n\tif internalLabels.pidFile != \"\" {\n\t\tm[labels.PIDFile] = internalLabels.pidFile\n\t}\n\n\tif internalLabels.ipAddress != \"\" {\n\t\tm[labels.IPAddress] = internalLabels.ipAddress\n\t}\n\n\tif internalLabels.ip6Address != \"\" {\n\t\tm[labels.IP6Address] = internalLabels.ip6Address\n\t}\n\n\tm[labels.Platform], err = platformutil.NormalizeString(internalLabels.platform)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(internalLabels.mountPoints) > 0 {\n\t\tmounts := dockercompatMounts(internalLabels.mountPoints)\n\t\tmountPointsJSON, err := json.Marshal(mounts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tm[labels.Mounts] = string(mountPointsJSON)\n\t}\n\n\tif internalLabels.macAddress != \"\" {\n\t\tm[labels.MACAddress] = internalLabels.macAddress\n\t}\n\n\tif internalLabels.pidContainer != \"\" {\n\t\tm[labels.PIDContainer] = internalLabels.pidContainer\n\t}\n\n\tif internalLabels.ipc != \"\" {\n\t\tm[labels.IPC] = internalLabels.ipc\n\t}\n\n\tif internalLabels.rm != \"\" {\n\t\tm[labels.ContainerAutoRemove] = internalLabels.rm\n\t}\n\n\tif internalLabels.cidFile != \"\" {\n\t\thostConfigLabel.CidFile = internalLabels.cidFile\n\t}\n\n\tif len(internalLabels.dnsServers) > 0 {\n\t\tdnsSettings.DNSServers = internalLabels.dnsServers\n\t}\n\n\tif len(internalLabels.dnsSearchDomains) > 0 {\n\t\tdnsSettings.DNSSearchDomains = internalLabels.dnsSearchDomains\n\t}\n\n\tif len(internalLabels.dnsResolvConfOptions) > 0 {\n\t\tdnsSettings.DNSResolvConfOptions = internalLabels.dnsResolvConfOptions\n\t}\n\n\tif len(internalLabels.deviceMapping) > 0 {\n\t\thostConfigLabel.Devices = append(hostConfigLabel.Devices, internalLabels.deviceMapping...)\n\t}\n\n\thostConfigJSON, err := json.Marshal(hostConfigLabel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm[labels.HostConfigLabel] = string(hostConfigJSON)\n\n\tdnsSettingsJSON, err := json.Marshal(dnsSettings)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm[labels.DNSSetting] = string(dnsSettingsJSON)\n\n\tif internalLabels.user != \"\" {\n\t\tm[labels.User] = internalLabels.user\n\t}\n\n\tif len(internalLabels.healthcheck) > 0 {\n\t\tm[labels.HealthCheck] = internalLabels.healthcheck\n\t}\n\n\treturn containerd.WithAdditionalContainerLabels(m), nil\n}\n\nfunc withHealthcheck(options types.ContainerCreateOptions, ensuredImage *imgutil.EnsuredImage) (string, error) {\n\t// If explicitly disabled\n\tif options.NoHealthcheck {\n\t\thc := &healthcheck.Healthcheck{\n\t\t\tTest: []string{\"NONE\"},\n\t\t}\n\t\thcJSON, err := hc.ToJSONString()\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to serialize disabled healthcheck config: %w\", err)\n\t\t}\n\t\treturn hcJSON, nil\n\t}\n\n\t// Start with health checks in image if present\n\thc := &healthcheck.Healthcheck{}\n\tif ensuredImage != nil && ensuredImage.ImageConfig.Labels != nil {\n\t\tif label := ensuredImage.ImageConfig.Labels[labels.HealthCheck]; label != \"\" {\n\t\t\tparsed, err := healthcheck.HealthCheckFromJSON(label)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to parse healthcheck label in image: %w\", err)\n\t\t\t}\n\t\t\thc = parsed\n\t\t}\n\t}\n\n\t// Apply CLI overrides\n\tif options.HealthCmd != \"\" {\n\t\thc.Test = []string{\"CMD-SHELL\", options.HealthCmd}\n\t}\n\tif options.HealthInterval != 0 {\n\t\thc.Interval = options.HealthInterval\n\t}\n\tif options.HealthTimeout != 0 {\n\t\thc.Timeout = options.HealthTimeout\n\t}\n\tif options.HealthRetries != 0 {\n\t\thc.Retries = options.HealthRetries\n\t}\n\tif options.HealthStartPeriod != 0 {\n\t\thc.StartPeriod = options.HealthStartPeriod\n\t}\n\n\t// Apply defaults for any unset values, but only if we have a healthcheck configured\n\tif len(hc.Test) > 0 && hc.Test[0] != \"NONE\" {\n\t\thc.ApplyDefaults()\n\t}\n\n\t// If no healthcheck config is set (via CLI or image), return empty string so we skip adding to container config.\n\tif reflect.DeepEqual(hc, &healthcheck.Healthcheck{}) {\n\t\treturn \"\", nil\n\t}\n\thcJSON, err := hc.ToJSONString()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to serialize healthcheck config: %w\", err)\n\t}\n\treturn hcJSON, nil\n}\n\n// loadNetOpts loads network options into InternalLabels.\nfunc (il *internalLabels) loadNetOpts(opts types.NetworkOptions) {\n\til.hostname = opts.Hostname\n\til.domainname = opts.Domainname\n\til.ipAddress = opts.IPAddress\n\til.ip6Address = opts.IP6Address\n\til.networks = opts.NetworkSlice\n\til.macAddress = opts.MACAddress\n\til.dnsServers = opts.DNSServers\n\til.dnsSearchDomains = opts.DNSSearchDomains\n\til.dnsResolvConfOptions = opts.DNSResolvConfOptions\n}\n\nfunc dockercompatMounts(mountPoints []*mountutil.Processed) []dockercompat.MountPoint {\n\tresult := make([]dockercompat.MountPoint, len(mountPoints))\n\tfor i := range mountPoints {\n\t\tmp := mountPoints[i]\n\t\tresult[i] = dockercompat.MountPoint{\n\t\t\tType:        mp.Type,\n\t\t\tName:        mp.Name,\n\t\t\tSource:      mp.Mount.Source,\n\t\t\tDestination: mp.Mount.Destination,\n\t\t\tDriver:      \"\",\n\t\t\tMode:        mp.Mode,\n\t\t}\n\t\tresult[i].RW, result[i].Propagation = dockercompat.ParseMountProperties(strings.Split(mp.Mode, \",\"))\n\n\t\t// it's an anonymous volume\n\t\tif mp.AnonymousVolume != \"\" {\n\t\t\tresult[i].Name = mp.AnonymousVolume\n\t\t}\n\n\t\t// volume only support local driver\n\t\tif mp.Type == \"volume\" {\n\t\t\tresult[i].Driver = \"local\"\n\t\t}\n\t}\n\treturn result\n}\n\nfunc processeds(mountPoints []dockercompat.MountPoint) []*mountutil.Processed {\n\tresult := make([]*mountutil.Processed, len(mountPoints))\n\tfor i := range mountPoints {\n\t\tmp := mountPoints[i]\n\t\tresult[i] = &mountutil.Processed{\n\t\t\tType: mp.Type,\n\t\t\tName: mp.Name,\n\t\t\tMount: specs.Mount{\n\t\t\t\tSource:      mp.Source,\n\t\t\t\tDestination: mp.Destination,\n\t\t\t},\n\t\t\tMode: mp.Mode,\n\t\t}\n\t}\n\treturn result\n}\n\nfunc propagateInternalContainerdLabelsToOCIAnnotations() oci.SpecOpts {\n\treturn func(ctx context.Context, oc oci.Client, c *containers.Container, s *oci.Spec) error {\n\t\tallowed := make(map[string]string)\n\t\tfor k, v := range c.Labels {\n\t\t\tif strings.Contains(k, labels.Prefix) {\n\t\t\t\tallowed[k] = v\n\t\t\t}\n\t\t}\n\t\treturn oci.WithAnnotations(allowed)(ctx, oc, c, s)\n\t}\n}\n\nfunc writeCIDFile(path, id string) error {\n\t_, err := os.Stat(path)\n\tif err == nil {\n\t\treturn fmt.Errorf(\"container ID file found, make sure the other container isn't running or delete %s\", path)\n\t} else if errors.Is(err, os.ErrNotExist) {\n\t\tf, err := os.Create(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create the container ID file: %s for %s, err: %s\", path, id, err)\n\t\t}\n\t\tdefer f.Close()\n\n\t\tif _, err := f.WriteString(id); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\treturn err\n}\n\n// generateLogConfig creates a LogConfig for the current container store\nfunc generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns, address string) (logConfig logging.LogConfig, err error) {\n\tvar u *url.URL\n\tif u, err = url.Parse(logDriver); err == nil && (u.Scheme != \"\" || logDriver == \"none\") {\n\t\tlogConfig.LogURI = logDriver\n\t} else {\n\t\tlogConfig.Driver = logDriver\n\t\tlogConfig.Address = address\n\t\tlogConfig.Opts, err = parseKVStringsMapFromLogOpt(logOpt, logDriver)\n\t\tif err != nil {\n\t\t\treturn logConfig, err\n\t\t}\n\t\tvar (\n\t\t\tlogDriverInst logging.Driver\n\t\t\tlogConfigB    []byte\n\t\t\tlu            *url.URL\n\t\t)\n\t\tlogDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts, logConfig.Address)\n\t\tif err != nil {\n\t\t\treturn logConfig, err\n\t\t}\n\t\tif err = logDriverInst.Init(dataStore, ns, id); err != nil {\n\t\t\treturn logConfig, err\n\t\t}\n\n\t\tlogConfigB, err = json.Marshal(logConfig)\n\t\tif err != nil {\n\t\t\treturn logConfig, err\n\t\t}\n\n\t\tlogConfigFilePath := logging.LogConfigFilePath(dataStore, ns, id)\n\t\tif err = filesystem.WriteFile(logConfigFilePath, logConfigB, 0600); err != nil {\n\t\t\treturn logConfig, err\n\t\t}\n\n\t\tlu, err = GenerateLogURI(dataStore)\n\t\tif err != nil {\n\t\t\treturn logConfig, err\n\t\t}\n\t\tif lu != nil {\n\t\t\tlog.L.Debugf(\"generated log driver: %s\", lu.String())\n\t\t\tlogConfig.LogURI = lu.String()\n\t\t}\n\t}\n\treturn logConfig, nil\n}\n\nfunc generateRemoveStateDirFunc(ctx context.Context, id string, internalLabels internalLabels) func() {\n\treturn func() {\n\t\tif rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil {\n\t\t\tlog.G(ctx).WithError(rmErr).Warnf(\"failed to remove container %q state dir %q\", id, internalLabels.stateDir)\n\t\t}\n\t}\n}\n\nfunc generateRemoveOrphanedDirsFunc(ctx context.Context, id, dataStore string, internalLabels internalLabels) func() {\n\treturn func() {\n\t\tif rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil {\n\t\t\tlog.G(ctx).WithError(rmErr).Warnf(\"failed to remove container %q state dir %q\", id, internalLabels.stateDir)\n\t\t}\n\n\t\ths, err := hostsstore.New(dataStore, internalLabels.namespace)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to instantiate hostsstore for %q\", internalLabels.namespace)\n\t\t} else if err = hs.Delete(id); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to remove an etchosts directory for container %q\", id)\n\t\t}\n\t}\n}\n\nfunc generateGcFunc(ctx context.Context, container containerd.Container, ns, id, name, dataStore string, containerErr error, containerNameStore namestore.NameStore, netManager containerutil.NetworkOptionsManager, internalLabels internalLabels) func() {\n\treturn func() {\n\t\tif containerErr == nil {\n\t\t\tnetGcErr := netManager.CleanupNetworking(ctx, container)\n\t\t\tif netGcErr != nil {\n\t\t\t\tlog.G(ctx).WithError(netGcErr).Warnf(\"failed to revert container %q networking settings\", id)\n\t\t\t}\n\t\t} else {\n\t\t\ths, err := hostsstore.New(dataStore, internalLabels.namespace)\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to instantiate hostsstore for %q\", internalLabels.namespace)\n\t\t\t} else {\n\t\t\t\tif _, err := hs.HostsPath(id); err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"an etchosts directory for container %q dosen't exist\", id)\n\t\t\t\t} else if err = hs.Delete(id); err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to remove an etchosts directory for container %q\", id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tipc, ipcErr := ipcutil.DecodeIPCLabel(internalLabels.ipc)\n\t\tif ipcErr != nil {\n\t\t\tlog.G(ctx).WithError(ipcErr).Warnf(\"failed to decode ipc label for container %q\", id)\n\t\t}\n\t\tif ipcErr := ipcutil.CleanUp(ipc); ipcErr != nil {\n\t\t\tlog.G(ctx).WithError(ipcErr).Warnf(\"failed to clean up ipc for container %q\", id)\n\t\t}\n\t\tif rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil {\n\t\t\tlog.G(ctx).WithError(rmErr).Warnf(\"failed to remove container %q state dir %q\", id, internalLabels.stateDir)\n\t\t}\n\n\t\tvar errE error\n\t\tif containerNameStore, errE = namestore.New(dataStore, ns); errE != nil {\n\t\t\tlog.G(ctx).WithError(errE).Warnf(\"failed to instantiate container name store during cleanup for container %q\", id)\n\t\t}\n\t\t// Double-releasing may happen with containers started with --rm, so, ignore NotFound errors\n\t\tif errE := containerNameStore.Release(name, id); errE != nil && !errors.Is(errE, store.ErrNotFound) {\n\t\t\tlog.G(ctx).WithError(errE).Warnf(\"failed to release container name store for container %q (%s)\", name, id)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/create_userns_opts_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\nfunc getUserNamespaceOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\toptions *types.ContainerCreateOptions,\n\tensuredImage imgutil.EnsuredImage,\n\tid string,\n) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\treturn []oci.SpecOpts{}, []containerd.NewContainerOpts{}, nil\n}\n\nfunc getContainerUserNamespaceNetOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tnetManager containerutil.NetworkOptionsManager,\n) ([]oci.SpecOpts, error) {\n\treturn []oci.SpecOpts{}, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/create_userns_opts_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\nfunc getUserNamespaceOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\toptions *types.ContainerCreateOptions,\n\tensuredImage imgutil.EnsuredImage,\n\tid string,\n) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\treturn []oci.SpecOpts{}, []containerd.NewContainerOpts{}, nil\n}\n\nfunc getContainerUserNamespaceNetOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tnetManager containerutil.NetworkOptionsManager,\n) ([]oci.SpecOpts, error) {\n\treturn []oci.SpecOpts{}, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/create_userns_opts_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/moby/sys/user\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/nettype\"\n)\n\n// IDMap contains a single entry for user namespace range remapping. An array\n// of IDMap entries represents the structure that will be provided to the Linux\n// kernel for creating a user namespace.\ntype IDMap struct {\n\tContainerID int `json:\"container_id\"`\n\tHostID      int `json:\"host_id\"`\n\tSize        int `json:\"size\"`\n}\n\n// IdentityMapping contains a mappings of UIDs and GIDs.\n// The zero value represents an empty mapping.\ntype IdentityMapping struct {\n\tUIDMaps []IDMap `json:\"UIDMaps\"`\n\tGIDMaps []IDMap `json:\"GIDMaps\"`\n}\n\nconst (\n\tcapabRemapIDs = \"remap-ids\"\n)\n\nfunc getUserNamespaceOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\toptions *types.ContainerCreateOptions,\n\tensuredImage imgutil.EnsuredImage,\n\tid string,\n) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\tif isDefaultUserns(options) {\n\t\treturn nil, createDefaultSnapshotOpts(id, ensuredImage), nil\n\t}\n\n\tsupportsRemap, err := snapshotterSupportsRemapLabels(ctx, client, ensuredImage.Snapshotter)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t} else if !supportsRemap {\n\t\treturn nil, nil, errors.New(\"snapshotter does not support remap-ids capability\")\n\t}\n\n\tidMapping, err := loadAndValidateIDMapping(options.UserNS)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tuidMaps, gidMaps := convertMappings(idMapping)\n\tspecOpts := getUserNamespaceSpecOpts(uidMaps, gidMaps)\n\tsnapshotOpts, err := createSnapshotOpts(id, ensuredImage, uidMaps, gidMaps)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn specOpts, snapshotOpts, nil\n}\n\n// getContainerUserNamespaceNetOpts retrieves the user namespace path for the specified container.\nfunc getContainerUserNamespaceNetOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tnetManager containerutil.NetworkOptionsManager,\n) ([]oci.SpecOpts, error) {\n\tnetOpts, err := netManager.InternalNetworkingOptionLabels(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetType, err := nettype.Detect(netOpts.NetworkSlice)\n\tif err != nil {\n\t\treturn nil, err\n\t} else if netType == nettype.Container {\n\t\tcontainerName, err := getContainerNameFromNetworkSlice(netOpts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcontainer, err := findContainer(ctx, client, containerName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err := validateContainerStatus(ctx, container); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tuserNsPath, err := getUserNamespacePath(ctx, container)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar userNameSpaceSpecOpts []oci.SpecOpts\n\t\tuserNameSpaceSpecOpts = append(userNameSpaceSpecOpts, oci.WithLinuxNamespace(specs.LinuxNamespace{\n\t\t\tType: specs.UserNamespace,\n\t\t\tPath: userNsPath,\n\t\t}))\n\t\treturn userNameSpaceSpecOpts, nil\n\t} else if netType == nettype.Namespace {\n\t\tnetNsPath, err := getNamespacePathFromNetworkSlice(netOpts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tuserNsPath, err := getUserNamespacePathFromNetNsPath(netNsPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar userNameSpaceSpecOpts []oci.SpecOpts\n\t\tuserNameSpaceSpecOpts = append(userNameSpaceSpecOpts, oci.WithLinuxNamespace(specs.LinuxNamespace{\n\t\t\tType: specs.UserNamespace,\n\t\t\tPath: userNsPath,\n\t\t}))\n\t\treturn userNameSpaceSpecOpts, nil\n\n\t}\n\treturn []oci.SpecOpts{}, nil\n}\n\nfunc getNamespacePathFromNetworkSlice(netOpts types.NetworkOptions) (string, error) {\n\tif len(netOpts.NetworkSlice) > 1 {\n\t\treturn \"\", fmt.Errorf(\"only one network namespace is supported\")\n\t}\n\tnetItems := strings.Split(netOpts.NetworkSlice[0], \":\")\n\tif len(netItems) < 2 {\n\t\treturn \"\", fmt.Errorf(\"namespace networking argument format must be 'ns:<path>', got: %q\", netOpts.NetworkSlice[0])\n\t}\n\treturn netItems[1], nil\n}\n\nfunc getUserNamespacePathFromNetNsPath(netNsPath string) (string, error) {\n\tvar path string\n\tvar maxSymlinkDepth = 255\n\tdepth := 0\n\tfor {\n\t\tvar err error\n\t\tpath, err = os.Readlink(netNsPath)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t} else if depth > maxSymlinkDepth {\n\t\t\treturn \"\", fmt.Errorf(\"EvalSymlinks: too many links\")\n\t\t}\n\n\t\tdepth++\n\t\t_, err = os.Readlink(path)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t} else if depth > maxSymlinkDepth {\n\t\t\treturn \"\", fmt.Errorf(\"EvalSymlinks: too many links\")\n\t\t}\n\n\t\tnetNsPath = path\n\t\tdepth++\n\t}\n\tmatched, err := regexp.MatchString(`^/proc/\\d+/ns/net$`, netNsPath)\n\tif err != nil {\n\t\treturn \"\", err\n\t} else if !matched {\n\t\treturn \"\", fmt.Errorf(\"path is not of the form /proc/<pid>/ns/net, unable to resolve user namespace\")\n\t}\n\tuserNsPath := filepath.Join(filepath.Dir(netNsPath), \"user\")\n\n\treturn userNsPath, nil\n}\n\nfunc convertIDMapToLinuxIDMapping(idMaps []IDMap) []specs.LinuxIDMapping {\n\t// Create a slice to hold the resulting LinuxIDMapping structs\n\tlinuxIDMappings := make([]specs.LinuxIDMapping, len(idMaps))\n\n\t// Iterate through the IDMap slice and convert each one\n\tfor i, idMap := range idMaps {\n\t\tlinuxIDMappings[i] = specs.LinuxIDMapping{\n\t\t\tContainerID: uint32(idMap.ContainerID),\n\t\t\tHostID:      uint32(idMap.HostID),\n\t\t\tSize:        uint32(idMap.Size),\n\t\t}\n\t}\n\n\t// Return the converted slice\n\treturn linuxIDMappings\n}\n\n// findContainer searches for a container by name and returns it if found.\nfunc findContainer(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tcontainerName string,\n) (containerd.Container, error) {\n\tvar container containerd.Container\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(_ context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple containers found with prefix: %s\", containerName)\n\t\t\t}\n\t\t\tcontainer = found.Container\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tif n, err := walker.Walk(ctx, containerName); err != nil {\n\t\treturn container, err\n\t} else if n == 0 {\n\t\treturn container, fmt.Errorf(\"container not found: %s\", containerName)\n\t}\n\n\treturn container, nil\n}\n\n// validateContainerStatus checks if the container is running.\nfunc validateContainerStatus(ctx context.Context, container containerd.Container) error {\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status.Status != containerd.Running {\n\t\treturn fmt.Errorf(\"container %s is not running\", container.ID())\n\t}\n\n\treturn nil\n}\n\n// getUserNamespacePath returns the path to the container's user namespace.\nfunc getUserNamespacePath(ctx context.Context, container containerd.Container) (string, error) {\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn fmt.Sprintf(\"/proc/%d/ns/user\", task.Pid()), nil\n}\n\n// Determines if the default UserNS should be used.\nfunc isDefaultUserns(options *types.ContainerCreateOptions) bool {\n\treturn options.UserNS == \"\" || options.UserNS == \"host\"\n}\n\n// Creates default snapshot options.\nfunc createDefaultSnapshotOpts(id string, image imgutil.EnsuredImage) []containerd.NewContainerOpts {\n\treturn []containerd.NewContainerOpts{\n\t\tcontainerd.WithNewSnapshot(id, image.Image),\n\t}\n}\n\n// parseGroup parses a string identifier (name or ID) and returns the corresponding group\nfunc parseGroup(identifier string) (user.Group, bool, error) {\n\tid, err := strconv.Atoi(identifier)\n\tif err == nil {\n\t\tgrp, err := user.LookupGid(id)\n\t\tif err != nil {\n\t\t\treturn user.Group{}, true, fmt.Errorf(\"could not get group for gid %d: %w\", id, err)\n\t\t}\n\t\treturn grp, true, nil\n\t}\n\n\tgrp, err := user.LookupGroup(identifier)\n\tif err != nil {\n\t\treturn user.Group{}, false, fmt.Errorf(\"could not get group for groupname %s: %w\", identifier, err)\n\t}\n\treturn grp, false, nil\n}\n\n// parseIdentifier parses a string identifier (name or ID) and returns the corresponding user\nfunc parseUser(identifier string) (user.User, bool, error) {\n\tid, err := strconv.Atoi(identifier)\n\tif err == nil {\n\t\tusr, err := user.LookupUid(id)\n\t\tif err != nil {\n\t\t\treturn user.User{}, true, fmt.Errorf(\"could not get user for uid %d: %w\", id, err)\n\t\t}\n\t\treturn usr, true, nil\n\t}\n\n\tusr, err := user.LookupUser(identifier)\n\tif err != nil {\n\t\treturn user.User{}, false, fmt.Errorf(\"could not get user for username %s: %w\", identifier, err)\n\t}\n\treturn usr, false, nil\n}\n\nfunc getUserAndGroup(spec string) (user.User, user.Group, error) {\n\tparts := strings.Split(spec, \":\")\n\tif len(parts) > 2 {\n\t\treturn user.User{}, user.Group{}, fmt.Errorf(\"invalid identity mapping format: %s\", spec)\n\t}\n\tif len(parts) == 2 && (parts[0] == \"\" || parts[1] == \"\") {\n\t\treturn user.User{}, user.Group{}, fmt.Errorf(\"invalid identity mapping format: %s\", spec)\n\t}\n\n\tuserPart := parts[0]\n\tusr, _, err := parseUser(userPart)\n\tif err != nil {\n\t\treturn user.User{}, user.Group{}, err\n\t}\n\n\tvar groupPart string\n\tif len(parts) == 1 {\n\t\tgroupPart = userPart\n\t} else {\n\t\tgroupPart = parts[1]\n\t}\n\n\tgroup, _, err := parseGroup(groupPart)\n\tif err != nil {\n\t\treturn user.User{}, user.Group{}, err\n\t}\n\n\treturn usr, group, nil\n}\n\n// LoadIdentityMapping takes a requested identity mapping specification and\n// using the data from /etc/sub{uid,gid} ranges, creates the\n// uid and gid remapping ranges for that user/group pair.\n// The specification can be in the following formats:\n// (format: <name|uid>[:<group|gid>])\nfunc LoadIdentityMapping(spec string) (IdentityMapping, error) {\n\tusr, groupUsr, err := getUserAndGroup(spec)\n\tif err != nil {\n\t\treturn IdentityMapping{}, err\n\t}\n\tsubuidRanges, err := lookupUserSubRangesFile(\"/etc/subuid\", usr)\n\tif err != nil {\n\t\treturn IdentityMapping{}, err\n\t}\n\n\tsubgidRanges, err := lookupGroupSubRangesFile(\"/etc/subgid\", groupUsr)\n\tif err != nil {\n\t\treturn IdentityMapping{}, err\n\t}\n\n\treturn IdentityMapping{\n\t\tUIDMaps: subuidRanges,\n\t\tGIDMaps: subgidRanges,\n\t}, nil\n}\n\nfunc lookupUserSubRangesFile(path string, usr user.User) ([]IDMap, error) {\n\tuidstr := strconv.Itoa(usr.Uid)\n\trangeList, err := user.ParseSubIDFileFilter(path, func(sid user.SubID) bool {\n\t\treturn sid.Name == usr.Name || sid.Name == uidstr\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rangeList) == 0 {\n\t\treturn nil, fmt.Errorf(\"no subuid ranges found for user %q\", usr.Name)\n\t}\n\n\tidMap := []IDMap{}\n\n\tcontainerID := 0\n\tfor _, idrange := range rangeList {\n\t\tidMap = append(idMap, IDMap{\n\t\t\tContainerID: containerID,\n\t\t\tHostID:      int(idrange.SubID),\n\t\t\tSize:        int(idrange.Count),\n\t\t})\n\t\tcontainerID = containerID + int(idrange.Count)\n\t}\n\treturn idMap, nil\n}\n\nfunc lookupGroupSubRangesFile(path string, grp user.Group) ([]IDMap, error) {\n\tgidstr := strconv.Itoa(grp.Gid)\n\trangeList, err := user.ParseSubIDFileFilter(path, func(sid user.SubID) bool {\n\t\treturn sid.Name == grp.Name || sid.Name == gidstr\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rangeList) == 0 {\n\t\treturn nil, fmt.Errorf(\"no subuid ranges found for user %q\", grp.Name)\n\t}\n\n\tidMap := []IDMap{}\n\tcontainerID := 0\n\tfor _, idrange := range rangeList {\n\t\tidMap = append(idMap, IDMap{\n\t\t\tContainerID: containerID,\n\t\t\tHostID:      int(idrange.SubID),\n\t\t\tSize:        int(idrange.Count),\n\t\t})\n\t\tcontainerID = containerID + int(idrange.Count)\n\t}\n\treturn idMap, nil\n}\n\n// Loads and validates the ID mapping from the given UserNS.\nfunc loadAndValidateIDMapping(userNS string) (IdentityMapping, error) {\n\tidMapping, err := LoadIdentityMapping(userNS)\n\tif err != nil {\n\t\treturn IdentityMapping{}, err\n\t}\n\tif !validIDMapping(idMapping) {\n\t\treturn IdentityMapping{}, errors.New(\"no valid UID/GID mappings found\")\n\t}\n\treturn idMapping, nil\n}\n\n// Validates that both UID and GID mappings are available.\nfunc validIDMapping(mapping IdentityMapping) bool {\n\treturn len(mapping.UIDMaps) > 0 && len(mapping.GIDMaps) > 0\n}\n\n// Converts IDMapping into LinuxIDMapping structures.\nfunc convertMappings(mapping IdentityMapping) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping) {\n\treturn convertIDMapToLinuxIDMapping(mapping.UIDMaps),\n\t\tconvertIDMapToLinuxIDMapping(mapping.GIDMaps)\n}\n\n// Builds OCI spec options for the user namespace.\nfunc getUserNamespaceSpecOpts(\n\tuidMaps, gidMaps []specs.LinuxIDMapping,\n) []oci.SpecOpts {\n\treturn []oci.SpecOpts{oci.WithUserNamespace(uidMaps, gidMaps)}\n}\n\n// Creates snapshot options based on ID mappings and snapshotter capabilities.\nfunc createSnapshotOpts(\n\tid string,\n\timage imgutil.EnsuredImage,\n\tuidMaps, gidMaps []specs.LinuxIDMapping,\n) ([]containerd.NewContainerOpts, error) {\n\tif !isValidMapping(uidMaps, gidMaps) {\n\t\treturn nil, errors.New(\"snapshotter uidmap gidmap config invalid\")\n\t}\n\treturn []containerd.NewContainerOpts{containerd.WithNewSnapshot(id, image.Image, WithUserNSRemapperLabels(uidMaps, gidMaps))}, nil\n}\n\nfunc WithUserNSRemapperLabels(uidmaps, gidmaps []specs.LinuxIDMapping) snapshots.Opt {\n\tidMap := ContainerdIDMap{\n\t\tUidMap: uidmaps,\n\t\tGidMap: gidmaps,\n\t}\n\tuidmapLabel, gidmapLabel := idMap.Marshal()\n\treturn snapshots.WithLabels(map[string]string{\n\t\tsnapshots.LabelSnapshotUIDMapping: uidmapLabel,\n\t\tsnapshots.LabelSnapshotGIDMapping: gidmapLabel,\n\t})\n}\n\nfunc isValidMapping(uidMaps, gidMaps []specs.LinuxIDMapping) bool {\n\treturn len(uidMaps) > 0 && len(gidMaps) > 0\n}\n\nfunc getContainerNameFromNetworkSlice(netOpts types.NetworkOptions) (string, error) {\n\tnetItems := strings.Split(netOpts.NetworkSlice[0], \":\")\n\tif len(netItems) < 2 || netItems[1] == \"\" {\n\t\treturn \"\", fmt.Errorf(\"container networking argument format must be 'container:<id|name>', got: %q\", netOpts.NetworkSlice[0])\n\t}\n\tcontainerName := netItems[1]\n\treturn containerName, nil\n}\n\nfunc snapshotterSupportsRemapLabels(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tsnapshotterName string,\n) (bool, error) {\n\tcaps, err := client.GetSnapshotterCapabilities(ctx, snapshotterName)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn hasCapability(caps, capabRemapIDs), nil\n}\n\n// Checks if the given capability exists in the list.\nfunc hasCapability(caps []string, capability string) bool {\n\tfor _, cap := range caps {\n\t\tif cap == capability {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/cmd/container/create_userns_opts_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"testing\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\n// TestCreateSnapshotOpts tests the createSnapshotOpts function.\nfunc TestCreateSnapshotOpts(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname        string\n\t\tid          string\n\t\timage       imgutil.EnsuredImage\n\t\tuidMaps     []specs.LinuxIDMapping\n\t\tgidMaps     []specs.LinuxIDMapping\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname:  \"Single remapping\",\n\t\t\tid:    \"container1\",\n\t\t\timage: imgutil.EnsuredImage{},\n\t\t\tuidMaps: []specs.LinuxIDMapping{\n\t\t\t\t{HostID: 1000, Size: 1},\n\t\t\t},\n\t\t\tgidMaps: []specs.LinuxIDMapping{\n\t\t\t\t{HostID: 1000, Size: 1},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"Multi remapping with support\",\n\t\t\tid:    \"container2\",\n\t\t\timage: imgutil.EnsuredImage{},\n\t\t\tuidMaps: []specs.LinuxIDMapping{\n\t\t\t\t{HostID: 1000, Size: 1},\n\t\t\t\t{HostID: 2000, Size: 1},\n\t\t\t},\n\t\t\tgidMaps: []specs.LinuxIDMapping{\n\t\t\t\t{HostID: 3000, Size: 1},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"Empty UID/GID maps\",\n\t\t\tid:          \"container4\",\n\t\t\timage:       imgutil.EnsuredImage{},\n\t\t\tuidMaps:     []specs.LinuxIDMapping{},\n\t\t\tgidMaps:     []specs.LinuxIDMapping{},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, testCase := range tests {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\t_, err := createSnapshotOpts(testCase.id, testCase.image, testCase.uidMaps, testCase.gidMaps)\n\n\t\t\tif testCase.expectError {\n\t\t\t\tassert.Assert(t, err != nil)\n\t\t\t} else {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestGetContainerNameFromNetworkSlice tests the getContainerNameFromNetworkSlice function.\nfunc TestGetContainerNameFromNetworkSlice(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname        string\n\t\tnetOpts     types.NetworkOptions\n\t\texpected    string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Valid input with container name\",\n\t\t\tnetOpts: types.NetworkOptions{\n\t\t\t\tNetworkSlice: []string{\"container:mycontainer\"},\n\t\t\t},\n\t\t\texpected:    \"mycontainer\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid input with no colon separator\",\n\t\t\tnetOpts: types.NetworkOptions{\n\t\t\t\tNetworkSlice: []string{\"container-mycontainer\"},\n\t\t\t},\n\t\t\texpected:    \"\",\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Empty NetworkSlice\",\n\t\t\tnetOpts: types.NetworkOptions{\n\t\t\t\tNetworkSlice: []string{\"\"},\n\t\t\t},\n\t\t\texpected:    \"\",\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Missing container name\",\n\t\t\tnetOpts: types.NetworkOptions{\n\t\t\t\tNetworkSlice: []string{\"container:\"},\n\t\t\t},\n\t\t\texpected:    \"\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, testCase := range tests {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tcontainerName, err := getContainerNameFromNetworkSlice(testCase.netOpts)\n\t\t\tif testCase.expectError {\n\t\t\t\tassert.Assert(t, err != nil)\n\t\t\t} else {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.Equal(t, testCase.expected, containerName)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/create_userns_opts_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\nfunc getUserNamespaceOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\toptions *types.ContainerCreateOptions,\n\tensuredImage imgutil.EnsuredImage,\n\tid string,\n) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\treturn []oci.SpecOpts{}, []containerd.NewContainerOpts{}, nil\n}\n\nfunc getContainerUserNamespaceNetOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tnetManager containerutil.NetworkOptionsManager,\n) ([]oci.SpecOpts, error) {\n\treturn []oci.SpecOpts{}, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/exec.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/console\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/flagutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signalutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/taskutil\"\n)\n\n// Exec will find the right running container to run a new command.\nfunc Exec(ctx context.Context, client *containerd.Client, args []string, options types.ContainerExecOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\treturn execActionWithContainer(ctx, client, found.Container, args, options)\n\t\t},\n\t}\n\treq := args[0]\n\tn, err := walker.Walk(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", req)\n\t}\n\treturn nil\n}\n\nfunc execActionWithContainer(ctx context.Context, client *containerd.Client, container containerd.Container, args []string, options types.ContainerExecOptions) error {\n\tpspec, err := generateExecProcessSpec(ctx, client, container, args, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar (\n\t\tioCreator cio.Creator\n\t\tin        io.Reader\n\t\tstdinC    = &taskutil.StdinCloser{\n\t\t\tStdin: os.Stdin,\n\t\t}\n\t)\n\n\tif options.Interactive {\n\t\tin = stdinC\n\t}\n\tcioOpts := []cio.Opt{cio.WithStreams(in, os.Stdout, os.Stderr)}\n\tif options.TTY {\n\t\tcioOpts = append(cioOpts, cio.WithTerminal)\n\t}\n\tioCreator = cio.NewCreator(cioOpts...)\n\n\texecID := \"exec-\" + idgen.GenerateID()\n\tprocess, err := task.Exec(ctx, execID, pspec, ioCreator)\n\tif err != nil {\n\t\treturn err\n\t}\n\tstdinC.Closer = func() {\n\t\tprocess.CloseIO(ctx, containerd.WithStdinCloser)\n\t}\n\t// if detach, we should not call this defer\n\tif !options.Detach {\n\t\tdefer process.Delete(ctx)\n\t}\n\n\tstatusC, err := process.Wait(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar con console.Console\n\tif options.TTY {\n\t\tcon, err = consoleutil.Current()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer con.Reset()\n\t\tif _, err := term.MakeRaw(int(con.Fd())); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif !options.Detach {\n\t\tif options.TTY {\n\t\t\tif err := consoleutil.HandleConsoleResize(ctx, process, con); err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Error(\"console resize\")\n\t\t\t}\n\t\t} else {\n\t\t\tsigc := signalutil.ForwardAllSignals(ctx, process)\n\t\t\tdefer signalutil.StopCatch(sigc)\n\t\t}\n\t}\n\n\tif err := process.Start(ctx); err != nil {\n\t\treturn err\n\t}\n\tif options.Detach {\n\t\treturn nil\n\t}\n\tstatus := <-statusC\n\n\tprocess.IO().Wait()\n\tprocess.IO().Close()\n\n\tcode, _, err := status.Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif code != 0 {\n\t\treturn fmt.Errorf(\"exec failed with exit code %d\", code)\n\t}\n\treturn nil\n}\n\nfunc generateExecProcessSpec(ctx context.Context, client *containerd.Client, container containerd.Container, args []string, options types.ContainerExecOptions) (*specs.Process, error) {\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuserOpts, err := generateUserOpts(options.User)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif userOpts != nil {\n\t\tc, err := container.Info(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, opt := range userOpts {\n\t\t\tif err := opt(ctx, client, &c, spec); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tpspec := spec.Process\n\tpspec.Terminal = options.TTY\n\tif pspec.Terminal {\n\t\tcon, err := consoleutil.Current()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif size, err := con.Size(); err == nil {\n\t\t\tpspec.ConsoleSize = &specs.Box{Height: uint(size.Height), Width: uint(size.Width)}\n\t\t}\n\t}\n\tpspec.Args = args[1:]\n\n\tif options.Workdir != \"\" {\n\t\tpspec.Cwd = options.Workdir\n\t}\n\tenvs, err := flagutil.MergeEnvFileAndOSEnv(options.EnvFile, options.Env)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpspec.Env = flagutil.ReplaceOrAppendEnvValues(pspec.Env, envs)\n\n\tif options.Privileged {\n\t\terr = setExecCapabilities(pspec)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn pspec, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/exec_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\t\"github.com/containerd/containerd/v2/pkg/cap\"\n)\n\nfunc setExecCapabilities(pspec *specs.Process) error {\n\tif pspec.Capabilities == nil {\n\t\tpspec.Capabilities = &specs.LinuxCapabilities{}\n\t}\n\tallCaps, err := cap.Current()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpspec.Capabilities.Bounding = allCaps\n\tpspec.Capabilities.Permitted = pspec.Capabilities.Bounding\n\tpspec.Capabilities.Inheritable = pspec.Capabilities.Bounding\n\tpspec.Capabilities.Effective = pspec.Capabilities.Bounding\n\n\t// https://github.com/moby/moby/pull/36466/files\n\t// > `docker exec --privileged` does not currently disable AppArmor\n\t// > profiles. Privileged configuration of the container is inherited\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/exec_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n)\n\nfunc setExecCapabilities(pspec *specs.Process) error {\n\t//no op freebsd\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/export.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/containerd/v2/pkg/archive\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Export exports a container's filesystem as a tar archive\nfunc Export(ctx context.Context, client *containerd.Client, containerReq string, options types.ContainerExportOptions) error {\n\tif runtime.GOOS == \"windows\" {\n\t\treturn fmt.Errorf(\"export command is not supported on Windows\")\n\t}\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\treturn exportContainer(ctx, client, found.Container, options)\n\t\t},\n\t}\n\n\tn, err := walker.Walk(ctx, containerReq)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", containerReq)\n\t}\n\treturn nil\n}\n\nfunc exportContainer(ctx context.Context, client *containerd.Client, container containerd.Container, options types.ContainerExportOptions) error {\n\t// Get container info to access the snapshot\n\tconInfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container info: %w\", err)\n\t}\n\n\t// Use the container's snapshot service to get mounts\n\t// This works for both running and stopped containers\n\tsn := client.SnapshotService(conInfo.Snapshotter)\n\tmounts, err := sn.Mounts(ctx, container.ID())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container mounts: %w\", err)\n\t}\n\n\t// Create a temporary directory to mount the snapshot\n\ttempDir, err := os.MkdirTemp(\"\", \"nerdctl-export-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary mount directory: %w\", err)\n\t}\n\tdefer os.RemoveAll(tempDir)\n\n\t// Mount the container's filesystem\n\terr = mount.All(mounts, tempDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to mount container snapshot: %w\", err)\n\t}\n\tdefer func() {\n\t\tif unmountErr := mount.Unmount(tempDir, 0); unmountErr != nil {\n\t\t\tlog.G(ctx).WithError(unmountErr).Warn(\"Failed to unmount snapshot\")\n\t\t}\n\t}()\n\n\tlog.G(ctx).Debugf(\"Mounted container snapshot at %s\", tempDir)\n\n\t// Create tar archive using WriteDiff\n\treturn createTarArchiveWithWriteDiff(ctx, tempDir, options)\n}\n\nfunc createTarArchiveWithWriteDiff(ctx context.Context, rootPath string, options types.ContainerExportOptions) error {\n\t// Create a temporary empty directory to use as the \"before\" state for WriteDiff\n\temptyDir, err := os.MkdirTemp(\"\", \"nerdctl-export-empty-\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary empty directory: %w\", err)\n\t}\n\tdefer os.RemoveAll(emptyDir)\n\n\t// Debug logging\n\tlog.G(ctx).Debugf(\"Using WriteDiff to export container filesystem from %s\", rootPath)\n\tlog.G(ctx).Debugf(\"Empty directory: %s\", emptyDir)\n\tlog.G(ctx).Debugf(\"Output writer type: %T\", options.Stdout)\n\n\t// Check if the rootPath directory exists and has contents\n\tif entries, err := os.ReadDir(rootPath); err != nil {\n\t\tlog.G(ctx).Debugf(\"Failed to read rootPath directory %s: %v\", rootPath, err)\n\t} else {\n\t\tlog.G(ctx).Debugf(\"RootPath %s contains %d entries\", rootPath, len(entries))\n\t\tfor i, entry := range entries {\n\t\t\tif i < 10 { // Only log first 10 entries to avoid spam\n\t\t\t\tlog.G(ctx).Debugf(\"  - %s (dir: %v)\", entry.Name(), entry.IsDir())\n\t\t\t}\n\t\t}\n\t\tif len(entries) > 10 {\n\t\t\tlog.G(ctx).Debugf(\"  ... and %d more entries\", len(entries)-10)\n\t\t}\n\t}\n\n\t// Double check that emptyDir is empty\n\tif entries, err := os.ReadDir(emptyDir); err != nil {\n\t\tlog.G(ctx).Debugf(\"Failed to read emptyDir directory %s: %v\", emptyDir, err)\n\t} else {\n\t\tlog.G(ctx).Debugf(\"EmptyDir %s contains %d entries\", emptyDir, len(entries))\n\t\tfor i, entry := range entries {\n\t\t\tif i < 10 { // Only log first 10 entries to avoid spam\n\t\t\t\tlog.G(ctx).Debugf(\"  - %s (dir: %v)\", entry.Name(), entry.IsDir())\n\t\t\t}\n\t\t}\n\t\tif len(entries) > 10 {\n\t\t\tlog.G(ctx).Debugf(\"  ... and %d more entries\", len(entries)-10)\n\t\t}\n\t}\n\n\t// Use WriteDiff to create a tar stream comparing the container rootfs (rootPath)\n\t// with an empty directory (emptyDir). This produces a complete export of the container.\n\terr = archive.WriteDiff(ctx, options.Stdout, emptyDir, rootPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write tar diff: %w\", err)\n\t}\n\n\tlog.G(ctx).Debugf(\"WriteDiff completed successfully\")\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/health_check.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\n// HealthCheck executes the health check command for a container\nfunc HealthCheck(ctx context.Context, client *containerd.Client, container containerd.Container) error {\n\t// verify container status and get task\n\ttask, err := isContainerRunning(ctx, container)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check if container has health check configured\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container info: %w\", err)\n\t}\n\thcConfigJSON, ok := info.Labels[labels.HealthCheck]\n\tif !ok {\n\t\treturn fmt.Errorf(\"container has no health check configured\")\n\t}\n\n\t// Parse health check configuration from labels\n\tvar hcConfig *healthcheck.Healthcheck\n\thcConfig, err = healthcheck.HealthCheckFromJSON(hcConfigJSON)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid health check configuration: %w\", err)\n\t}\n\tif hcConfig.Test == nil {\n\t\treturn fmt.Errorf(\"health check configuration has no test\")\n\t}\n\n\t// Populate defaults\n\thcConfig.Interval = timeoutWithDefault(hcConfig.Interval, healthcheck.DefaultProbeInterval)\n\thcConfig.Timeout = timeoutWithDefault(hcConfig.Timeout, healthcheck.DefaultProbeTimeout)\n\thcConfig.StartPeriod = timeoutWithDefault(hcConfig.StartPeriod, healthcheck.DefaultStartPeriod)\n\tif hcConfig.Retries == 0 {\n\t\thcConfig.Retries = healthcheck.DefaultProbeRetries\n\t}\n\n\t// Execute the health check\n\treturn healthcheck.ExecuteHealthCheck(ctx, task, container, hcConfig)\n}\n\nfunc isContainerRunning(ctx context.Context, container containerd.Container) (containerd.Task, error) {\n\t// Get container task to check status\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get container task: %w\", err)\n\t}\n\n\t// Check if container is running\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get container status: %w\", err)\n\t}\n\tif status.Status != containerd.Running {\n\t\treturn nil, fmt.Errorf(\"container is not running (status: %s)\", status.Status)\n\t}\n\n\treturn task, nil\n}\n\n// If configuredValue is zero, use defaultValue instead.\nfunc timeoutWithDefault(configuredValue time.Duration, defaultValue time.Duration) time.Duration {\n\tif configuredValue == 0 {\n\t\treturn defaultValue\n\t}\n\treturn configuredValue\n}\n"
  },
  {
    "path": "pkg/cmd/container/idmap.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n)\n\nconst invalidID = 1<<32 - 1\n\nvar invalidUser = User{Uid: invalidID, Gid: invalidID}\n\n// User is a Uid and Gid pair of a user\n//\n//nolint:revive\ntype User struct {\n\tUid uint32\n\tGid uint32\n}\n\n// IDMap contains the mappings of Uids and Gids.\n//\n//nolint:revive\ntype ContainerdIDMap struct {\n\tUidMap []specs.LinuxIDMapping `json:\"UidMap\"`\n\tGidMap []specs.LinuxIDMapping `json:\"GidMap\"`\n}\n\n// RootPair returns the ID pair for the root user\nfunc (i *ContainerdIDMap) RootPair() (User, error) {\n\tuid, err := toHost(0, i.UidMap)\n\tif err != nil {\n\t\treturn invalidUser, err\n\t}\n\tgid, err := toHost(0, i.GidMap)\n\tif err != nil {\n\t\treturn invalidUser, err\n\t}\n\treturn User{Uid: uid, Gid: gid}, nil\n}\n\n// ToHost returns the host user ID pair for the container ID pair.\nfunc (i *ContainerdIDMap) ToHost(pair User) (User, error) {\n\tvar (\n\t\ttarget User\n\t\terr    error\n\t)\n\ttarget.Uid, err = toHost(pair.Uid, i.UidMap)\n\tif err != nil {\n\t\treturn invalidUser, err\n\t}\n\ttarget.Gid, err = toHost(pair.Gid, i.GidMap)\n\tif err != nil {\n\t\treturn invalidUser, err\n\t}\n\treturn target, nil\n}\n\n// Marshal serializes the IDMap object into two strings:\n// one uidmap list and another one for gidmap list\nfunc (i *ContainerdIDMap) Marshal() (string, string) {\n\tmarshal := func(mappings []specs.LinuxIDMapping) string {\n\t\tvar arr []string\n\t\tfor _, m := range mappings {\n\t\t\tarr = append(arr, serializeLinuxIDMapping(m))\n\t\t}\n\t\treturn strings.Join(arr, \",\")\n\t}\n\treturn marshal(i.UidMap), marshal(i.GidMap)\n}\n\n// Unmarshal deserialize the passed uidmap and gidmap strings\n// into a IDMap object. Error is returned in case of failure\nfunc (i *ContainerdIDMap) Unmarshal(uidMap, gidMap string) error {\n\tunmarshal := func(str string, fn func(m specs.LinuxIDMapping)) error {\n\t\tif len(str) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tfor _, mapping := range strings.Split(str, \",\") {\n\t\t\tm, err := deserializeLinuxIDMapping(mapping)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfn(m)\n\t\t}\n\t\treturn nil\n\t}\n\tif err := unmarshal(uidMap, func(m specs.LinuxIDMapping) {\n\t\ti.UidMap = append(i.UidMap, m)\n\t}); err != nil {\n\t\treturn err\n\t}\n\treturn unmarshal(gidMap, func(m specs.LinuxIDMapping) {\n\t\ti.GidMap = append(i.GidMap, m)\n\t})\n}\n\n// toHost takes an id mapping and a remapped ID, and translates the\n// ID to the mapped host ID. If no map is provided, then the translation\n// assumes a 1-to-1 mapping and returns the passed in id #\nfunc toHost(contID uint32, idMap []specs.LinuxIDMapping) (uint32, error) {\n\tif idMap == nil {\n\t\treturn contID, nil\n\t}\n\tfor _, m := range idMap {\n\t\thigh, err := safeSum(m.ContainerID, m.Size)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif contID >= m.ContainerID && contID < high {\n\t\t\thostID, err := safeSum(m.HostID, contID-m.ContainerID)\n\t\t\tif err != nil || hostID == invalidID {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn hostID, nil\n\t\t}\n\t}\n\treturn invalidID, fmt.Errorf(\"container ID %d cannot be mapped to a host ID\", contID)\n}\n\n// safeSum returns the sum of x and y. or an error if the result overflows\nfunc safeSum(x, y uint32) (uint32, error) {\n\tz := x + y\n\tif z < x || z < y {\n\t\treturn invalidID, errors.New(\"ID overflow\")\n\t}\n\treturn z, nil\n}\n\n// serializeLinuxIDMapping marshals a LinuxIDMapping object to string\nfunc serializeLinuxIDMapping(m specs.LinuxIDMapping) string {\n\treturn fmt.Sprintf(\"%d:%d:%d\", m.ContainerID, m.HostID, m.Size)\n}\n\n// deserializeLinuxIDMapping unmarshals a string to a LinuxIDMapping object\nfunc deserializeLinuxIDMapping(str string) (specs.LinuxIDMapping, error) {\n\tvar (\n\t\thostID, ctrID, length int64\n\t)\n\t_, err := fmt.Sscanf(str, \"%d:%d:%d\", &ctrID, &hostID, &length)\n\tif err != nil {\n\t\treturn specs.LinuxIDMapping{}, fmt.Errorf(\"input value %s unparsable: %w\", str, err)\n\t}\n\tif ctrID < 0 || ctrID >= invalidID || hostID < 0 || hostID >= invalidID || length < 0 || length >= invalidID {\n\t\treturn specs.LinuxIDMapping{}, fmt.Errorf(\"invalid mapping \\\"%s\\\"\", str)\n\t}\n\treturn specs.LinuxIDMapping{\n\t\tContainerID: uint32(ctrID),\n\t\tHostID:      uint32(hostID),\n\t\tSize:        uint32(length),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerinspector\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n)\n\n// Inspect prints detailed information for each container in `containers`.\nfunc Inspect(ctx context.Context, client *containerd.Client, containers []string, options types.ContainerInspectOptions) ([]any, error) {\n\tdataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn []any{}, err\n\t}\n\n\tf := &containerInspector{\n\t\tmode:        options.Mode,\n\t\tsize:        options.Size,\n\t\tsnapshotter: containerdutil.SnapshotService(client, options.GOptions.Snapshotter),\n\t\tdataStore:   dataStore,\n\t\tnamespace:   options.GOptions.Namespace,\n\t}\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient:  client,\n\t\tOnFound: f.Handler,\n\t}\n\n\terr = walker.WalkAll(ctx, containers, true)\n\tif err != nil {\n\t\treturn []any{}, err\n\t}\n\n\treturn f.entries, nil\n}\n\ntype containerInspector struct {\n\tmode        string\n\tsize        bool\n\tsnapshotter snapshots.Snapshotter\n\tentries     []interface{}\n\tdataStore   string\n\tnamespace   string\n}\n\nfunc (x *containerInspector) Handler(ctx context.Context, found containerwalker.Found) error {\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\tn, err := containerinspector.Inspect(ctx, found.Container)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcontainerLabels, err := found.Container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tports, err := portutil.LoadPortMappings(x.dataStore, x.namespace, n.ID, containerLabels)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n.Process != nil && n.Process.NetNS != nil && len(ports) > 0 {\n\t\tn.Process.NetNS.PortMappings = ports\n\t}\n\n\tswitch x.mode {\n\tcase \"native\":\n\t\tx.entries = append(x.entries, n)\n\tcase \"dockercompat\":\n\t\td, err := dockercompat.ContainerFromNative(n)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif x.size {\n\t\t\tresourceUsage, allResourceUsage, err := imgutil.ResourceUsage(ctx, x.snapshotter, d.ID)\n\t\t\tif err == nil {\n\t\t\t\td.SizeRw = &resourceUsage.Size\n\t\t\t\td.SizeRootFs = &allResourceUsage.Size\n\t\t\t}\n\t\t}\n\t\tx.entries = append(x.entries, d)\n\t\treturn err\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown mode %q\", x.mode)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/kill.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/moby/sys/signal\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/go-cni\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/nettype\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\n// Kill kills a list of containers\nfunc Kill(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerKillOptions) error {\n\tif !strings.HasPrefix(options.KillSignal, \"SIG\") {\n\t\toptions.KillSignal = \"SIG\" + options.KillSignal\n\t}\n\n\tparsedSignal, err := signal.ParseSignal(options.KillSignal)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tif err := cleanupNetwork(ctx, found.Container, options.GOptions); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unable to cleanup network for container: %s, %q\", found.Req, err)\n\t\t\t}\n\t\t\tif err := killContainer(ctx, found.Container, parsedSignal); err != nil {\n\t\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\t\tfmt.Fprintf(options.Stderr, \"No such container: %s\\n\", found.Req)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err := fmt.Fprintln(options.Stdout, found.Container.ID())\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, reqs, true)\n}\n\nfunc killContainer(ctx context.Context, container containerd.Container, signal syscall.Signal) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tcontainerutil.UpdateErrorLabel(ctx, container, err)\n\t\t}\n\t}()\n\tif err := containerutil.UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil {\n\t\treturn err\n\t}\n\ttask, err := container.Task(ctx, cio.Load)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpaused := false\n\n\tswitch status.Status {\n\tcase containerd.Created, containerd.Stopped:\n\t\treturn fmt.Errorf(\"cannot kill container %s: container is not running\", container.ID())\n\tcase containerd.Paused, containerd.Pausing:\n\t\tpaused = true\n\tdefault:\n\t}\n\n\tif err := task.Kill(ctx, signal); err != nil {\n\t\treturn err\n\t}\n\n\t// Clean up healthcheck systemd units\n\tif err := healthcheck.RemoveTransientHealthCheckFiles(ctx, container); err != nil {\n\t\tlog.G(ctx).Warnf(\"failed to clean up healthcheck units for container %s: %s\", container.ID(), err)\n\t}\n\n\t// signal will be sent once resume is finished\n\tif paused {\n\t\tif err := task.Resume(ctx); err != nil {\n\t\t\tlog.G(ctx).Warnf(\"cannot unpause container %s: %s\", container.ID(), err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// cleanupNetwork removes cni network setup, specifically the forwards\nfunc cleanupNetwork(ctx context.Context, container containerd.Container, globalOpts types.GlobalCommandOptions) error {\n\treturn rootlessutil.WithDetachedNetNSIfAny(func() error {\n\t\t// retrieve current active port mappings\n\t\tdataStore, err := clientutil.DataStore(globalOpts.DataRoot, globalOpts.Address)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcontainerLabels, err := container.Labels(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tports, err := portutil.LoadPortMappings(dataStore, globalOpts.Namespace, container.ID(), containerLabels)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"no oci spec: %q\", err)\n\t\t}\n\t\tportMappings := []cni.NamespaceOpts{\n\t\t\tcni.WithCapabilityPortMap(ports),\n\t\t}\n\n\t\t// retrieve info to get cni instance\n\t\tspec, err := container.Spec(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnetworksJSON := spec.Annotations[labels.Networks]\n\t\tvar networks []string\n\t\tif err := json.Unmarshal([]byte(networksJSON), &networks); err != nil {\n\t\t\tlog.G(ctx).WithError(err).WithField(\"container\", container.ID()).Infof(\"unable to retrieve networking information for that container\")\n\t\t\treturn nil\n\t\t}\n\t\tnetType, err := nettype.Detect(networks)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tswitch netType {\n\t\tcase nettype.Host, nettype.None, nettype.Container, nettype.Namespace:\n\t\t\t// NOP\n\t\tcase nettype.CNI:\n\t\t\te, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork(globalOpts.BridgeIP))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcniOpts := []cni.Opt{\n\t\t\t\tcni.WithPluginDir([]string{globalOpts.CNIPath}),\n\t\t\t}\n\t\t\tvar netw *netutil.NetworkConfig\n\t\t\tfor _, netstr := range networks {\n\t\t\t\tif netw, err = e.NetworkByNameOrID(netstr); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcniOpts = append(cniOpts, cni.WithConfListBytes(netw.Bytes))\n\t\t\t}\n\t\t\tcniObj, err := cni.New(cniOpts...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar namespaceOpts []cni.NamespaceOpts\n\t\t\tnamespaceOpts = append(namespaceOpts, portMappings...)\n\t\t\tnamespace := spec.Annotations[labels.Namespace]\n\t\t\tfullID := namespace + \"-\" + container.ID()\n\t\t\tif err := cniObj.Remove(ctx, fullID, \"\", namespaceOpts...); err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed to call cni.Remove\")\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unexpected network type %v\", netType)\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "pkg/cmd/container/list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/containerd/v2/pkg/progress\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n)\n\n// List prints containers according to `options`.\nfunc List(ctx context.Context, client *containerd.Client, options types.ContainerListOptions) ([]ListItem, error) {\n\tcontainers, cMap, err := filterContainers(ctx, client, options.Filters, options.LastN, options.All)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn prepareContainers(ctx, client, containers, cMap, options)\n}\n\n// filterContainers returns containers matching the filters.\n//\n//   - Supported filters: https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md#whale-blue_square-nerdctl-ps\n//   - all means showing all containers (default shows just running).\n//   - lastN means only showing n last created containers (includes all states). Non-positive values are ignored.\n//     In other words, if lastN is positive, all will be set to true.\nfunc filterContainers(ctx context.Context, client *containerd.Client, filters []string, lastN int, all bool) ([]containerd.Container, map[string]string, error) {\n\tcontainers, err := client.Containers(ctx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfilterCtx, err := foldContainerFilters(ctx, containers, filters)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcontainers = filterCtx.MatchesFilters(ctx)\n\n\tsort.Slice(containers, func(i, j int) bool {\n\t\tinfoI, _ := containers[i].Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tinfoJ, _ := containers[j].Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\treturn infoI.CreatedAt.After(infoJ.CreatedAt)\n\t})\n\n\tif lastN > 0 {\n\t\tall = true\n\t\tif lastN < len(containers) {\n\t\t\tcontainers = containers[:lastN]\n\t\t}\n\t}\n\n\tvar wg sync.WaitGroup\n\tstatusPerContainer := make(map[string]string)\n\tvar mu sync.Mutex\n\t// formatter.ContainerStatus(ctx, c) is time consuming so we do it in goroutines and return the container's id with status as a map.\n\t// prepareContainers func will use this map to avoid call formatter.ContainerStatus again.\n\tfor _, c := range containers {\n\t\tif c.ID() == \"\" {\n\t\t\treturn nil, nil, fmt.Errorf(\"container id is nill\")\n\t\t}\n\t\twg.Add(1)\n\t\tgo func(ctx context.Context, c containerd.Container) {\n\t\t\tdefer wg.Done()\n\t\t\tcStatus := formatter.ContainerStatus(ctx, c)\n\t\t\tmu.Lock()\n\t\t\tstatusPerContainer[c.ID()] = cStatus\n\t\t\tmu.Unlock()\n\t\t}(ctx, c)\n\t}\n\twg.Wait()\n\tif all || filterCtx.all {\n\t\treturn containers, statusPerContainer, nil\n\t}\n\n\tvar upContainers []containerd.Container\n\tfor _, c := range containers {\n\t\tcStatus := statusPerContainer[c.ID()]\n\t\tif strings.HasPrefix(cStatus, \"Up\") {\n\t\t\tupContainers = append(upContainers, c)\n\t\t}\n\t}\n\treturn upContainers, statusPerContainer, nil\n}\n\ntype ListItem struct {\n\tCommand   string\n\tCreatedAt time.Time\n\tID        string\n\tImage     string\n\tPlatform  string // nerdctl extension\n\tNames     string\n\tPorts     string\n\tStatus    string\n\tRuntime   string // nerdctl extension\n\tSize      string\n\tLabels    string\n\tLabelsMap map[string]string `json:\"-\"`\n\n\t// TODO: \"LocalVolumes\", \"Mounts\", \"Networks\", \"RunningFor\", \"State\"\n}\n\nfunc (x *ListItem) Label(s string) string {\n\treturn x.LabelsMap[s]\n}\n\nfunc prepareContainers(ctx context.Context, client *containerd.Client, containers []containerd.Container, statusPerContainer map[string]string, options types.ContainerListOptions) ([]ListItem, error) {\n\tlistItems := make([]ListItem, len(containers))\n\tsnapshottersCache := map[string]snapshots.Snapshotter{}\n\tfor i, c := range containers {\n\t\tinfo, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tif err != nil {\n\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tspec, err := c.Spec(ctx)\n\t\tif err != nil {\n\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tid := c.ID()\n\t\tif options.Truncate && len(id) > 12 {\n\t\t\tid = id[:12]\n\t\t}\n\t\tvar status string\n\t\tif s, ok := statusPerContainer[c.ID()]; ok {\n\t\t\tstatus = s\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"can't get container %s status\", c.ID())\n\t\t}\n\t\tdataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcontainerLabels, err := c.Labels(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tports, err := portutil.LoadPortMappings(dataStore, options.GOptions.Namespace, c.ID(), containerLabels)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tli := ListItem{\n\t\t\tCommand:   formatter.InspectContainerCommand(spec, options.Truncate, true),\n\t\t\tCreatedAt: info.CreatedAt,\n\t\t\tID:        id,\n\t\t\tImage:     info.Image,\n\t\t\tPlatform:  info.Labels[labels.Platform],\n\t\t\tNames:     containerutil.GetContainerName(info.Labels),\n\t\t\tPorts:     formatter.FormatPorts(ports),\n\t\t\tStatus:    status,\n\t\t\tRuntime:   info.Runtime.Name,\n\t\t\tLabels:    formatter.FormatLabels(info.Labels),\n\t\t\tLabelsMap: info.Labels,\n\t\t}\n\t\tif options.Size {\n\t\t\tsnapshotter, ok := snapshottersCache[info.Snapshotter]\n\t\t\tif !ok {\n\t\t\t\tsnapshottersCache[info.Snapshotter] = containerdutil.SnapshotService(client, info.Snapshotter)\n\t\t\t\tsnapshotter = snapshottersCache[info.Snapshotter]\n\t\t\t}\n\t\t\tcontainerSize, err := getContainerSize(ctx, snapshotter, info.SnapshotKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tli.Size = containerSize\n\t\t}\n\t\tlistItems[i] = li\n\t}\n\treturn listItems, nil\n}\n\nfunc getContainerNetworks(containerLables map[string]string) []string {\n\tvar networks []string\n\tif names, ok := containerLables[labels.Networks]; ok {\n\t\tif err := json.Unmarshal([]byte(names), &networks); err != nil {\n\t\t\tlog.L.Warn(err)\n\t\t}\n\t}\n\treturn networks\n}\n\nfunc getContainerSize(ctx context.Context, snapshotter snapshots.Snapshotter, snapshotKey string) (string, error) {\n\t// get container snapshot size\n\tvar containerSize int64\n\tvar imageSize int64\n\n\tif snapshotKey != \"\" {\n\t\trw, all, err := imgutil.ResourceUsage(ctx, snapshotter, snapshotKey)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tcontainerSize = rw.Size\n\t\timageSize = all.Size\n\t}\n\n\treturn fmt.Sprintf(\"%s (virtual %s)\", progress.Bytes(containerSize).String(), progress.Bytes(imageSize).String()), nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/list_util.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n)\n\nfunc foldContainerFilters(ctx context.Context, containers []containerd.Container, filters []string) (*containerFilterContext, error) {\n\tfilterCtx := &containerFilterContext{containers: containers}\n\terr := filterCtx.foldFilters(ctx, filters)\n\treturn filterCtx, err\n}\n\ntype containerFilterContext struct {\n\tcontainers []containerd.Container\n\n\tidFilterFuncs      []func(string) bool\n\tnameFilterFuncs    []func(string) bool\n\texitedFilterFuncs  []func(int) bool\n\tbeforeFilterFuncs  []func(t time.Time) bool\n\tsinceFilterFuncs   []func(t time.Time) bool\n\tstatusFilterFuncs  []func(containerd.ProcessStatus) bool\n\tlabelFilterFuncs   []func(map[string]string) bool\n\tvolumeFilterFuncs  []func([]*containerutil.ContainerVolume) bool\n\tnetworkFilterFuncs []func([]string) bool\n\n\tall bool\n}\n\nfunc (cl *containerFilterContext) MatchesFilters(ctx context.Context) []containerd.Container {\n\tmatchesContainers := make([]containerd.Container, 0, len(cl.containers))\n\tfor _, container := range cl.containers {\n\t\tif !cl.matchesInfoFilters(ctx, container) {\n\t\t\tcontinue\n\t\t}\n\t\tif !cl.matchesTaskFilters(ctx, container) {\n\t\t\tcontinue\n\t\t}\n\t\tmatchesContainers = append(matchesContainers, container)\n\t}\n\tcl.containers = matchesContainers\n\treturn cl.containers\n}\n\nfunc (cl *containerFilterContext) foldFilters(ctx context.Context, filters []string) error {\n\tfolders := []struct {\n\t\tfilterType string\n\t\tfoldFunc   func(context.Context, string, string) error\n\t}{\n\t\t{\"id\", cl.foldIDFilter}, {\"name\", cl.foldNameFilter},\n\t\t{\"before\", cl.foldBeforeFilter}, {\"since\", cl.foldSinceFilter},\n\t\t{\"network\", cl.foldNetworkFilter}, {\"label\", cl.foldLabelFilter},\n\t\t{\"volume\", cl.foldVolumeFilter}, {\"status\", cl.foldStatusFilter},\n\t\t{\"exited\", cl.foldExitedFilter},\n\t}\n\tfor _, filter := range filters {\n\t\tinvalidFilter := true\n\t\tfor _, folder := range folders {\n\t\t\tif !strings.HasPrefix(filter, folder.filterType) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsplited := strings.SplitN(filter, \"=\", 2)\n\t\t\tif len(splited) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid argument \\\"%s\\\" for \\\"-f, --filter\\\": bad format of filter (expected name=value)\", folder.filterType)\n\t\t\t}\n\t\t\tif err := folder.foldFunc(ctx, filter, splited[1]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tinvalidFilter = false\n\t\t\tbreak\n\t\t}\n\t\tif invalidFilter {\n\t\t\treturn fmt.Errorf(\"invalid filter '%s'\", filter)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldExitedFilter(_ context.Context, filter, value string) error {\n\texited, err := strconv.Atoi(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlog.L.Infof(\"checking exit status %v %v\", filter, value)\n\tcl.exitedFilterFuncs = append(cl.exitedFilterFuncs, func(exitStatus int) bool {\n\t\treturn exited == exitStatus\n\t})\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldStatusFilter(_ context.Context, filter, value string) error {\n\tstatus := containerd.ProcessStatus(value)\n\tswitch status {\n\tcase containerd.Running, containerd.Created, containerd.Stopped, containerd.Paused, containerd.Pausing, containerd.Unknown:\n\t\tcl.statusFilterFuncs = append(cl.statusFilterFuncs, func(stats containerd.ProcessStatus) bool {\n\t\t\treturn status == stats\n\t\t})\n\tcase containerd.ProcessStatus(\"exited\"):\n\t\tcl.statusFilterFuncs = append(cl.statusFilterFuncs, func(stats containerd.ProcessStatus) bool {\n\t\t\treturn containerd.Stopped == stats\n\t\t})\n\tcase containerd.ProcessStatus(\"restarting\"), containerd.ProcessStatus(\"removing\"), containerd.ProcessStatus(\"dead\"):\n\t\tlog.L.Warnf(\"%s is not supported and is ignored\", filter)\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid filter '%s'\", filter)\n\t}\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldBeforeFilter(ctx context.Context, filter, value string) error {\n\tbeforeC, err := idOrNameFilter(ctx, cl.containers, value)\n\tif err == nil {\n\t\tcl.beforeFilterFuncs = append(cl.beforeFilterFuncs, func(t time.Time) bool {\n\t\t\treturn t.Before(beforeC.CreatedAt)\n\t\t})\n\t}\n\treturn err\n}\n\nfunc (cl *containerFilterContext) foldSinceFilter(ctx context.Context, filter, value string) error {\n\tsinceC, err := idOrNameFilter(ctx, cl.containers, value)\n\tif err == nil {\n\t\tcl.sinceFilterFuncs = append(cl.sinceFilterFuncs, func(t time.Time) bool {\n\t\t\treturn t.After(sinceC.CreatedAt)\n\t\t})\n\t}\n\treturn err\n}\n\nfunc (cl *containerFilterContext) foldIDFilter(_ context.Context, filter, value string) error {\n\tcl.idFilterFuncs = append(cl.idFilterFuncs, func(id string) bool {\n\t\tif value == \"\" {\n\t\t\treturn false\n\t\t}\n\t\treturn strings.HasPrefix(id, value)\n\t})\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldNameFilter(_ context.Context, filter, value string) error {\n\tre, err := regexp.Compile(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcl.nameFilterFuncs = append(cl.nameFilterFuncs, func(name string) bool {\n\t\tif value == \"\" {\n\t\t\treturn true\n\t\t}\n\t\treturn re.MatchString(name)\n\t})\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldLabelFilter(_ context.Context, filter, value string) error {\n\tk, v, hasValue := value, \"\", false\n\tif subs := strings.SplitN(value, \"=\", 2); len(subs) == 2 {\n\t\thasValue = true\n\t\tk, v = subs[0], subs[1]\n\t}\n\tcl.labelFilterFuncs = append(cl.labelFilterFuncs, func(labels map[string]string) bool {\n\t\tif labels == nil {\n\t\t\treturn false\n\t\t}\n\t\tval, ok := labels[k]\n\t\tif !ok || (hasValue && val != v) {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldVolumeFilter(_ context.Context, filter, value string) error {\n\tcl.volumeFilterFuncs = append(cl.volumeFilterFuncs, func(vols []*containerutil.ContainerVolume) bool {\n\t\tfor _, vol := range vols {\n\t\t\tif (vol.Source != \"\" && vol.Source == value) ||\n\t\t\t\t(vol.Destination != \"\" && vol.Destination == value) ||\n\t\t\t\t(vol.Name != \"\" && vol.Name == value) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) foldNetworkFilter(_ context.Context, filter, value string) error {\n\tcl.networkFilterFuncs = append(cl.networkFilterFuncs, func(networks []string) bool {\n\t\tfor _, network := range networks {\n\t\t\tif network == value {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\treturn nil\n}\n\nfunc (cl *containerFilterContext) matchesInfoFilters(ctx context.Context, container containerd.Container) bool {\n\tif len(cl.idFilterFuncs)+len(cl.nameFilterFuncs)+len(cl.beforeFilterFuncs)+\n\t\tlen(cl.sinceFilterFuncs)+len(cl.labelFilterFuncs)+len(cl.volumeFilterFuncs)+len(cl.networkFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tinfo, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\treturn cl.matchesIDFilter(info) && cl.matchesNameFilter(info) && cl.matchesBeforeFilter(info) &&\n\t\tcl.matchesSinceFilter(info) && cl.matchesLabelFilter(info) && cl.matchesVolumeFilter(info) &&\n\t\tcl.matchesNetworkFilter(info)\n}\n\nfunc (cl *containerFilterContext) matchesTaskFilters(ctx context.Context, container containerd.Container) bool {\n\tif len(cl.exitedFilterFuncs)+len(cl.statusFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\tif errdefs.IsNotFound(err) {\n\t\t\t// Check if we want to filter created containers\n\t\t\treturn cl.matchesExitedFilter(containerd.Status{Status: containerd.Created}) && cl.matchesStatusFilter(containerd.Status{Status: containerd.Created})\n\t\t}\n\t\tlog.G(ctx).Warn(err)\n\t\treturn false\n\t}\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\tlog.G(ctx).Warn(err)\n\t\treturn false\n\t}\n\treturn cl.matchesExitedFilter(status) && cl.matchesStatusFilter(status)\n}\n\nfunc (cl *containerFilterContext) matchesExitedFilter(status containerd.Status) bool {\n\tif len(cl.exitedFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tif status.Status != containerd.Stopped {\n\t\treturn false\n\t}\n\tfor _, exitedFilterFunc := range cl.exitedFilterFuncs {\n\t\tif !exitedFilterFunc(int(status.ExitStatus)) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesStatusFilter(status containerd.Status) bool {\n\tif len(cl.statusFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tcl.all = true\n\tfor _, statusFilterFunc := range cl.statusFilterFuncs {\n\t\tif !statusFilterFunc(status.Status) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesIDFilter(info containers.Container) bool {\n\tif len(cl.idFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tfor _, idFilterFunc := range cl.idFilterFuncs {\n\t\tif !idFilterFunc(info.ID) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesNameFilter(info containers.Container) bool {\n\tif len(cl.nameFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tcName := containerutil.GetContainerName(info.Labels)\n\tfor _, nameFilterFunc := range cl.nameFilterFuncs {\n\t\tif !nameFilterFunc(cName) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesSinceFilter(info containers.Container) bool {\n\tif len(cl.sinceFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tfor _, sinceFilterFunc := range cl.sinceFilterFuncs {\n\t\tif !sinceFilterFunc(info.CreatedAt) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesBeforeFilter(info containers.Container) bool {\n\tif len(cl.beforeFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tfor _, beforeFilterFunc := range cl.beforeFilterFuncs {\n\t\tif !beforeFilterFunc(info.CreatedAt) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesLabelFilter(info containers.Container) bool {\n\tfor _, labelFilterFunc := range cl.labelFilterFuncs {\n\t\tif !labelFilterFunc(info.Labels) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (cl *containerFilterContext) matchesVolumeFilter(info containers.Container) bool {\n\tif len(cl.volumeFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tvols := containerutil.GetContainerVolumes(info.Labels)\n\tfor _, volumeFilterFunc := range cl.volumeFilterFuncs {\n\t\tif !volumeFilterFunc(vols) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (cl *containerFilterContext) matchesNetworkFilter(info containers.Container) bool {\n\tif len(cl.networkFilterFuncs) == 0 {\n\t\treturn true\n\t}\n\tnetworks := getContainerNetworks(info.Labels)\n\tfor _, networkFilterFunc := range cl.networkFilterFuncs {\n\t\tif !networkFilterFunc(networks) {\n\t\t\tcontinue\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc idOrNameFilter(ctx context.Context, containers []containerd.Container, value string) (*containers.Container, error) {\n\tfor _, container := range containers {\n\t\tinfo, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif strings.HasPrefix(info.ID, value) || strings.Contains(containerutil.GetContainerName(info.Labels), value) {\n\t\t\treturn &info, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"no such container %s\", value)\n}\n"
  },
  {
    "path": "pkg/cmd/container/logs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sort\"\n\t\"strings\"\n\t\"syscall\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types/cri\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels/k8slabels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging\"\n)\n\nfunc Logs(ctx context.Context, client *containerd.Client, container string, options types.ContainerLogsOptions) error {\n\tdataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch options.GOptions.Namespace {\n\tcase \"moby\":\n\t\tlog.G(ctx).Warn(\"Currently, `nerdctl logs` only supports containers created with `nerdctl run -d` or CRI\")\n\t}\n\n\tstopChannel := make(chan os.Signal, 1)\n\t// catch OS signals:\n\tsignal.Notify(stopChannel, syscall.SIGTERM, syscall.SIGINT)\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tl, err := found.Container.Labels(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlogPath, err := getLogPath(ctx, found.Container)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfollow := options.Follow\n\t\t\tif follow {\n\t\t\t\ttask, err := found.Container.Task(ctx, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !errdefs.IsNotFound(err) {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tfollow = false\n\t\t\t\t} else {\n\t\t\t\t\tstatus, err := task.Status(ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif status.Status != containerd.Running {\n\t\t\t\t\t\tfollow = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\twaitCh, err := task.Wait(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"failed to get wait channel for task %#v: %w\", task, err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Setup goroutine to send stop event if container task finishes:\n\t\t\t\t\t\tgo func() {\n\t\t\t\t\t\t\t<-waitCh\n\t\t\t\t\t\t\t// Wait for logger to process remaining logs after container exit\n\t\t\t\t\t\t\tif err = logging.WaitForLogger(dataStore, l[labels.Namespace], found.Container.ID()); err != nil {\n\t\t\t\t\t\t\t\tlog.G(ctx).WithError(err).Error(\"failed to wait for logger shutdown\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlog.G(ctx).Debugf(\"container task has finished, sending kill signal to log viewer\")\n\t\t\t\t\t\t\tstopChannel <- os.Interrupt\n\t\t\t\t\t\t}()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar detailPrefix string\n\t\t\tif options.Details {\n\t\t\t\tif logConfigJSON, ok := l[\"nerdctl/log-config\"]; ok {\n\t\t\t\t\ttype logConfig struct {\n\t\t\t\t\t\tOpts map[string]string `json:\"opts\"`\n\t\t\t\t\t}\n\n\t\t\t\t\te, err := getContainerEnvs(ctx, found.Container)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tvar logCfg logConfig\n\t\t\t\t\tvar optPairs []string\n\n\t\t\t\t\tif err := json.Unmarshal([]byte(logConfigJSON), &logCfg); err == nil {\n\t\t\t\t\t\tenvOpts, labelOpts := getLogOpts(logCfg.Opts)\n\n\t\t\t\t\t\tfor _, v := range envOpts {\n\t\t\t\t\t\t\tif env, ok := e[v]; ok {\n\t\t\t\t\t\t\t\toptPairs = append(optPairs, fmt.Sprintf(\"%s=%s\", v, env))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor _, v := range labelOpts {\n\t\t\t\t\t\t\tif label, ok := l[v]; ok {\n\t\t\t\t\t\t\t\toptPairs = append(optPairs, fmt.Sprintf(\"%s=%s\", v, label))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif len(optPairs) > 0 {\n\t\t\t\t\t\t\tsort.Strings(optPairs)\n\t\t\t\t\t\t\tdetailPrefix = strings.Join(optPairs, \",\")\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlog.L.Warn(\"failed to parse `--details` option, detailed information might not be displayed\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlogViewOpts := logging.LogViewOptions{\n\t\t\t\tContainerID:       found.Container.ID(),\n\t\t\t\tNamespace:         l[labels.Namespace],\n\t\t\t\tDatastoreRootPath: dataStore,\n\t\t\t\tLogPath:           logPath,\n\t\t\t\tFollow:            follow,\n\t\t\t\tTimestamps:        options.Timestamps,\n\t\t\t\tTail:              options.Tail,\n\t\t\t\tSince:             options.Since,\n\t\t\t\tUntil:             options.Until,\n\t\t\t\tDetails:           options.Details,\n\t\t\t\tDetailPrefix:      &detailPrefix,\n\t\t\t}\n\t\t\tlogViewer, err := logging.InitContainerLogViewer(l, logViewOpts, stopChannel, options.GOptions.Experimental)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn logViewer.PrintLogsTo(options.Stdout, options.Stderr)\n\t\t},\n\t}\n\tn, err := walker.Walk(ctx, container)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", container)\n\t}\n\treturn nil\n}\n\nfunc getLogPath(ctx context.Context, container containerd.Container) (string, error) {\n\textensions, err := container.Extensions(ctx)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"get extensions for container %s,failed: %#v\", container.ID(), err)\n\t}\n\tmetaData := extensions[k8slabels.ContainerMetadataExtension]\n\tvar meta cri.ContainerMetadata\n\tif metaData != nil {\n\t\terr = meta.UnmarshalJSON(metaData.GetValue())\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"unmarshal extensions for container %s,failed: %#v\", container.ID(), err)\n\t\t}\n\t}\n\n\treturn meta.LogPath, nil\n}\n\nfunc getContainerEnvs(ctx context.Context, container containerd.Container) (map[string]string, error) {\n\tenvMap := make(map[string]string)\n\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif spec.Process == nil {\n\t\treturn envMap, nil\n\t}\n\n\tfor _, env := range spec.Process.Env {\n\t\tparts := strings.SplitN(env, \"=\", 2)\n\t\tif len(parts) == 2 {\n\t\t\tenvMap[parts[0]] = parts[1]\n\t\t}\n\t}\n\n\treturn envMap, nil\n}\n\nfunc getLogOpts(logOpts map[string]string) ([]string, []string) {\n\tvar envOpts []string\n\tvar labelOpts []string\n\n\tfor k, v := range logOpts {\n\t\tlowerKey := strings.ToLower(k)\n\t\tif lowerKey == \"env\" {\n\t\t\tenvNames := strings.Split(v, \",\")\n\t\t\tenvOpts = append(envOpts, envNames...)\n\t\t}\n\n\t\tif lowerKey == \"labels\" {\n\t\t\tlabelNames := strings.Split(v, \",\")\n\t\t\tlabelOpts = append(labelOpts, labelNames...)\n\t\t}\n\t}\n\n\treturn envOpts, labelOpts\n}\n"
  },
  {
    "path": "pkg/cmd/container/pause.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Pause pauses all containers specified by `reqs`.\nfunc Pause(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerPauseOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tif err := containerutil.Pause(ctx, client, found.Container.ID()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t_, err := fmt.Fprintln(options.Stdout, found.Req)\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, reqs, true)\n}\n"
  },
  {
    "path": "pkg/cmd/container/prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\n// Prune remove all stopped containers\nfunc Prune(ctx context.Context, client *containerd.Client, options types.ContainerPruneOptions) error {\n\tcontainers, err := client.Containers(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar deleted []string\n\tfor _, c := range containers {\n\t\tif err = RemoveContainer(ctx, c, options.GOptions, false, true, client); err == nil {\n\t\t\tdeleted = append(deleted, c.ID())\n\t\t\tcontinue\n\t\t}\n\t\tif errors.As(err, &ErrContainerStatus{}) {\n\t\t\tcontinue\n\t\t}\n\t\tlog.G(ctx).WithError(err).Warnf(\"failed to remove container %s\", c.ID())\n\t}\n\n\tif len(deleted) > 0 {\n\t\tfmt.Fprintln(options.Stdout, \"Deleted Containers:\")\n\t\tfmt.Fprintln(options.Stdout, strings.Join(deleted, \"\\n\"))\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"syscall\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipcutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/namestore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nvar _ error = ErrContainerStatus{}\n\n// ErrContainerStatus represents an error that container is in a status unexpected\n// by the caller. E.g., remove a non-stoped/non-created container without force.\ntype ErrContainerStatus struct {\n\tID     string\n\tStatus containerd.ProcessStatus\n}\n\nfunc (e ErrContainerStatus) Error() string {\n\treturn fmt.Sprintf(\"container %s is in %v status\", e.ID, e.Status)\n}\n\n// NewStatusError creates an ErrContainerStatus from container id and status.\nfunc NewStatusError(id string, status containerd.ProcessStatus) error {\n\treturn ErrContainerStatus{\n\t\tID:     id,\n\t\tStatus: status,\n\t}\n}\n\n// Remove removes a list of `containers`.\nfunc Remove(ctx context.Context, client *containerd.Client, containers []string, options types.ContainerRemoveOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tif err := RemoveContainer(ctx, found.Container, options.GOptions, options.Force, options.Volumes, client); err != nil {\n\t\t\t\tif errors.As(err, &ErrContainerStatus{}) {\n\t\t\t\t\terr = fmt.Errorf(\"%s. unpause/stop container first or force removal\", err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err := fmt.Fprintln(options.Stdout, found.Req)\n\t\t\treturn err\n\t\t},\n\t}\n\n\terr := walker.WalkAll(ctx, containers, true)\n\tif err != nil && options.Force {\n\t\tlog.G(ctx).Error(err)\n\t\treturn nil\n\t}\n\treturn err\n}\n\n// RemoveContainer removes a container from containerd store.\n// It will first retrieve system objects (namestore, etcetera), then assess whether we should remove the container or not\n// based of \"force\" and the status of the task.\n// If we are to delete, it then kills and delete the task.\n// If task removal fails, we stop (except if it was just \"NotFound\").\n// We then enter the defer cleanup function that will:\n// - remove the network config (windows only)\n// - delete the container\n// - then and ONLY then, on a successful container remove, clean things-up on our side (volume store, etcetera)\n// If you do need to add more cleanup, please do so at the bottom of the defer function\nfunc RemoveContainer(ctx context.Context, c containerd.Container, globalOptions types.GlobalCommandOptions, force bool, removeAnonVolumes bool, client *containerd.Client) (retErr error) {\n\t// Get labels\n\tcontainerLabels, err := c.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get datastore\n\tdataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Ensure we do have a stateDir label\n\tstateDir := containerLabels[labels.StateDir]\n\tif stateDir == \"\" {\n\t\tstateDir, err = containerutil.ContainerStateDirPath(globalOptions.Namespace, dataStore, c.ID())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Lock the container state\n\tlf, err := containerutil.Lock(stateDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t// If there was an error, update the label\n\t\t// Note that we will (obviously) not store any unlocking or statedir removal error from below\n\t\tif retErr != nil {\n\t\t\tcontainerutil.UpdateErrorLabel(ctx, c, retErr)\n\t\t}\n\t\t// Release the lock\n\t\tretErr = errors.Join(lf.Release(), retErr)\n\t\t// Note: technically, this is racy...\n\t\tif retErr == nil {\n\t\t\tretErr = os.RemoveAll(containerLabels[labels.StateDir])\n\t\t}\n\t}()\n\n\t// Get namespace\n\tcontainerNamespace, err := namespaces.NamespaceRequired(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get namestore\n\tnameStore, err := namestore.New(dataStore, containerNamespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Get volume store\n\tvolStore, err := volumestore.New(dataStore, globalOptions.Namespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Decode IPC\n\tipc, err := ipcutil.DecodeIPCLabel(containerLabels[labels.IPC])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the container id and name\n\tid := c.ID()\n\tname := containerLabels[labels.Name]\n\n\t// This will evaluate retErr to decide if we proceed with removal or not\n\tdefer func() {\n\t\t// If there was an error, and it was not \"NotFound\", this is a hard error, we stop here and do nothing.\n\t\tif retErr != nil && !errdefs.IsNotFound(retErr) {\n\t\t\treturn\n\t\t}\n\n\t\t// Otherwise, nil the error so that we do not write the error label on the container\n\t\tretErr = nil\n\n\t\t// Clean up healthcheck systemd units\n\t\tif err := healthcheck.RemoveTransientHealthCheckFiles(ctx, c); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to clean up healthcheck units for container %q\", id)\n\t\t}\n\n\t\t// Now, delete the actual container\n\t\tvar delOpts []containerd.DeleteOpts\n\t\tif _, err := c.Image(ctx); err == nil {\n\t\t\tdelOpts = append(delOpts, containerd.WithSnapshotCleanup)\n\t\t}\n\n\t\tspec, err := c.Spec(ctx)\n\t\tif err != nil {\n\t\t\tretErr = err\n\t\t\treturn\n\t\t}\n\n\t\tnetOpts, err := containerutil.NetworkOptionsFromSpec(spec)\n\t\tif err != nil {\n\t\t\tretErr = err\n\t\t\treturn\n\t\t}\n\n\t\tportSlice, err := portutil.LoadPortMappings(dataStore, globalOptions.Namespace, id, containerLabels)\n\t\tif err != nil {\n\t\t\tretErr = err\n\t\t\treturn\n\t\t}\n\t\tnetOpts.PortMappings = portSlice\n\n\t\tif err == nil {\n\t\t\tnetworkManager, err := containerutil.NewNetworkingOptionsManager(globalOptions, netOpts, client)\n\t\t\tif err != nil {\n\t\t\t\tretErr = fmt.Errorf(\"failed to instantiate network options manager: %w\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err := networkManager.CleanupNetworking(ctx, c); err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to clean up container networking: %q\", id)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.G(ctx).WithError(err).WithField(\"container\", id).Infof(\"unable to retrieve networking information for that container\")\n\t\t}\n\n\t\t// Delete the container now. If it fails, try again without snapshot cleanup\n\t\t// If it still fails, time to stop.\n\t\tif c.Delete(ctx, delOpts...) != nil {\n\t\t\tretErr = c.Delete(ctx)\n\t\t\tif retErr != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// Container has been removed successfully. Now we just finish the cleanup on our side.\n\n\t\t// Cleanup IPC - soft failure\n\t\tif err = ipcutil.CleanUp(ipc); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to cleanup IPC for container %q\", id)\n\t\t}\n\n\t\t// Enforce release name here in case the poststop hook name release fails - soft failure\n\t\tif name != \"\" {\n\t\t\t// Double-releasing may happen with containers started with --rm, so, ignore NotFound errors\n\t\t\tif err := nameStore.Release(name, id); err != nil && !errors.Is(err, store.ErrNotFound) {\n\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to release container name %s\", name)\n\t\t\t}\n\t\t}\n\n\t\ths, err := hostsstore.New(dataStore, containerNamespace)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to instantiate hostsstore for %q\", containerNamespace)\n\t\t} else if err = hs.Delete(id); err != nil {\n\t\t\t// De-allocate hosts file - soft failure\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to remove hosts file for container %q\", id)\n\t\t}\n\n\t\t// Volume removal is not handled by the poststop hook lifecycle because it depends on removeAnonVolumes option\n\t\t// Note that the anonymous volume list has been obtained earlier, without locking the volume store.\n\t\t// Technically, a concurrent operation MAY have deleted these anonymous volumes already at this point, which\n\t\t// would make this operation here \"soft fail\".\n\t\t// This is not a problem per-se, though we will output a warning in that case.\n\t\tif anonVolumesJSON, ok := containerLabels[labels.AnonymousVolumes]; ok && removeAnonVolumes {\n\t\t\tvar anonVolumes []string\n\t\t\tif err = json.Unmarshal([]byte(anonVolumesJSON), &anonVolumes); err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to unmarshall anonvolume information for container %q\", id)\n\t\t\t} else {\n\t\t\t\tvar errs []error\n\t\t\t\t_, errs, err = volStore.Remove(func() ([]string, []error, error) {\n\t\t\t\t\treturn anonVolumes, nil, nil\n\t\t\t\t})\n\t\t\t\tif err != nil || len(errs) > 0 {\n\t\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to remove anonymous volumes %v\", anonVolumes)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Get the task.\n\ttask, err := c.Task(ctx, cio.Load)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Task was here, get the status\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Now, we have a live task with a status.\n\tswitch status.Status {\n\tcase containerd.Paused:\n\t\t// Paused containers only get removed if we force\n\t\tif !force {\n\t\t\treturn NewStatusError(id, status.Status)\n\t\t}\n\tcase containerd.Running:\n\t\t// Running containers only get removed if we force\n\t\tif !force {\n\t\t\treturn NewStatusError(id, status.Status)\n\t\t}\n\t\t// Kill the task. Soft error.\n\t\tif err = task.Kill(ctx, syscall.SIGKILL); err != nil && !errdefs.IsNotFound(err) {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to send SIGKILL to task %v\", id)\n\t\t}\n\t\tes, err := task.Wait(ctx)\n\t\tif err == nil {\n\t\t\t<-es\n\t\t}\n\tcase containerd.Created:\n\t\t// TODO(Iceber): Since `containerd.WithProcessKill` blocks the killing of tasks with PID 0,\n\t\t// remove the judgment and break when it is compatible with the tasks.\n\t\tif task.Pid() == 0 {\n\t\t\t// Created tasks with PID 0 always get removed\n\t\t\t// Delete the task, without forcing kill\n\t\t\t_, err = task.Delete(ctx)\n\t\t\treturn err\n\t\t}\n\tcase containerd.Stopped:\n\t\t// Stopped containers always get removed\n\t\t// Delete the task, without forcing kill\n\t\t_, err = task.Delete(ctx)\n\t\treturn err\n\tdefault:\n\t\t// Unknown status error out\n\t\treturn fmt.Errorf(\"unknown container status %s\", status.Status)\n\t}\n\n\t// Delete the task\n\t_, err = task.Delete(ctx, containerd.WithProcessKill)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/cmd/container/rename.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"runtime\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/namestore\"\n)\n\n// Rename change container name to a new name\n// containerID is container name, short ID, or long ID\nfunc Rename(ctx context.Context, client *containerd.Client, containerID, newContainerName string,\n\toptions types.ContainerRenameOptions) error {\n\tdataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnamest, err := namestore.New(dataStore, options.GOptions.Namespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\thostst, err := hostsstore.New(dataStore, options.GOptions.Namespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\treturn renameContainer(ctx, found.Container, newContainerName,\n\t\t\t\toptions.GOptions.Namespace, namest, hostst)\n\t\t},\n\t}\n\n\tif n, err := walker.Walk(ctx, containerID); err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", containerID)\n\t}\n\treturn nil\n}\n\nfunc renameContainer(ctx context.Context, container containerd.Container, newName, ns string,\n\tnamst namestore.NameStore, hostst hostsstore.Store) (err error) {\n\tl, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tname := l[labels.Name]\n\n\tid := container.ID()\n\n\tdefer func() {\n\t\t// If we errored, rollback whatever we can\n\t\tif err != nil {\n\t\t\tlbls := map[string]string{\n\t\t\t\tlabels.Name: name,\n\t\t\t}\n\t\t\tnamst.Rename(newName, id, name)\n\t\t\thostst.Update(id, name)\n\t\t\tcontainer.SetLabels(ctx, lbls)\n\t\t}\n\t}()\n\n\tif err = namst.Rename(name, id, newName); err != nil {\n\t\treturn err\n\t}\n\tif runtime.GOOS == \"linux\" {\n\t\tif err = hostst.Update(id, newName); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warn(\"failed to update host networking definitions \" +\n\t\t\t\t\"- if your container is using network 'none', this is expected - otherwise, please report this as a bug\")\n\t\t}\n\t}\n\tlbls := map[string]string{\n\t\tlabels.Name: newName,\n\t}\n\tif _, err = container.SetLabels(ctx, lbls); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/restart.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels/k8slabels\"\n)\n\n// Restart will restart one or more containers.\nfunc Restart(ctx context.Context, client *containerd.Client, containers []string, options types.ContainerRestartOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tinfo, err := found.Container.Info(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"can't get container %s info \", found.Container.ID())\n\t\t\t}\n\t\t\tif _, ok := info.Labels[k8slabels.ContainerType]; ok {\n\t\t\t\tlog.L.Warnf(\"nerdctl does not support restarting container %s created by Kubernetes\", info.ID)\n\t\t\t}\n\t\t\tif err := containerutil.Stop(ctx, found.Container, options.Timeout, options.Signal); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif err := containerutil.Start(ctx, found.Container, false, false, client, \"\", \"\", (*config.Config)(&options.GOption), options.NerdctlCmd, options.NerdctlArgs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = fmt.Fprintln(options.Stdout, found.Req)\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, containers, true)\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_blkio_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n)\n\n// WeightDevice is a structure that holds device:weight pair\ntype WeightDevice struct {\n\tPath   string\n\tWeight uint16\n}\n\nfunc (w *WeightDevice) String() string {\n\treturn fmt.Sprintf(\"%s:%d\", w.Path, w.Weight)\n}\n\n// ThrottleDevice is a structure that holds device:rate_per_second pair\ntype ThrottleDevice struct {\n\tPath string\n\tRate uint64\n}\n\nfunc (t *ThrottleDevice) String() string {\n\treturn fmt.Sprintf(\"%s:%d\", t.Path, t.Rate)\n}\n\nfunc toOCIWeightDevices(weightDevices []*WeightDevice) ([]specs.LinuxWeightDevice, error) {\n\tvar stat unix.Stat_t\n\tblkioWeightDevices := make([]specs.LinuxWeightDevice, 0, len(weightDevices))\n\n\tfor _, weightDevice := range weightDevices {\n\t\tif err := unix.Stat(weightDevice.Path, &stat); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", weightDevice.Path, err)\n\t\t}\n\t\tweight := weightDevice.Weight\n\t\td := specs.LinuxWeightDevice{Weight: &weight}\n\t\t// The type is 32bit on mips.\n\t\td.Major = int64(unix.Major(uint64(stat.Rdev))) //nolint: unconvert\n\t\td.Minor = int64(unix.Minor(uint64(stat.Rdev))) //nolint: unconvert\n\t\tblkioWeightDevices = append(blkioWeightDevices, d)\n\t}\n\n\treturn blkioWeightDevices, nil\n}\n\nfunc toOCIThrottleDevices(devs []*ThrottleDevice) ([]specs.LinuxThrottleDevice, error) {\n\tvar stat unix.Stat_t\n\tthrottleDevices := make([]specs.LinuxThrottleDevice, 0, len(devs))\n\n\tfor _, d := range devs {\n\t\tif err := unix.Stat(d.Path, &stat); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", d.Path, err)\n\t\t}\n\t\td := specs.LinuxThrottleDevice{Rate: d.Rate}\n\t\t// the type is 32bit on mips\n\t\td.Major = int64(unix.Major(uint64(stat.Rdev))) //nolint: unconvert\n\t\td.Minor = int64(unix.Minor(uint64(stat.Rdev))) //nolint: unconvert\n\t\tthrottleDevices = append(throttleDevices, d)\n\t}\n\n\treturn throttleDevices, nil\n}\n\nfunc withBlkioWeight(blkioWeight uint16) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Resources.BlockIO == nil {\n\t\t\ts.Linux.Resources.BlockIO = &specs.LinuxBlockIO{}\n\t\t}\n\t\ts.Linux.Resources.BlockIO.Weight = &blkioWeight\n\t\treturn nil\n\t}\n}\n\nfunc withBlkioWeightDevice(weightDevices []specs.LinuxWeightDevice) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Resources.BlockIO == nil {\n\t\t\ts.Linux.Resources.BlockIO = &specs.LinuxBlockIO{}\n\t\t}\n\t\ts.Linux.Resources.BlockIO.WeightDevice = weightDevices\n\t\treturn nil\n\t}\n}\n\nfunc withBlkioReadBpsDevice(devices []specs.LinuxThrottleDevice) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Resources.BlockIO == nil {\n\t\t\ts.Linux.Resources.BlockIO = &specs.LinuxBlockIO{}\n\t\t}\n\t\ts.Linux.Resources.BlockIO.ThrottleReadBpsDevice = devices\n\t\treturn nil\n\t}\n}\n\nfunc withBlkioWriteBpsDevice(devices []specs.LinuxThrottleDevice) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Resources.BlockIO == nil {\n\t\t\ts.Linux.Resources.BlockIO = &specs.LinuxBlockIO{}\n\t\t}\n\t\ts.Linux.Resources.BlockIO.ThrottleWriteBpsDevice = devices\n\t\treturn nil\n\t}\n}\n\nfunc withBlkioReadIOPSDevice(devices []specs.LinuxThrottleDevice) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Resources.BlockIO == nil {\n\t\t\ts.Linux.Resources.BlockIO = &specs.LinuxBlockIO{}\n\t\t}\n\t\ts.Linux.Resources.BlockIO.ThrottleReadIOPSDevice = devices\n\t\treturn nil\n\t}\n}\n\nfunc withBlkioWriteIOPSDevice(devices []specs.LinuxThrottleDevice) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux.Resources.BlockIO == nil {\n\t\t\ts.Linux.Resources.BlockIO = &specs.LinuxBlockIO{}\n\t\t}\n\t\ts.Linux.Resources.BlockIO.ThrottleWriteIOPSDevice = devices\n\t\treturn nil\n\t}\n}\n\nfunc BlkioOCIOpts(options types.ContainerCreateOptions) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\n\t// Handle BlkioWeight\n\tif options.BlkioWeight != 0 {\n\t\tif !infoutil.BlockIOWeight(options.GOptions.CgroupManager) {\n\t\t\t// blkio weight is not available on cgroup v1 since kernel 5.0.\n\t\t\t// On cgroup v2, blkio weight is implemented using io.weight\n\t\t\tlog.L.Warn(\"kernel support for cgroup blkio weight missing, weight discarded\")\n\t\t} else {\n\t\t\tif options.BlkioWeight < 10 || options.BlkioWeight > 1000 {\n\t\t\t\treturn nil, errors.New(\"range of blkio weight is from 10 to 1000\")\n\t\t\t}\n\t\t\topts = append(opts, withBlkioWeight(options.BlkioWeight))\n\t\t}\n\t}\n\n\t// Handle BlkioWeightDevice\n\tif len(options.BlkioWeightDevice) > 0 {\n\t\tif !infoutil.BlockIOWeightDevice(options.GOptions.CgroupManager) {\n\t\t\t// blkio weight device is not available on cgroup v1 since kernel 5.0.\n\t\t\t// On cgroup v2, blkio weight is implemented using io.weight\n\t\t\tlog.L.Warn(\"kernel support for cgroup blkio weight device missing, weight device discarded\")\n\t\t} else {\n\t\t\tweightDevices, err := validateWeightDevices(options.BlkioWeightDevice)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid weight device: %w\", err)\n\t\t\t}\n\t\t\tlinuxWeightDevices, err := toOCIWeightDevices(weightDevices)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\topts = append(opts, withBlkioWeightDevice(linuxWeightDevices))\n\t\t}\n\t}\n\n\t// Handle BlockIOReadBpsDevice\n\tif len(options.BlkioDeviceReadBps) > 0 {\n\t\tif !infoutil.BlockIOReadBpsDevice(options.GOptions.CgroupManager) {\n\t\t\tlog.L.Warn(\"kernel support for cgroup blkio read bps device missing, read bps device discarded\")\n\t\t} else {\n\t\t\treadBpsDevices, err := validateThrottleBpsDevices(options.BlkioDeviceReadBps)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid read bps device: %w\", err)\n\t\t\t}\n\t\t\tthrottleDevices, err := toOCIThrottleDevices(readBpsDevices)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\topts = append(opts, withBlkioReadBpsDevice(throttleDevices))\n\t\t}\n\t}\n\n\t// Handle BlockIOWriteBpsDevice\n\tif len(options.BlkioDeviceWriteBps) > 0 {\n\t\tif !infoutil.BlockIOWriteBpsDevice(options.GOptions.CgroupManager) {\n\t\t\tlog.L.Warn(\"kernel support for cgroup blkio write bps device missing, write bps device discarded\")\n\t\t} else {\n\t\t\twriteBpsDevices, err := validateThrottleBpsDevices(options.BlkioDeviceWriteBps)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid write bps device: %w\", err)\n\t\t\t}\n\t\t\tthrottleDevices, err := toOCIThrottleDevices(writeBpsDevices)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\topts = append(opts, withBlkioWriteBpsDevice(throttleDevices))\n\t\t}\n\t}\n\n\t// Handle BlockIOReadIopsDevice\n\tif len(options.BlkioDeviceReadIOps) > 0 {\n\t\tif !infoutil.BlockIOReadIOpsDevice(options.GOptions.CgroupManager) {\n\t\t\tlog.L.Warn(\"kernel support for cgroup blkio read iops device missing, read iops device discarded\")\n\t\t} else {\n\t\t\treadIopsDevices, err := validateThrottleIOpsDevices(options.BlkioDeviceReadIOps)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid read iops device: %w\", err)\n\t\t\t}\n\t\t\tthrottleDevices, err := toOCIThrottleDevices(readIopsDevices)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\topts = append(opts, withBlkioReadIOPSDevice(throttleDevices))\n\t\t}\n\t}\n\n\t// Handle BlockIOWriteIopsDevice\n\tif len(options.BlkioDeviceWriteIOps) > 0 {\n\t\tif !infoutil.BlockIOWriteIOpsDevice(options.GOptions.CgroupManager) {\n\t\t\tlog.L.Warn(\"kernel support for cgroup blkio write iops device missing, write iops device discarded\")\n\t\t} else {\n\t\t\twriteIopsDevices, err := validateThrottleIOpsDevices(options.BlkioDeviceWriteIOps)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid write iops device: %w\", err)\n\t\t\t}\n\t\t\tthrottleDevices, err := toOCIThrottleDevices(writeIopsDevices)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\topts = append(opts, withBlkioWriteIOPSDevice(throttleDevices))\n\t\t}\n\t}\n\n\treturn opts, nil\n}\n\n// validateWeightDevices validates an array of device-weight strings\n//\n// from https://github.com/docker/cli/blob/master/opts/weightdevice.go#L15\nfunc validateWeightDevices(vals []string) ([]*WeightDevice, error) {\n\tweightDevices := make([]*WeightDevice, 0, len(vals))\n\tfor _, val := range vals {\n\t\tk, v, ok := strings.Cut(val, \":\")\n\t\tif !ok || k == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"bad format: %s\", val)\n\t\t}\n\t\tif !strings.HasPrefix(k, \"/dev/\") {\n\t\t\treturn nil, fmt.Errorf(\"bad format for device path: %s\", val)\n\t\t}\n\t\tweight, err := strconv.ParseUint(v, 10, 16)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid weight for device: %s\", val)\n\t\t}\n\t\tif weight > 0 && (weight < 10 || weight > 1000) {\n\t\t\treturn nil, fmt.Errorf(\"invalid weight for device: %s\", val)\n\t\t}\n\n\t\tweightDevices = append(weightDevices, &WeightDevice{\n\t\t\tPath:   k,\n\t\t\tWeight: uint16(weight),\n\t\t})\n\t}\n\treturn weightDevices, nil\n}\n\n// validateThrottleBpsDevices validates an array of device-rate strings for bytes per second\n//\n// from https://github.com/docker/cli/blob/master/opts/throttledevice.go#L16\nfunc validateThrottleBpsDevices(vals []string) ([]*ThrottleDevice, error) {\n\tthrottleDevices := make([]*ThrottleDevice, 0, len(vals))\n\tfor _, val := range vals {\n\t\tk, v, ok := strings.Cut(val, \":\")\n\t\tif !ok || k == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"bad format: %s\", val)\n\t\t}\n\n\t\tif !strings.HasPrefix(k, \"/dev/\") {\n\t\t\treturn nil, fmt.Errorf(\"bad format for device path: %s\", val)\n\t\t}\n\t\trate, err := units.RAMInBytes(v)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb\", val)\n\t\t}\n\t\tif rate < 0 {\n\t\t\treturn nil, fmt.Errorf(\"invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb\", val)\n\t\t}\n\n\t\tthrottleDevices = append(throttleDevices, &ThrottleDevice{\n\t\t\tPath: k,\n\t\t\tRate: uint64(rate),\n\t\t})\n\t}\n\treturn throttleDevices, nil\n}\n\n// validateThrottleIOpsDevices validates an array of device-rate strings for IO operations per second\n//\n// from https://github.com/docker/cli/blob/master/opts/throttledevice.go#L40\nfunc validateThrottleIOpsDevices(vals []string) ([]*ThrottleDevice, error) {\n\tthrottleDevices := make([]*ThrottleDevice, 0, len(vals))\n\tfor _, val := range vals {\n\t\tk, v, ok := strings.Cut(val, \":\")\n\t\tif !ok || k == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"bad format: %s\", val)\n\t\t}\n\n\t\tif !strings.HasPrefix(k, \"/dev/\") {\n\t\t\treturn nil, fmt.Errorf(\"bad format for device path: %s\", val)\n\t\t}\n\t\trate, err := strconv.ParseUint(v, 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer\", val)\n\t\t}\n\n\t\tthrottleDevices = append(throttleDevices, &ThrottleDevice{\n\t\t\tPath: k,\n\t\t\tRate: rate,\n\t\t})\n\t}\n\treturn throttleDevices, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_cdi.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\n\t\"tags.cncf.io/container-device-interface/pkg/cdi\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\tcdispec \"github.com/containerd/containerd/v2/pkg/cdi\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n)\n\n// detectGPUVendorFromCDI detects the first available GPU vendor from CDI cache.\n// Returns empty string if no known vendor is found.\nfunc detectGPUVendorFromCDI() string {\n\tcache := cdi.GetDefaultCache()\n\tavailableVendors := cache.ListVendors()\n\tknownGPUVendors := []string{\"nvidia.com\", \"amd.com\"}\n\tfor _, known := range knownGPUVendors {\n\t\tfor _, available := range availableVendors {\n\t\t\tif known == available {\n\t\t\t\treturn known\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// withStaticCDIRegistry inits the CDI registry with given spec dirs\n// and disables auto-refresh.\nfunc withStaticCDIRegistry(cdiSpecDirs []string) oci.SpecOpts {\n\treturn func(ctx context.Context, _ oci.Client, _ *containers.Container, _ *oci.Spec) error {\n\t\t_ = cdi.Configure(\n\t\t\tcdi.WithSpecDirs(cdiSpecDirs...),\n\t\t\tcdi.WithAutoRefresh(false),\n\t\t)\n\t\tif err := cdi.Refresh(); err != nil {\n\t\t\t// We don't consider registry refresh failure a fatal error.\n\t\t\t// For instance, a dynamically generated invalid CDI Spec file for\n\t\t\t// any particular vendor shouldn't prevent injection of devices of\n\t\t\t// different vendors. CDI itself knows better and it will fail the\n\t\t\t// injection if necessary.\n\t\t\tlog.L.Warnf(\"CDI cache refresh failed: %v\", err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// withCDIDevices creates the OCI runtime spec options for injecting CDI devices.\nfunc withCDIDevices(devices ...string) oci.SpecOpts {\n\treturn func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {\n\t\tif len(devices) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\treturn cdispec.WithCDIDevices(devices...)(ctx, client, c, s)\n\t}\n}\n\n// withGPUs creates the OCI runtime spec options for injecting GPUs via CDI.\n// It parses the given GPU options and converts them to CDI device IDs.\n// withCDIDevices is then used to perform the actual injection.\nfunc withGPUs(gpuOpts ...string) oci.SpecOpts {\n\treturn func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {\n\t\tif len(gpuOpts) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tcdiDevices, err := parseGPUOpts(gpuOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn withCDIDevices(cdiDevices...)(ctx, client, c, s)\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_cgroup_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\ntype customMemoryOptions struct {\n\tMemoryReservation *int64\n\tMemorySwappiness  *uint64\n\tdisableOOMKiller  *bool\n}\n\nfunc generateCgroupOpts(id string, options types.ContainerCreateOptions, internalLabels *internalLabels) ([]oci.SpecOpts, error) {\n\tif options.KernelMemory != \"\" {\n\t\tlog.L.Warnf(\"The --kernel-memory flag is no longer supported. This flag is a noop.\")\n\t}\n\n\tif options.Memory == \"\" && options.OomKillDisable {\n\t\tlog.L.Warn(\"Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\")\n\t}\n\n\tif options.GOptions.CgroupManager == \"none\" {\n\t\tif !rootlessutil.IsRootless() {\n\t\t\treturn nil, errors.New(`cgroup-manager \"none\" is only supported for rootless`)\n\t\t}\n\n\t\tif options.CPUs > 0.0 || options.Memory != \"\" || options.MemorySwap != \"\" || options.PidsLimit > 0 {\n\t\t\tlog.L.Warn(`cgroup manager is set to \"none\", discarding resource limit requests. ` +\n\t\t\t\t\"(Hint: enable cgroup v2 with systemd: https://rootlesscontaine.rs/getting-started/common/cgroup2/)\")\n\t\t}\n\t\tif options.CgroupParent != \"\" {\n\t\t\tlog.L.Warnf(`cgroup manager is set to \"none\", ignoring cgroup parent %q`+\n\t\t\t\t\"(Hint: enable cgroup v2 with systemd: https://rootlesscontaine.rs/getting-started/common/cgroup2/)\", options.CgroupParent)\n\t\t}\n\t\treturn []oci.SpecOpts{oci.WithCgroup(\"\")}, nil\n\t}\n\n\tvar opts []oci.SpecOpts // nolint: prealloc\n\tpath, err := generateCgroupPath(id, options.GOptions.CgroupManager, options.CgroupParent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif path != \"\" {\n\t\topts = append(opts, oci.WithCgroup(path))\n\t}\n\n\t// cpus: from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/run/run_unix.go#L187-L193\n\tif options.CPUs > 0.0 {\n\t\tvar (\n\t\t\tperiod = uint64(100000)\n\t\t\tquota  = int64(options.CPUs * 100000.0)\n\t\t)\n\t\topts = append(opts, oci.WithCPUCFS(quota, period))\n\t}\n\n\tif options.CPUShares != 0 {\n\t\topts = append(opts, oci.WithCPUShares(options.CPUShares))\n\t}\n\n\tif options.CPUSetCPUs != \"\" {\n\t\topts = append(opts, oci.WithCPUs(options.CPUSetCPUs))\n\t}\n\tif options.CPUQuota != -1 || options.CPUPeriod != 0 {\n\t\tif options.CPUs > 0.0 {\n\t\t\treturn nil, errors.New(\"cpus and quota/period should be used separately\")\n\t\t}\n\t\topts = append(opts, oci.WithCPUCFS(options.CPUQuota, options.CPUPeriod))\n\t}\n\tif options.CPUSetMems != \"\" {\n\t\topts = append(opts, oci.WithCPUsMems(options.CPUSetMems))\n\t}\n\n\tif options.CPURealtimePeriod != 0 || options.CPURealtimeRuntime != 0 {\n\t\tif !infoutil.CPURealtime(options.GOptions.CgroupManager) {\n\t\t\t// CPU realtime scheduling is not supported in cgroup V2\n\t\t\treturn nil, errors.New(\"kernel does not support CPU real-time scheduler\")\n\t\t}\n\n\t\tif options.CPURealtimePeriod != 0 && options.CPURealtimeRuntime != 0 &&\n\t\t\toptions.CPURealtimeRuntime > options.CPURealtimePeriod {\n\t\t\treturn nil, errors.New(\"cpu real-time runtime cannot be higher than cpu real-time period\")\n\t\t}\n\t}\n\topts = append(opts, oci.WithCPURT(int64(options.CPURealtimeRuntime), options.CPURealtimePeriod))\n\n\tvar mem64 int64\n\tif options.Memory != \"\" {\n\t\tmem64, err = units.RAMInBytes(options.Memory)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse memory bytes %q: %w\", options.Memory, err)\n\t\t}\n\t\topts = append(opts, oci.WithMemoryLimit(uint64(mem64)))\n\t}\n\n\tvar memReserve64 int64\n\tif options.MemoryReservation != \"\" {\n\t\tmemReserve64, err = units.RAMInBytes(options.MemoryReservation)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse memory bytes %q: %w\", options.MemoryReservation, err)\n\t\t}\n\t}\n\tvar memSwap64 int64\n\tif options.MemorySwap != \"\" {\n\t\tif options.MemorySwap == \"-1\" {\n\t\t\tmemSwap64 = -1\n\t\t} else {\n\t\t\tmemSwap64, err = units.RAMInBytes(options.MemorySwap)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to parse memory-swap bytes %q: %w\", options.MemorySwap, err)\n\t\t\t}\n\t\t\tif mem64 > 0 && memSwap64 > 0 && memSwap64 < mem64 {\n\t\t\t\treturn nil, fmt.Errorf(\"minimum memoryswap limit should be larger than memory limit, see usage\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// if `--memory-swap` is unset, the container can use as much swap as the `--memory` setting.\n\t\tmemSwap64 = mem64 * 2\n\t}\n\tif memSwap64 == 0 {\n\t\t// if --memory-swap is set to 0, the setting is ignored, and the value is treated as unset.\n\t\tmemSwap64 = mem64 * 2\n\t}\n\tif memSwap64 != 0 {\n\t\topts = append(opts, oci.WithMemorySwap(memSwap64))\n\t}\n\tif mem64 > 0 && memReserve64 > 0 && mem64 < memReserve64 {\n\t\treturn nil, fmt.Errorf(\"minimum memory limit can not be less than memory reservation limit, see usage\")\n\t}\n\tif options.MemorySwappiness64 > 100 || options.MemorySwappiness64 < -1 {\n\t\treturn nil, fmt.Errorf(\"invalid value: %v, valid memory swappiness range is 0-100\", options.MemorySwappiness64)\n\t}\n\n\tvar customMemRes customMemoryOptions\n\tif memReserve64 >= 0 && options.MemoryReservationChanged {\n\t\tcustomMemRes.MemoryReservation = &memReserve64\n\t}\n\tif options.MemorySwappiness64 >= 0 && options.MemorySwappiness64Changed {\n\t\tmemSwapinessUint64 := uint64(options.MemorySwappiness64)\n\t\tcustomMemRes.MemorySwappiness = &memSwapinessUint64\n\t}\n\tif options.OomKillDisable {\n\t\tcustomMemRes.disableOOMKiller = &options.OomKillDisable\n\t}\n\topts = append(opts, withCustomMemoryResources(customMemRes))\n\n\tif options.PidsLimit > 0 {\n\t\topts = append(opts, oci.WithPidsLimit(options.PidsLimit))\n\t}\n\n\tif len(options.CgroupConf) > 0 && infoutil.CgroupsVersion() == \"1\" {\n\t\treturn nil, errors.New(\"cannot use --cgroup-conf without cgroup v2\")\n\t}\n\n\tunifieds := make(map[string]string)\n\tfor _, unified := range options.CgroupConf {\n\t\tsplitUnified := strings.SplitN(unified, \"=\", 2)\n\t\tif len(splitUnified) < 2 {\n\t\t\treturn nil, errors.New(\"--cgroup-conf must be formatted KEY=VALUE\")\n\t\t}\n\t\tunifieds[splitUnified[0]] = splitUnified[1]\n\t}\n\topts = append(opts, withUnified(unifieds))\n\n\tblkioOpts, err := BlkioOCIOpts(options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, blkioOpts...)\n\n\tswitch options.Cgroupns {\n\tcase \"private\":\n\t\tns := specs.LinuxNamespace{\n\t\t\tType: specs.CgroupNamespace,\n\t\t}\n\t\topts = append(opts, oci.WithLinuxNamespace(ns))\n\tcase \"host\":\n\t\topts = append(opts, oci.WithHostNamespace(specs.CgroupNamespace))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown cgroupns mode %q\", options.Cgroupns)\n\t}\n\n\tfor _, f := range options.Device {\n\t\tdevPath, conPath, mode, err := ParseDevice(f)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse device %q: %w\", f, err)\n\t\t}\n\t\topts = append(opts, oci.WithDevices(devPath, conPath, mode))\n\t\tvar deviceMap dockercompat.DeviceMapping\n\t\tdeviceMap.PathOnHost = devPath\n\t\tdeviceMap.PathInContainer = conPath\n\t\tdeviceMap.CgroupPermissions = mode\n\t\tinternalLabels.deviceMapping = append(internalLabels.deviceMapping, deviceMap)\n\t}\n\n\treturn opts, nil\n}\n\nfunc generateCgroupPath(id, cgroupManager, cgroupParent string) (string, error) {\n\tvar (\n\t\tpath         string\n\t\tusingSystemd = cgroupManager == \"systemd\"\n\t\tslice        = \"system.slice\"\n\t\tscopePrefix  = \":nerdctl:\"\n\t)\n\tif rootlessutil.IsRootlessChild() {\n\t\tslice = \"user.slice\"\n\t}\n\n\tif cgroupParent == \"\" {\n\t\tif usingSystemd {\n\t\t\t// \"slice:prefix:name\"\n\t\t\tpath = slice + scopePrefix + id\n\t\t}\n\t\t// Nothing to do for the non-systemd case if a parent wasn't supplied,\n\t\t// containerd already sets a default cgroup path as /<namespace>/<containerID>\n\t\treturn path, nil\n\t}\n\n\t// If the user asked for a cgroup parent, we will use systemd,\n\t// Docker uses the following:\n\t// parent + prefix (in our case, nerdctl) + containerID.\n\t//\n\t// In the non systemd case, it's just /parent/containerID\n\tif usingSystemd {\n\t\tif len(cgroupParent) <= 6 || !strings.HasSuffix(cgroupParent, \".slice\") {\n\t\t\treturn \"\", errors.New(`cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"`)\n\t\t}\n\t\tpath = cgroupParent + scopePrefix + id\n\t} else {\n\t\tpath = filepath.Join(cgroupParent, id)\n\t}\n\n\treturn path, nil\n}\n\n// ParseDevice parses the give device string into hostDevPath, containerPath and mode(defaults: \"rwm\").\nfunc ParseDevice(s string) (hostDevPath string, containerPath string, mode string, err error) {\n\tmode = \"rwm\"\n\tsplit := strings.Split(s, \":\")\n\tvar containerDevPath string\n\tswitch len(split) {\n\tcase 1: // e.g. \"/dev/sda1\"\n\t\thostDevPath = split[0]\n\t\tcontainerDevPath = hostDevPath\n\tcase 2: // e.g., \"/dev/sda1:rwm\", or \"/dev/sda1:/dev/sda1\n\t\thostDevPath = split[0]\n\t\tif !strings.Contains(split[1], \"/\") {\n\t\t\tcontainerDevPath = hostDevPath\n\t\t\tmode = split[1]\n\t\t} else {\n\t\t\tcontainerDevPath = split[1]\n\t\t}\n\tcase 3: // e.g., \"/dev/sda1:/dev/sda1:rwm\"\n\t\thostDevPath = split[0]\n\t\tcontainerDevPath = split[1]\n\t\tmode = split[2]\n\tdefault:\n\t\treturn \"\", \"\", \"\", errors.New(\"too many `:` symbols\")\n\t}\n\n\tif !filepath.IsAbs(hostDevPath) {\n\t\treturn \"\", \"\", \"\", fmt.Errorf(\"%q is not an absolute path\", hostDevPath)\n\t}\n\n\tif err := validateDeviceMode(mode); err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\treturn hostDevPath, containerDevPath, mode, nil\n}\n\nfunc validateDeviceMode(mode string) error {\n\tfor _, r := range mode {\n\t\tswitch r {\n\t\tcase 'r', 'w', 'm':\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid mode %q: unexpected rune %v\", mode, r)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc withUnified(unified map[string]string) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) (err error) {\n\t\tif unified == nil {\n\t\t\treturn nil\n\t\t}\n\t\ts.Linux.Resources.Unified = make(map[string]string)\n\t\tfor k, v := range unified {\n\t\t\ts.Linux.Resources.Unified[k] = v\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc withCustomMemoryResources(memoryOptions customMemoryOptions) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\tif s.Linux != nil {\n\t\t\tif s.Linux.Resources == nil {\n\t\t\t\ts.Linux.Resources = &specs.LinuxResources{}\n\t\t\t}\n\t\t\tif s.Linux.Resources.Memory == nil {\n\t\t\t\ts.Linux.Resources.Memory = &specs.LinuxMemory{}\n\t\t\t}\n\t\t\tif memoryOptions.disableOOMKiller != nil {\n\t\t\t\ts.Linux.Resources.Memory.DisableOOMKiller = memoryOptions.disableOOMKiller\n\t\t\t}\n\t\t\tif memoryOptions.MemorySwappiness != nil {\n\t\t\t\ts.Linux.Resources.Memory.Swappiness = memoryOptions.MemorySwappiness\n\t\t\t}\n\t\t\tif memoryOptions.MemoryReservation != nil {\n\t\t\t\ts.Linux.Resources.Memory.Reservation = memoryOptions.MemoryReservation\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_gpus.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"encoding/csv\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// GPUReq is a request for GPUs.\ntype GPUReq struct {\n\tCount        int\n\tDeviceIDs    []string\n\tCapabilities []string\n}\n\nfunc (req *GPUReq) toCDIDeviceIDs(vendor string) []string {\n\tvar cdiDeviceIDs []string\n\tfor _, id := range req.normalizeDeviceIDs() {\n\t\tcdiDeviceIDs = append(cdiDeviceIDs, vendor+\"/gpu=\"+id)\n\t}\n\treturn cdiDeviceIDs\n}\n\nfunc (req *GPUReq) normalizeDeviceIDs() []string {\n\tif len(req.DeviceIDs) > 0 {\n\t\treturn req.DeviceIDs\n\t}\n\tif req.Count < 0 {\n\t\treturn []string{\"all\"}\n\t}\n\tvar ids []string\n\tfor i := 0; i < req.Count; i++ {\n\t\tids = append(ids, fmt.Sprintf(\"%d\", i))\n\t}\n\n\treturn ids\n}\n\n// ParseGPUOptCSV parses a GPU option from CSV.\nfunc ParseGPUOptCSV(value string) (*GPUReq, error) {\n\tcsvReader := csv.NewReader(strings.NewReader(value))\n\tfields, err := csvReader.Read()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar (\n\t\treq  GPUReq\n\t\tseen = map[string]struct{}{}\n\t)\n\tfor _, field := range fields {\n\t\tparts := strings.SplitN(field, \"=\", 2)\n\t\tkey := parts[0]\n\t\tif _, ok := seen[key]; ok {\n\t\t\treturn nil, fmt.Errorf(\"gpu request key '%s' can be specified only once\", key)\n\t\t}\n\t\tseen[key] = struct{}{}\n\n\t\tif len(parts) == 1 {\n\t\t\tseen[\"count\"] = struct{}{}\n\t\t\treq.Count, err = parseCount(key)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tvalue := parts[1]\n\t\tswitch key {\n\t\tcase \"driver\":\n\t\t\tif value != \"nvidia\" {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid driver %q: \\\"nvidia\\\" is only supported\", value)\n\t\t\t}\n\t\tcase \"count\":\n\t\t\treq.Count, err = parseCount(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase \"device\":\n\t\t\treq.DeviceIDs = strings.Split(value, \",\")\n\t\tcase \"capabilities\":\n\t\t\treq.Capabilities = strings.Split(value, \",\")\n\t\tcase \"options\":\n\t\t\t// This option is allowed but not used for gpus.\n\t\t\t// Please see also: https://github.com/moby/moby/pull/38828\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected key '%s' in '%s'\", key, field)\n\t\t}\n\t}\n\n\tif req.Count != 0 && len(req.DeviceIDs) > 0 {\n\t\treturn nil, errors.New(\"cannot set both Count and DeviceIDs on device request\")\n\t}\n\tif _, ok := seen[\"count\"]; !ok && len(req.DeviceIDs) == 0 {\n\t\treq.Count = 1\n\t}\n\n\treturn &req, nil\n}\n\nfunc parseGPUOpts(gpuOpts []string) ([]string, error) {\n\tif len(gpuOpts) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tvendor := detectGPUVendorFromCDI()\n\tif vendor == \"\" {\n\t\treturn nil, fmt.Errorf(\"no known GPU vendor found in CDI specs\")\n\t}\n\n\tgpuCDIDevices := []string{}\n\tfor _, gpu := range gpuOpts {\n\t\treq, err := ParseGPUOptCSV(gpu)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgpuCDIDevices = append(gpuCDIDevices, req.toCDIDeviceIDs(vendor)...)\n\t}\n\treturn gpuCDIDevices, nil\n}\n\nfunc parseCount(s string) (int, error) {\n\tif s == \"all\" {\n\t\treturn -1, nil\n\t}\n\ti, err := strconv.Atoi(s)\n\tif err != nil {\n\t\treturn i, fmt.Errorf(\"count must be an integer: %w\", err)\n\t}\n\treturn i, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/moby/sys/userns\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/bypass4netnsutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipcutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// WithoutRunMount returns a SpecOpts that unmounts the default tmpfs on \"/run\"\nfunc WithoutRunMount() func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {\n\treturn oci.WithoutRunMount\n}\n\nfunc setPlatformOptions(ctx context.Context, client *containerd.Client, id, uts string, internalLabels *internalLabels, options types.ContainerCreateOptions) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\topts = append(opts,\n\t\toci.WithDefaultUnixDevices,\n\t\tWithoutRunMount(), // unmount default tmpfs on \"/run\": https://github.com/containerd/nerdctl/issues/157)\n\t)\n\n\topts = append(opts,\n\t\toci.WithMounts([]specs.Mount{\n\t\t\t{Type: \"cgroup\", Source: \"cgroup\", Destination: \"/sys/fs/cgroup\", Options: []string{\"ro\", \"nosuid\", \"noexec\", \"nodev\"}},\n\t\t}))\n\n\tcgOpts, err := generateCgroupOpts(id, options, internalLabels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, cgOpts...)\n\n\tannotations := strutil.ConvertKVStringsToMap(options.Annotations)\n\n\tcapOpts, err := generateCapOpts(\n\t\tstrutil.DedupeStrSlice(options.CapAdd),\n\t\tstrutil.DedupeStrSlice(options.CapDrop))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, capOpts...)\n\tsecurityOptsMaps := strutil.ConvertKVStringsToMap(strutil.DedupeStrSlice(options.SecurityOpt))\n\tsecOpts, err := generateSecurityOpts(options.Privileged, securityOptsMaps)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, secOpts...)\n\n\tb4nnOpts, err := bypass4netnsutil.GenerateBypass4netnsOpts(securityOptsMaps, annotations, id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, b4nnOpts...)\n\n\tulimitOpts, err := generateUlimitsOpts(options.Ulimit)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If without any ulimitOpts, we need to reset the default value from spec\n\t// which has 1024 as file limit. Make this behavior same as containerd/cri.\n\tif len(ulimitOpts) == 0 {\n\t\tulimitOpts = append(ulimitOpts, withRlimits(nil))\n\t}\n\n\topts = append(opts, ulimitOpts...)\n\tif options.Sysctl != nil {\n\t\topts = append(opts, WithSysctls(strutil.ConvertKVStringsToMap(options.Sysctl)))\n\t}\n\n\tif options.RDTClass != \"\" {\n\t\topts = append(opts, oci.WithRdt(options.RDTClass, \"\", \"\"))\n\t}\n\n\tnsOpts, err := generateNamespaceOpts(ctx, client, uts, internalLabels, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts = append(opts, nsOpts...)\n\n\topts, err = setOOMScoreAdj(opts, options.OomScoreAdjChanged, options.OomScoreAdj)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn opts, nil\n}\n\n// generateNamespaceOpts help to validate the namespace options exposed via run and return the correct opts.\nfunc generateNamespaceOpts(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tuts string,\n\tinternalLabels *internalLabels,\n\toptions types.ContainerCreateOptions,\n) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\n\tswitch uts {\n\tcase \"host\":\n\t\topts = append(opts, oci.WithHostNamespace(specs.UTSNamespace))\n\tcase \"\":\n\t\t// Default, do nothing. Every container gets its own UTS ns by default.\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown uts value. valid value(s) are 'host', got: %q\", uts)\n\t}\n\n\tstateDir := internalLabels.stateDir\n\tipcOpts, ipcLabel, err := generateIPCOpts(ctx, client, options.IPC, options.ShmSize, stateDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinternalLabels.ipc = ipcLabel\n\topts = append(opts, ipcOpts...)\n\n\tpidOpts, pidLabel, err := generatePIDOpts(ctx, client, options.Pid)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinternalLabels.pidContainer = pidLabel\n\topts = append(opts, pidOpts...)\n\n\treturn opts, nil\n}\n\nfunc generateIPCOpts(ctx context.Context, client *containerd.Client, ipcFlag string, shmSize string, stateDir string) ([]oci.SpecOpts, string, error) {\n\tipcFlag = strings.ToLower(ipcFlag)\n\n\tipc, err := ipcutil.DetectFlags(ctx, client, stateDir, ipcFlag, shmSize)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tipcLabel, err := ipcutil.EncodeIPCLabel(ipc)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\topts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\treturn opts, ipcLabel, nil\n}\n\nfunc generatePIDOpts(ctx context.Context, client *containerd.Client, pid string) ([]oci.SpecOpts, string, error) {\n\topts := make([]oci.SpecOpts, 0)\n\tpid = strings.ToLower(pid)\n\tvar pidInternalLabel string\n\n\tswitch pid {\n\tcase \"\":\n\t\t// do nothing\n\tcase \"host\":\n\t\topts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))\n\t\tif rootlessutil.IsRootless() {\n\t\t\topts = append(opts, containerutil.WithBindMountHostProcfs)\n\t\t}\n\tdefault: // container:<id|name>\n\t\tparsed := strings.Split(pid, \":\")\n\t\tif len(parsed) < 2 || parsed[0] != \"container\" {\n\t\t\treturn nil, \"\", fmt.Errorf(\"invalid pid namespace. Set --pid=[host|container:<name|id>\")\n\t\t}\n\n\t\tcontainerName := parsed[1]\n\t\twalker := &containerwalker.ContainerWalker{\n\t\t\tClient: client,\n\t\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\t\tif found.MatchCount > 1 {\n\t\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t\t}\n\n\t\t\t\to, err := containerutil.GenerateSharingPIDOpts(ctx, found.Container)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\topts = append(opts, o...)\n\t\t\t\tpidInternalLabel = found.Container.ID()\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t}\n\t\tmatchedCount, err := walker.Walk(ctx, containerName)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t\tif matchedCount < 1 {\n\t\t\treturn nil, \"\", fmt.Errorf(\"no such container: %s\", containerName)\n\t\t}\n\t}\n\n\treturn opts, pidInternalLabel, nil\n}\n\nfunc setOOMScoreAdj(opts []oci.SpecOpts, oomScoreAdjChanged bool, oomScoreAdj int) ([]oci.SpecOpts, error) {\n\tif !oomScoreAdjChanged {\n\t\treturn opts, nil\n\t}\n\t// score=0 means literally zero, not \"unchanged\"\n\tif oomScoreAdj < -1000 || oomScoreAdj > 1000 {\n\t\treturn nil, fmt.Errorf(\"invalid value %d, range for oom score adj is [-1000, 1000]\", oomScoreAdj)\n\t}\n\n\tif userns.RunningInUserNS() {\n\t\t// > The value of /proc/<pid>/oom_score_adj may be reduced no lower than the last value set by a CAP_SYS_RESOURCE process.\n\t\t// > To reduce the value any lower requires CAP_SYS_RESOURCE.\n\t\t// https://github.com/torvalds/linux/blob/v6.0/Documentation/filesystems/proc.rst#31-procpidoom_adj--procpidoom_score_adj--adjust-the-oom-killer-score\n\t\t//\n\t\t// The minimum=100 is from `/proc/$(pgrep -u $(id -u) systemd)/oom_score_adj`\n\t\t// (FIXME: find a more robust way to get the current minimum value)\n\t\tconst minimum = 100\n\t\tif oomScoreAdj < minimum {\n\t\t\tlog.L.Warnf(\"Limiting oom_score_adj (%d -> %d)\", oomScoreAdj, minimum)\n\t\t\toomScoreAdj = minimum\n\t\t}\n\t}\n\n\topts = append(opts, withOOMScoreAdj(oomScoreAdj))\n\treturn opts, nil\n}\n\nfunc withOOMScoreAdj(score int) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Process.OOMScoreAdj = &score\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_mount.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\tsecurejoin \"github.com/cyphar/filepath-securejoin\"\n\t\"github.com/moby/sys/userns\"\n\t\"github.com/opencontainers/image-spec/identity\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/core/leases\"\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/continuity/fs\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// copy from https://github.com/containerd/containerd/blob/v1.6.0-rc.1/pkg/cri/opts/spec_linux.go#L129-L151\nfunc withMounts(mounts []specs.Mount) oci.SpecOpts {\n\treturn func(ctx context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\t// Copy all mounts from default mounts, except for\n\t\t// - mounts overridden by supplied mount;\n\t\t// - all mounts under /dev if a supplied /dev is present.\n\t\tmountSet := make(map[string]struct{})\n\t\tfor _, m := range mounts {\n\t\t\tmountSet[filepath.Clean(m.Destination)] = struct{}{}\n\t\t}\n\n\t\tdefaultMounts := s.Mounts\n\t\ts.Mounts = nil\n\n\t\tfor _, m := range defaultMounts {\n\t\t\tdst := filepath.Clean(m.Destination)\n\t\t\tif _, ok := mountSet[dst]; ok {\n\t\t\t\t// filter out mount overridden by a supplied mount\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, mountDev := mountSet[\"/dev\"]; mountDev && strings.HasPrefix(dst, \"/dev/\") {\n\t\t\t\t// filter out everything under /dev if /dev is a supplied mount\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.Mounts = append(s.Mounts, m)\n\t\t}\n\n\t\ts.Mounts = append(s.Mounts, mounts...)\n\n\t\tsort.Slice(s.Mounts, func(i, j int) bool {\n\t\t\t// Consistent with the less function in Docker.\n\t\t\t// https://github.com/moby/moby/blob/0db417451313474133c5ed62bbf95e2d3c92444d/daemon/volumes.go#L34\n\t\t\treturn strings.Count(filepath.Clean(s.Mounts[i].Destination), string(os.PathSeparator)) < strings.Count(filepath.Clean(s.Mounts[j].Destination), string(os.PathSeparator))\n\t\t})\n\n\t\treturn nil\n\t}\n}\n\n// parseMountFlags parses --volume, --mount and --tmpfs.\nfunc parseMountFlags(volStore volumestore.VolumeStore, options types.ContainerCreateOptions) ([]*mountutil.Processed, error) {\n\tvar parsed []*mountutil.Processed //nolint:prealloc\n\tfor _, v := range strutil.DedupeStrSlice(options.Volume) {\n\t\t// createDir=true for -v option to allow creation of directory on host if not found.\n\t\tx, err := mountutil.ProcessFlagV(v, volStore, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparsed = append(parsed, x)\n\t}\n\n\tfor _, v := range strutil.DedupeStrSlice(options.Tmpfs) {\n\t\tx, err := mountutil.ProcessFlagTmpfs(v)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparsed = append(parsed, x)\n\t}\n\n\tfor _, v := range strutil.DedupeStrSlice(options.Mount) {\n\t\tx, err := mountutil.ProcessFlagMount(v, volStore)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparsed = append(parsed, x)\n\t}\n\n\treturn parsed, nil\n}\n\n// generateMountOpts generates volume-related mount opts.\n// Other mounts such as procfs mount are not handled here.\nfunc generateMountOpts(ctx context.Context, client *containerd.Client, ensuredImage *imgutil.EnsuredImage,\n\tvolStore volumestore.VolumeStore, options types.ContainerCreateOptions) ([]oci.SpecOpts, []string, []*mountutil.Processed, error) {\n\t//nolint:prealloc\n\tvar (\n\t\topts        []oci.SpecOpts\n\t\tanonVolumes []string\n\t\tuserMounts  []specs.Mount\n\t\tmountPoints []*mountutil.Processed\n\t)\n\tmounted := make(map[string]struct{})\n\tvar imageVolumes map[string]struct{}\n\tvar tempDir string\n\tif ensuredImage != nil {\n\t\timageVolumes = ensuredImage.ImageConfig.Volumes\n\n\t\tif err := ensuredImage.Image.Unpack(ctx, options.GOptions.Snapshotter); err != nil {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\"error unpacking image: %w\", err)\n\t\t}\n\n\t\tdiffIDs, err := ensuredImage.Image.RootFS(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\tchainID := identity.ChainID(diffIDs).String()\n\n\t\ts := client.SnapshotService(options.GOptions.Snapshotter)\n\t\ttempDir, err = os.MkdirTemp(\"\", \"initialC\")\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\t// We use Remove here instead of RemoveAll.\n\t\t// The RemoveAll will delete the temp dir and all children it contains.\n\t\t// When the Unmount fails, RemoveAll will incorrectly delete data from the mounted dir\n\t\tdefer os.Remove(tempDir)\n\n\t\t// Add a lease of 1 hour to the view so that it is not garbage collected\n\t\t// Note(gsamfira): should we make this shorter?\n\t\tctx, done, err := client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\"failed to create lease: %w\", err)\n\t\t}\n\t\tdefer done(ctx)\n\n\t\tvar mounts []mount.Mount\n\t\tmounts, err = s.View(ctx, tempDir, chainID)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\n\t\tmm := client.MountManager()\n\n\t\tactive, err := mm.Activate(ctx, tempDir, mounts)\n\t\tif err == nil {\n\t\t\tdefer mm.Deactivate(ctx, tempDir)\n\t\t\tmounts = active.System\n\t\t} else if !errors.Is(err, errdefs.ErrNotImplemented) {\n\t\t\treturn nil, nil, nil, fmt.Errorf(\"failed to activate mounts: %w\", err)\n\t\t}\n\n\t\t// windows has additional steps for mounting see\n\t\t// https://github.com/containerd/containerd/commit/791e175c79930a34cfbb2048fbcaa8493fd2c86b\n\t\tunmounter := func(tempDir string) {\n\t\t\tif uerr := mount.UnmountMounts(mounts, tempDir, 0); uerr != nil {\n\t\t\t\tlog.G(ctx).Debugf(\"Failed to unmount snapshot %q\", tempDir)\n\t\t\t\tif err == nil {\n\t\t\t\t\terr = uerr\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif runtime.GOOS == \"linux\" {\n\t\t\tdefer unmounter(tempDir)\n\t\t\tfor _, m := range mounts {\n\t\t\t\tm := m\n\t\t\t\tif m.Type == \"bind\" && userns.RunningInUserNS() {\n\t\t\t\t\t// For https://github.com/containerd/nerdctl/issues/2056\n\t\t\t\t\tunpriv, err := mountutil.UnprivilegedMountFlags(m.Source)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, nil, err\n\t\t\t\t\t}\n\t\t\t\t\tm.Options = strutil.DedupeStrSlice(append(m.Options, unpriv...))\n\t\t\t\t}\n\t\t\t\tif err := m.Mount(tempDir); err != nil {\n\t\t\t\t\tif rmErr := s.Remove(ctx, tempDir); rmErr != nil && !errdefs.IsNotFound(rmErr) {\n\t\t\t\t\t\treturn nil, nil, nil, rmErr\n\t\t\t\t\t}\n\t\t\t\t\treturn nil, nil, nil, fmt.Errorf(\"failed to mount %+v on %q: %w\", m, tempDir, err)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tdefer unmounter(tempDir)\n\t\t\tif err := mount.All(mounts, tempDir); err != nil {\n\t\t\t\tif err := s.Remove(ctx, tempDir); err != nil && !errdefs.IsNotFound(err) {\n\t\t\t\t\treturn nil, nil, nil, err\n\t\t\t\t}\n\t\t\t\treturn nil, nil, nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tif parsed, err := parseMountFlags(volStore, options); err != nil {\n\t\treturn nil, nil, nil, err\n\t} else if len(parsed) > 0 {\n\t\tociMounts := make([]specs.Mount, len(parsed))\n\t\tfor i, x := range parsed {\n\t\t\tociMounts[i] = x.Mount\n\t\t\tmounted[filepath.Clean(x.Mount.Destination)] = struct{}{}\n\n\t\t\ttarget, err := securejoin.SecureJoin(tempDir, x.Mount.Destination)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, nil, err\n\t\t\t}\n\n\t\t\t// Copying content in AnonymousVolume and namedVolume\n\t\t\tif x.Type == \"volume\" {\n\t\t\t\tif err := copyExistingContents(target, x.Mount.Source); err != nil {\n\t\t\t\t\treturn nil, nil, nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif x.AnonymousVolume != \"\" {\n\t\t\t\tanonVolumes = append(anonVolumes, x.AnonymousVolume)\n\t\t\t}\n\t\t\topts = append(opts, x.Opts...)\n\t\t}\n\t\tuserMounts = append(userMounts, ociMounts...)\n\n\t\t// add parsed user specified bind-mounts/volume/tmpfs to mountPoints\n\t\tmountPoints = append(mountPoints, parsed...)\n\t}\n\n\t// imageVolumes are defined in Dockerfile \"VOLUME\" instruction\n\tfor imgVolRaw := range imageVolumes {\n\t\timgVol := filepath.Clean(imgVolRaw)\n\t\tswitch imgVol {\n\t\tcase \"/\", \"/dev\", \"/sys\", \"proc\":\n\t\t\treturn nil, nil, nil, fmt.Errorf(\"invalid VOLUME: %q\", imgVolRaw)\n\t\t}\n\t\tif _, ok := mounted[imgVol]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tanonVolName := idgen.GenerateID()\n\n\t\tlog.G(ctx).Debugf(\"creating anonymous volume %q, for \\\"VOLUME %s\\\"\",\n\t\t\tanonVolName, imgVolRaw)\n\t\tanonVol, err := volStore.CreateWithoutLock(anonVolName, []string{})\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\n\t\ttarget, err := securejoin.SecureJoin(tempDir, imgVol)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\n\t\t//copying up initial contents of the mount point directory\n\t\tif err := copyExistingContents(target, anonVol.Mountpoint); err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\n\t\tm := specs.Mount{\n\t\t\tType:        \"none\",\n\t\t\tSource:      anonVol.Mountpoint,\n\t\t\tDestination: imgVol,\n\t\t\tOptions:     []string{\"rbind\"},\n\t\t}\n\t\tuserMounts = append(userMounts, m)\n\t\tanonVolumes = append(anonVolumes, anonVolName)\n\n\t\tmountPoint := &mountutil.Processed{\n\t\t\tType:            \"volume\",\n\t\t\tAnonymousVolume: anonVolName,\n\t\t\tMount:           m,\n\t\t}\n\t\tmountPoints = append(mountPoints, mountPoint)\n\t}\n\n\topts = append(opts, withMounts(userMounts))\n\n\tcontainers, err := client.Containers(ctx)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tvfSet := strutil.SliceToSet(options.VolumesFrom)\n\tvar vfMountPoints []dockercompat.MountPoint\n\tvar vfAnonVolumes []string\n\n\tfor _, c := range containers {\n\t\tls, err := c.Labels(ctx)\n\t\tif err != nil {\n\t\t\t// Containerd note: there is no guarantee that the containers we got from the list still exist at this point\n\t\t\t// If that is the case, just ignore and move on\n\t\t\tif errors.Is(err, errdefs.ErrNotFound) {\n\t\t\t\tlog.G(ctx).Debugf(\"container %q is gone - ignoring\", c.ID())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\t_, idMatch := vfSet[c.ID()]\n\t\tnameMatch := false\n\t\tif name, found := ls[labels.Name]; found {\n\t\t\t_, nameMatch = vfSet[name]\n\t\t}\n\n\t\tif idMatch || nameMatch {\n\t\t\tif av, found := ls[labels.AnonymousVolumes]; found {\n\t\t\t\terr = json.Unmarshal([]byte(av), &vfAnonVolumes)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, nil, nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif m, found := ls[labels.Mounts]; found {\n\t\t\t\terr = json.Unmarshal([]byte(m), &vfMountPoints)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, nil, nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tps := processeds(vfMountPoints)\n\t\t\ts, err := c.Spec(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, nil, err\n\t\t\t}\n\t\t\topts = append(opts, withMounts(s.Mounts))\n\t\t\tanonVolumes = append(anonVolumes, vfAnonVolumes...)\n\t\t\tmountPoints = append(mountPoints, ps...)\n\t\t}\n\t}\n\n\treturn opts, anonVolumes, mountPoints, nil\n}\n\n// copyExistingContents copies from the source to the destination and\n// ensures the ownership is appropriately set.\nfunc copyExistingContents(source, destination string) error {\n\tif _, err := os.Stat(source); os.IsNotExist(err) {\n\t\treturn nil\n\t}\n\tdstList, err := os.ReadDir(destination)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(dstList) != 0 {\n\t\tlog.L.Debugf(\"volume at %q is not initially empty, skipping copying\", destination)\n\t\treturn nil\n\t}\n\treturn fs.CopyDir(destination, source)\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_restart.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/runtime/restart\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc checkRestartCapabilities(ctx context.Context, client *containerd.Client, restartFlag string) (bool, error) {\n\tpolicySlice := strings.Split(restartFlag, \":\")\n\tswitch policySlice[0] {\n\tcase \"\", \"no\":\n\t\treturn true, nil\n\t}\n\tres, err := client.IntrospectionService().Plugins(ctx, \"id==restart\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif len(res.Plugins) == 0 {\n\t\treturn false, fmt.Errorf(\"no restart plugin found\")\n\t}\n\trestartPlugin := res.Plugins[0]\n\tcapabilities := restartPlugin.Capabilities\n\tif len(capabilities) == 0 {\n\t\tcapabilities = []string{\"always\"}\n\t}\n\tif !strutil.InStringSlice(capabilities, policySlice[0]) {\n\t\treturn false, fmt.Errorf(\"unsupported restart policy %q, supported policies are: %q\", policySlice[0], capabilities)\n\t}\n\treturn true, nil\n}\n\nfunc generateRestartOpts(ctx context.Context, client *containerd.Client, restartFlag, logURI string, inRun bool) ([]containerd.NewContainerOpts, error) {\n\tif restartFlag == \"\" || restartFlag == \"no\" {\n\t\treturn nil, nil\n\t}\n\tif _, err := checkRestartCapabilities(ctx, client, restartFlag); err != nil {\n\t\treturn nil, err\n\t}\n\n\tpolicy, err := restart.NewPolicy(restartFlag)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdesireStatus := containerd.Created\n\tif inRun {\n\t\tdesireStatus = containerd.Running\n\t}\n\topts := []containerd.NewContainerOpts{restart.WithPolicy(policy), restart.WithStatus(desireStatus)}\n\tif logURI != \"\" {\n\t\topts = append(opts, restart.WithLogURIString(logURI))\n\t}\n\treturn opts, nil\n}\n\n// UpdateContainerRestartPolicyLabel updates the restart policy label of the container.\nfunc UpdateContainerRestartPolicyLabel(ctx context.Context, client *containerd.Client, container containerd.Container, restartFlag string) error {\n\tif _, err := checkRestartCapabilities(ctx, client, restartFlag); err != nil {\n\t\treturn err\n\t}\n\tpolicy, err := restart.NewPolicy(restartFlag)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tupdateOpts := []containerd.UpdateContainerOpts{restart.WithPolicy(policy)}\n\n\tlables, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, statusLabelExist := lables[restart.StatusLabel]\n\tif !statusLabelExist {\n\t\ttask, err := container.Task(ctx, nil)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to get task:%w\", err)\n\t\t}\n\t\tdesireStatus := containerd.Running\n\t\tstatus, err := task.Status(ctx)\n\t\tif err == nil {\n\t\t\tswitch status.Status {\n\t\t\tcase containerd.Stopped:\n\t\t\t\tdesireStatus = containerd.Stopped\n\t\t\tcase containerd.Created:\n\t\t\t\tdesireStatus = containerd.Created\n\t\t\t}\n\t\t}\n\t\tupdateOpts = append(updateOpts, restart.WithStatus(desireStatus))\n\t}\n\n\treturn container.Update(ctx, updateOpts...)\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_runtime.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\truncoptions \"github.com/containerd/containerd/api/types/runc/options\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/containerd/v2/plugins\"\n\t\"github.com/containerd/log\"\n)\n\nfunc generateRuntimeCOpts(cgroupManager, runtimeStr string) ([]containerd.NewContainerOpts, error) {\n\truntime := plugins.RuntimeRuncV2\n\tvar (\n\t\truncOpts    runcoptions.Options\n\t\truntimeOpts interface{} = &runcOpts\n\t)\n\tif cgroupManager == \"systemd\" {\n\t\truncOpts.SystemdCgroup = true\n\t}\n\tif runtimeStr != \"\" {\n\t\tif strings.HasPrefix(runtimeStr, \"io.containerd.\") || runtimeStr == \"wtf.sbk.runj.v1\" {\n\t\t\truntime = runtimeStr\n\t\t\tif !strings.HasPrefix(runtimeStr, \"io.containerd.runc.\") {\n\t\t\t\tif cgroupManager == \"systemd\" {\n\t\t\t\t\tlog.L.Warnf(\"cannot set cgroup manager to %q for runtime %q\", cgroupManager, runtimeStr)\n\t\t\t\t}\n\t\t\t\truntimeOpts = nil\n\t\t\t}\n\t\t} else {\n\t\t\t// runtimeStr may be a runc binary - check that it exists\n\t\t\t// if it does not, treat it as a runtime\n\t\t\tex, err := exec.LookPath(runtimeStr)\n\t\t\tif err != nil {\n\t\t\t\truntime = runtimeStr\n\t\t\t} else {\n\t\t\t\truncOpts.BinaryName = ex\n\t\t\t}\n\t\t}\n\t}\n\to := containerd.WithRuntime(runtime, runtimeOpts)\n\treturn []containerd.NewContainerOpts{o}, nil\n}\n\n// WithSysctls sets the provided sysctls onto the spec\nfunc WithSysctls(sysctls map[string]string) oci.SpecOpts {\n\treturn func(ctx context.Context, client oci.Client, c *containers.Container, s *specs.Spec) error {\n\t\tif s.Linux == nil {\n\t\t\ts.Linux = &specs.Linux{}\n\t\t}\n\t\tif s.Linux.Sysctl == nil {\n\t\t\ts.Linux.Sysctl = make(map[string]string)\n\t\t}\n\t\tfor k, v := range sysctls {\n\t\t\ts.Linux.Sysctl[k] = v\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_security_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/containerd/containerd/v2/contrib/apparmor\"\n\t\"github.com/containerd/containerd/v2/contrib/seccomp\"\n\t\"github.com/containerd/containerd/v2/pkg/cap\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/maputil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nvar privilegedOpts = []oci.SpecOpts{\n\toci.WithPrivileged,\n\toci.WithAllDevicesAllowed,\n\toci.WithHostDevices,\n\toci.WithNewPrivileges,\n}\n\nvar privilegedWithoutDevicesOpts = []oci.SpecOpts{\n\toci.WithPrivileged,\n\toci.WithNewPrivileges,\n}\n\nconst (\n\tsystemPathsUnconfined = \"unconfined\"\n)\n\nfunc generateSecurityOpts(privileged bool, securityOptsMap map[string]string) ([]oci.SpecOpts, error) {\n\tfor k := range securityOptsMap {\n\t\tswitch k {\n\t\tcase \"seccomp\", \"apparmor\", \"no-new-privileges\", \"systempaths\", \"privileged-without-host-devices\", \"writable-cgroups\":\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"unknown security-opt: %q\", k)\n\t\t}\n\t}\n\tvar opts []oci.SpecOpts\n\tif seccompProfile, ok := securityOptsMap[\"seccomp\"]; ok && seccompProfile != defaults.SeccompProfileName {\n\t\tif seccompProfile == \"\" {\n\t\t\treturn nil, errors.New(\"invalid security-opt \\\"seccomp\\\"\")\n\t\t}\n\n\t\tif seccompProfile != \"unconfined\" {\n\t\t\topts = append(opts, seccomp.WithProfile(seccompProfile))\n\t\t}\n\t} else {\n\t\topts = append(opts, seccomp.WithDefaultProfile())\n\t}\n\n\tcanLoadNewAppArmor := apparmorutil.CanLoadNewProfile()\n\tcanApplyExistingProfile := apparmorutil.CanApplyExistingProfile()\n\tif aaProfile, ok := securityOptsMap[\"apparmor\"]; ok {\n\t\tif aaProfile == \"\" {\n\t\t\treturn nil, errors.New(\"invalid security-opt \\\"apparmor\\\"\")\n\t\t}\n\t\tif aaProfile != \"unconfined\" {\n\t\t\tif !canApplyExistingProfile {\n\t\t\t\tlog.L.Warnf(\"the host does not support AppArmor. Ignoring profile %q\", aaProfile)\n\t\t\t} else {\n\t\t\t\topts = append(opts, apparmor.WithProfile(aaProfile))\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif canLoadNewAppArmor {\n\t\t\tif err := apparmor.LoadDefaultProfile(defaults.AppArmorProfileName); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif apparmorutil.CanApplySpecificExistingProfile(defaults.AppArmorProfileName) {\n\t\t\topts = append(opts, apparmor.WithProfile(defaults.AppArmorProfileName))\n\t\t}\n\t}\n\n\tnnp, err := maputil.MapBoolValueAsOpt(securityOptsMap, \"no-new-privileges\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !nnp {\n\t\topts = append(opts, oci.WithNewPrivileges)\n\t}\n\n\tif value, ok := securityOptsMap[\"systempaths\"]; ok && value == systemPathsUnconfined {\n\t\topts = append(opts, oci.WithMaskedPaths(nil))\n\t\topts = append(opts, oci.WithReadonlyPaths(nil))\n\t} else if ok && value != systemPathsUnconfined {\n\t\treturn nil, errors.New(`invalid security-opt \"systempaths=unconfined\"`)\n\t}\n\n\tprivilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, \"privileged-without-host-devices\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif privilegedWithoutHostDevices && !privileged {\n\t\treturn nil, errors.New(\"flag `--security-opt privileged-without-host-devices` can't be used without `--privileged` enabled\")\n\t}\n\tif value, ok := securityOptsMap[\"writable-cgroups\"]; ok {\n\t\twritable, err := strconv.ParseBool(value)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid \\\"writable-cgroups\\\" value: %q\", value)\n\t\t}\n\t\tif writable {\n\t\t\topts = append(opts, oci.WithWriteableCgroupfs)\n\t\t}\n\t}\n\n\tif privileged {\n\t\tif privilegedWithoutHostDevices {\n\t\t\topts = append(opts, privilegedWithoutDevicesOpts...)\n\t\t} else {\n\t\t\topts = append(opts, privilegedOpts...)\n\t\t}\n\t}\n\n\treturn opts, nil\n}\n\nfunc canonicalizeCapName(s string) string {\n\tif s == \"\" {\n\t\treturn \"\"\n\t}\n\ts = strings.ToUpper(s)\n\tif !strings.HasPrefix(s, \"CAP_\") {\n\t\ts = \"CAP_\" + s\n\t}\n\tif !isKnownCapName(s) {\n\t\tlog.L.Warnf(\"unknown capability name %q\", s)\n\t\t// Not a fatal error, because runtime might be aware of this cap\n\t}\n\treturn s\n}\n\nvar (\n\tknownCapNames     map[string]struct{}\n\tknownCapNamesOnce sync.Once\n)\n\nfunc isKnownCapName(s string) bool {\n\tknownCapNamesOnce.Do(func() {\n\t\tknown := cap.Known()\n\t\tknownCapNames = make(map[string]struct{}, len(known))\n\t\tfor _, f := range known {\n\t\t\tknownCapNames[f] = struct{}{}\n\t\t}\n\t})\n\t_, ok := knownCapNames[s]\n\treturn ok\n}\n\nfunc generateCapOpts(capAdd, capDrop []string) ([]oci.SpecOpts, error) {\n\tif len(capAdd) == 0 && len(capDrop) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tvar opts []oci.SpecOpts\n\tif strutil.InStringSlice(capDrop, \"ALL\") {\n\t\topts = append(opts, oci.WithCapabilities(nil))\n\t}\n\n\tif strutil.InStringSlice(capAdd, \"ALL\") {\n\t\topts = append(opts, oci.WithAllCurrentCapabilities)\n\t} else {\n\t\tvar capsAdd []string\n\t\tfor _, c := range capAdd {\n\t\t\tcapsAdd = append(capsAdd, canonicalizeCapName(c))\n\t\t}\n\t\topts = append(opts, oci.WithAddedCapabilities(capsAdd))\n\t}\n\n\tif !strutil.InStringSlice(capDrop, \"ALL\") {\n\t\tvar capsDrop []string\n\t\tfor _, c := range capDrop {\n\t\t\tcapsDrop = append(capsDrop, canonicalizeCapName(c))\n\t\t}\n\t\topts = append(opts, oci.WithDroppedCapabilities(capsDrop))\n\t}\n\treturn opts, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_ulimit_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc generateUlimitsOpts(ulimits []string) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\tulimits = strutil.DedupeStrSlice(ulimits)\n\tif len(ulimits) > 0 {\n\t\tvar rlimits []specs.POSIXRlimit\n\t\tfor _, ulimit := range ulimits {\n\t\t\tl, err := units.ParseUlimit(ulimit)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\trlimits = append(rlimits, specs.POSIXRlimit{\n\t\t\t\tType: \"RLIMIT_\" + strings.ToUpper(l.Name),\n\t\t\t\tHard: uint64(l.Hard),\n\t\t\t\tSoft: uint64(l.Soft),\n\t\t\t})\n\t\t}\n\t\topts = append(opts, withRlimits(rlimits))\n\t}\n\treturn opts, nil\n}\n\nfunc withRlimits(rlimits []specs.POSIXRlimit) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Process.Rlimits = rlimits\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_unix_nolinux.go",
    "content": "//go:build unix && !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\nfunc WithoutRunMount() func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {\n\t// not valid on freebsd\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { return nil }\n}\n\nfunc setPlatformOptions(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tid, uts string,\n\tinternalLabels *internalLabels,\n\toptions types.ContainerCreateOptions,\n) ([]oci.SpecOpts, error) {\n\treturn []oci.SpecOpts{}, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_user.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n)\n\nfunc generateUserOpts(user string) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\tif user != \"\" {\n\t\topts = append(opts, oci.WithUser(user), withResetAdditionalGIDs(), oci.WithAdditionalGIDs(user))\n\t}\n\treturn opts, nil\n}\n\nfunc generateUmaskOpts(umask string) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\n\tif umask != \"\" {\n\t\tdecVal, err := strconv.ParseUint(umask, 8, 32)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid Umask Value:%s\", umask)\n\t\t}\n\t\topts = append(opts, withAdditionalUmask(uint32(decVal)))\n\t}\n\treturn opts, nil\n}\n\nfunc generateGroupsOpts(groups []string) ([]oci.SpecOpts, error) {\n\tvar opts []oci.SpecOpts\n\n\tif len(groups) != 0 {\n\t\topts = append(opts, oci.WithAppendAdditionalGroups(groups...))\n\t}\n\treturn opts, nil\n}\n\nfunc withResetAdditionalGIDs() oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Process.User.AdditionalGids = nil\n\t\treturn nil\n\t}\n}\n\nfunc withAdditionalUmask(umask uint32) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Process.User.Umask = &umask\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/run_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\nconst (\n\t// HostProcessInheritUser inherits permissions of containerd process\n\thostProcessInheritUser = \"microsoft.com/hostprocess-inherit-user\"\n\n\t// HostProcessContainer will launch a host process container\n\thostProcessContainer = \"microsoft.com/hostprocess-container\"\n\tuvmMemorySizeInMB    = \"io.microsoft.virtualmachine.computetopology.memory.sizeinmb\"\n\tuvmCPUCount          = \"io.microsoft.virtualmachine.computetopology.processor.count\"\n)\n\nfunc setPlatformOptions(\n\tctx context.Context,\n\tclient *containerd.Client,\n\tid, uts string,\n\tinternalLabels *internalLabels,\n\toptions types.ContainerCreateOptions,\n) ([]oci.SpecOpts, error) {\n\topts := []oci.SpecOpts{}\n\tif options.CPUs > 0.0 {\n\t\topts = append(opts, oci.WithWindowsCPUCount(uint64(options.CPUs)))\n\t}\n\n\tif options.Memory != \"\" {\n\t\tmem64, err := units.RAMInBytes(options.Memory)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse memory bytes %q: %w\", options.Memory, err)\n\t\t}\n\t\topts = append(opts, oci.WithMemoryLimit(uint64(mem64)))\n\t}\n\n\tswitch options.Isolation {\n\tcase \"hyperv\":\n\t\tif options.Memory != \"\" {\n\t\t\tmem64, err := units.RAMInBytes(options.Memory)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to parse memory bytes %q: %w\", options.Memory, err)\n\t\t\t}\n\t\t\tuvmMemmory := map[string]string{\n\t\t\t\tuvmMemorySizeInMB: fmt.Sprintf(\"%v\", mem64),\n\t\t\t}\n\t\t\topts = append(opts, oci.WithAnnotations(uvmMemmory))\n\t\t}\n\n\t\tif options.CPUs > 0.0 {\n\t\t\tuvmCPU := map[string]string{\n\t\t\t\tuvmCPUCount: fmt.Sprintf(\"%v\", options.CPUs),\n\t\t\t}\n\t\t\topts = append(opts, oci.WithAnnotations(uvmCPU))\n\t\t}\n\t\topts = append(opts, oci.WithWindowsHyperV)\n\tcase \"host\":\n\t\thpAnnotations := map[string]string{\n\t\t\thostProcessContainer: \"true\",\n\t\t}\n\n\t\t// If user is set we will attempt to start container with that user (must be present on the host)\n\t\t// Otherwise we will inherit permissions from the user that the containerd process is running as\n\t\tif options.User == \"\" {\n\t\t\thpAnnotations[hostProcessInheritUser] = \"true\"\n\t\t}\n\n\t\topts = append(opts, oci.WithAnnotations(hpAnnotations))\n\tcase \"process\":\n\t\t// override the default isolation mode in the case where\n\t\t// the containerd default_runtime is set to hyper-v\n\t\topts = append(opts, WithWindowsProcessIsolated())\n\tcase \"default\":\n\t\t// no op\n\t\t// use containerd's default runtime option `default_runtime` set in the config.toml\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown isolation value %q. valid values are 'host', 'process' or 'default'\", options.Isolation)\n\t}\n\n\topts = append(opts,\n\t\toci.WithWindowNetworksAllowUnqualifiedDNSQuery(),\n\t\toci.WithWindowsIgnoreFlushesDuringBoot())\n\n\tfor _, dev := range options.Device {\n\t\tidType, devID, ok := strings.Cut(dev, \"://\")\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"devices must be in the format IDType://ID\")\n\t\t}\n\t\tif idType == \"\" {\n\t\t\treturn nil, errors.New(\"devices must have a non-empty IDType\")\n\t\t}\n\t\topts = append(opts, oci.WithWindowsDevice(idType, devID))\n\t}\n\n\treturn opts, nil\n}\n\nfunc WithWindowsProcessIsolated() oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {\n\t\tif s.Windows == nil {\n\t\t\ts.Windows = &specs.Windows{}\n\t\t}\n\t\tif s.Windows.HyperV != nil {\n\t\t\ts.Windows.HyperV = nil\n\t\t}\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/start.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/checkpointutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Start starts a list of `containers`. If attach is true, it only starts a single container.\nfunc Start(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerStartOptions) error {\n\tif options.Attach && len(reqs) > 1 {\n\t\treturn fmt.Errorf(\"you cannot start and attach multiple containers at once\")\n\t}\n\tif options.Checkpoint != \"\" && len(reqs) > 1 {\n\t\treturn fmt.Errorf(\"you cannot start multiple containers with checkpoint at once\")\n\t}\n\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tvar err error\n\t\t\tvar checkpointDir string\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tif options.Checkpoint != \"\" {\n\t\t\t\tif options.CheckpointDir == \"\" {\n\t\t\t\t\toptions.CheckpointDir = filepath.Join(options.GOptions.DataRoot, \"checkpoints\")\n\t\t\t\t}\n\t\t\t\tcheckpointDir, err = checkpointutil.GetCheckpointDir(options.CheckpointDir, options.Checkpoint, found.Container.ID(), false)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := containerutil.Start(ctx, found.Container, options.Attach, options.Interactive, client, options.DetachKeys, checkpointDir, (*config.Config)(&options.GOptions), options.NerdctlCmd, options.NerdctlArgs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !options.Attach {\n\t\t\t\t_, err := fmt.Fprintln(options.Stdout, found.Req)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, reqs, true)\n}\n"
  },
  {
    "path": "pkg/cmd/container/stats.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\t\"time\"\n\n\teventstypes \"github.com/containerd/containerd/api/events\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/events\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/typeurl/v2\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerinspector\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/eventutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/statsutil\"\n)\n\ntype stats struct {\n\tmu sync.Mutex\n\tcs []*statsutil.Stats\n}\n\n// add is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/stats_helpers.go#L26-L34\nfunc (s *stats) add(cs *statsutil.Stats) bool {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif _, exists := s.isKnownContainer(cs.ID); !exists {\n\t\ts.cs = append(s.cs, cs)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// remove is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/stats_helpers.go#L36-L42\nfunc (s *stats) remove(id string) {\n\ts.mu.Lock()\n\tif i, exists := s.isKnownContainer(id); exists {\n\t\ts.cs = append(s.cs[:i], s.cs[i+1:]...)\n\t}\n\ts.mu.Unlock()\n}\n\n// isKnownContainer is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/stats_helpers.go#L44-L51\nfunc (s *stats) isKnownContainer(cid string) (int, bool) {\n\tfor i, c := range s.cs {\n\t\tif c.ID == cid {\n\t\t\treturn i, true\n\t\t}\n\t}\n\treturn -1, false\n}\n\n// Stats displays a live stream of container(s) resource usage statistics.\nfunc Stats(ctx context.Context, client *containerd.Client, containerIDs []string, options types.ContainerStatsOptions) error {\n\t// NOTE: rootless container does not rely on cgroupv1.\n\t// more details about possible ways to resolve this concern: #223\n\tif rootlessutil.IsRootless() && infoutil.CgroupsVersion() == \"1\" {\n\t\treturn errors.New(\"stats requires cgroup v2 for rootless containers, see https://rootlesscontaine.rs/getting-started/common/cgroup2/\")\n\t}\n\n\tshowAll := len(containerIDs) == 0\n\tcloseChan := make(chan error)\n\n\tvar err error\n\tw := options.Stdout\n\tvar tmpl *template.Template\n\tswitch options.Format {\n\tcase \"\", \"table\":\n\t\tw = tabwriter.NewWriter(options.Stdout, 10, 1, 3, ' ', 0)\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// waitFirst is a WaitGroup to wait first stat data's reach for each container\n\twaitFirst := &sync.WaitGroup{}\n\tcStats := stats{}\n\n\tmonitorContainerEvents := func(started chan<- struct{}, c chan *events.Envelope, closeEventChan chan struct{}) {\n\t\teventsClient := client.EventService()\n\t\teventsCh, errCh := eventsClient.Subscribe(ctx)\n\n\t\t// Whether we successfully subscribed to eventsCh or not, we can now\n\t\t// unblock the main goroutine.\n\t\tclose(started)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-closeEventChan:\n\t\t\t\t// c is closed, so we can stop monitoring events\n\t\t\t\treturn\n\t\t\tcase event := <-eventsCh:\n\t\t\t\tc <- event\n\t\t\tcase err = <-errCh:\n\t\t\t\tcloseChan <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// getContainerList get all existing containers (only used when calling `nerdctl stats` without arguments).\n\tgetContainerList := func() {\n\t\tcontainers, err := client.Containers(ctx)\n\t\tif err != nil {\n\t\t\tcloseChan <- err\n\t\t}\n\n\t\tfor _, c := range containers {\n\t\t\tcStatus := formatter.ContainerStatus(ctx, c)\n\t\t\tif !options.All {\n\t\t\t\tif !strings.HasPrefix(cStatus, \"Up\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\t// if an error occurs when getting labels, the ID alone is sufficient for the stats screen.\n\t\t\tclabels, _ := c.Labels(ctx)\n\t\t\ts := statsutil.NewStats(c.ID(), containerutil.GetContainerName(clabels))\n\t\t\tif cStats.add(s) {\n\t\t\t\twaitFirst.Add(1)\n\t\t\t\tgo collect(ctx, options.GOptions, s, waitFirst, c.ID(), !options.NoStream)\n\t\t\t}\n\t\t}\n\t}\n\n\tif showAll {\n\t\tstarted := make(chan struct{})\n\t\tvar (\n\t\t\tdatacc *eventstypes.ContainerCreate\n\t\t\tdatacd *eventstypes.ContainerDelete\n\t\t)\n\n\t\teh := eventutil.InitEventHandler()\n\t\teh.Handle(\"/containers/create\", func(e events.Envelope) {\n\t\t\tif e.Event != nil {\n\t\t\t\tanydata, err := typeurl.UnmarshalAny(e.Event)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// just skip\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tswitch v := anydata.(type) {\n\t\t\t\tcase *eventstypes.ContainerCreate:\n\t\t\t\t\tdatacc = v\n\t\t\t\tdefault:\n\t\t\t\t\t// just skip\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Load the container to get its ID for retrieving the container name from Labels.\n\t\t\t// if an error occurs, the ID alone is sufficient for the stats screen.\n\t\t\tcontainer, _ := client.LoadContainer(ctx, datacc.ID)\n\t\t\tclabels, _ := container.Labels(ctx)\n\t\t\ts := statsutil.NewStats(datacc.ID, containerutil.GetContainerName(clabels))\n\t\t\tif cStats.add(s) {\n\t\t\t\twaitFirst.Add(1)\n\t\t\t\tgo collect(ctx, options.GOptions, s, waitFirst, datacc.ID, !options.NoStream)\n\t\t\t}\n\t\t})\n\n\t\teh.Handle(\"/containers/delete\", func(e events.Envelope) {\n\t\t\tif e.Event != nil {\n\t\t\t\tanydata, err := typeurl.UnmarshalAny(e.Event)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// just skip\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tswitch v := anydata.(type) {\n\t\t\t\tcase *eventstypes.ContainerDelete:\n\t\t\t\t\tdatacd = v\n\t\t\t\tdefault:\n\t\t\t\t\t// just skip\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tcStats.remove(datacd.ID)\n\t\t})\n\n\t\teventChan := make(chan *events.Envelope)\n\t\tcloseEventChan := make(chan struct{})\n\n\t\tgo eh.Watch(eventChan)\n\t\tgo monitorContainerEvents(started, eventChan, closeEventChan)\n\n\t\tdefer func() {\n\t\t\tcloseEventChan <- struct{}{}\n\t\t\tclose(eventChan)\n\t\t}()\n\n\t\t<-started\n\n\t\t// Start a goroutine to retrieve the initial list of containers stats.\n\t\tgetContainerList()\n\n\t\t// make sure each container get at least one valid stat data\n\t\twaitFirst.Wait()\n\n\t} else {\n\t\twalker := &containerwalker.ContainerWalker{\n\t\t\tClient: client,\n\t\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\t\t// if an error occurs when getting labels, the ID alone is sufficient for the stats screen.\n\t\t\t\tclabels, _ := found.Container.Labels(ctx)\n\t\t\t\ts := statsutil.NewStats(found.Container.ID(), containerutil.GetContainerName(clabels))\n\t\t\t\tif cStats.add(s) {\n\t\t\t\t\twaitFirst.Add(1)\n\t\t\t\t\tgo collect(ctx, options.GOptions, s, waitFirst, found.Container.ID(), !options.NoStream)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t}\n\n\t\tif err := walker.WalkAll(ctx, containerIDs, false); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// make sure each container get at least one valid stat data\n\t\twaitFirst.Wait()\n\n\t}\n\n\tcleanScreen := func() {\n\t\tif !options.NoStream {\n\t\t\tfmt.Fprint(options.Stdout, \"\\033[2J\")\n\t\t\tfmt.Fprint(options.Stdout, \"\\033[H\")\n\t\t}\n\t}\n\n\tticker := time.NewTicker(500 * time.Millisecond)\n\tdefer ticker.Stop()\n\n\t// firstTick is for creating distant CPU readings.\n\t// firstTick stats are not displayed.\n\tfirstTick := true\n\tfor range ticker.C {\n\t\tcleanScreen()\n\t\tccstats := []statsutil.StatsEntry{}\n\t\tcStats.mu.Lock()\n\t\tfor _, c := range cStats.cs {\n\t\t\tif err := c.GetError(); err != nil {\n\t\t\t\tfmt.Fprintf(options.Stderr, \"unable to get stat entry: %s\\n\", err)\n\t\t\t}\n\t\t\tccstats = append(ccstats, c.GetStatistics())\n\t\t}\n\t\tcStats.mu.Unlock()\n\n\t\tif !firstTick {\n\t\t\t// print header for every tick\n\t\t\tif options.Format == \"\" || options.Format == \"table\" {\n\t\t\t\tfmt.Fprintln(w, \"CONTAINER ID\\tNAME\\tCPU %\\tMEM USAGE / LIMIT\\tMEM %\\tNET I/O\\tBLOCK I/O\\tPIDS\")\n\t\t\t}\n\t\t}\n\n\t\tfor _, c := range ccstats {\n\t\t\tif c.ID == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trc := statsutil.RenderEntry(&c, options.NoTrunc)\n\t\t\tif !firstTick {\n\t\t\t\tif tmpl != nil {\n\t\t\t\t\tvar b bytes.Buffer\n\t\t\t\t\tif err := tmpl.Execute(&b, rc); err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif _, err = fmt.Fprintln(options.Stdout, b.String()); err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif _, err := fmt.Fprintf(w, \"%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n\",\n\t\t\t\t\t\trc.ID,\n\t\t\t\t\t\trc.Name,\n\t\t\t\t\t\trc.CPUPerc,\n\t\t\t\t\t\trc.MemUsage,\n\t\t\t\t\t\trc.MemPerc,\n\t\t\t\t\t\trc.NetIO,\n\t\t\t\t\t\trc.BlockIO,\n\t\t\t\t\t\trc.PIDs,\n\t\t\t\t\t); err != nil {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif f, ok := w.(formatter.Flusher); ok {\n\t\t\tf.Flush()\n\t\t}\n\n\t\tif len(cStats.cs) == 0 && !showAll {\n\t\t\tbreak\n\t\t}\n\t\tif options.NoStream && !firstTick {\n\t\t\tbreak\n\t\t}\n\t\tselect {\n\t\tcase err, ok := <-closeChan:\n\t\t\tif ok {\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\t// just skip\n\t\t}\n\t\tfirstTick = false\n\t}\n\n\treturn err\n}\n\nfunc collect(ctx context.Context, globalOptions types.GlobalCommandOptions, s *statsutil.Stats, waitFirst *sync.WaitGroup, id string, noStream bool) {\n\tlog.G(ctx).Debugf(\"collecting stats for %s\", s.ID)\n\tvar (\n\t\tgetFirst = true\n\t\tu        = make(chan error, 1)\n\t)\n\n\tdefer func() {\n\t\t// if error happens and we get nothing of stats, release wait group whatever\n\t\tif getFirst {\n\t\t\tgetFirst = false\n\t\t\twaitFirst.Done()\n\t\t}\n\t}()\n\tclient, ctx, cancel, err := clientutil.NewClient(ctx, globalOptions.Namespace, globalOptions.Address)\n\tif err != nil {\n\t\ts.SetError(err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tcancel()\n\t\tclient.Close()\n\t}()\n\tcontainer, err := client.LoadContainer(ctx, id)\n\tif err != nil {\n\t\ts.SetError(err)\n\t\treturn\n\t}\n\tgo func() {\n\t\tpreviousStats := new(statsutil.ContainerStats)\n\t\tfirstSet := true\n\t\tfor {\n\t\t\t// task is in the for loop to avoid nil task just after Container creation\n\t\t\ttask, err := container.Task(ctx, nil)\n\t\t\tif err != nil {\n\t\t\t\tu <- err\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Sample system CPU usage close to container usage to avoid\n\t\t\t// noise in metric calculations.\n\t\t\tsystemUsage, onlineCPUs, err := getSystemCPUUsage()\n\t\t\tif err != nil {\n\t\t\t\tu <- err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsystemInfo := statsutil.SystemInfo{\n\t\t\t\tOnlineCPUs:  onlineCPUs,\n\t\t\t\tSystemUsage: systemUsage,\n\t\t\t}\n\t\t\tmetric, err := task.Metrics(ctx)\n\t\t\tif err != nil {\n\t\t\t\tu <- err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tanydata, err := typeurl.UnmarshalAny(metric.Data)\n\t\t\tif err != nil {\n\t\t\t\tu <- err\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tnetNS, err := containerinspector.InspectNetNS(ctx, int(task.Pid()))\n\t\t\tif err != nil {\n\t\t\t\tu <- err\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// when (firstSet == true), we only set container stats without rendering stat entry\n\t\t\tstatsEntry, err := setContainerStatsAndRenderStatsEntry(previousStats, firstSet, anydata, int(task.Pid()), netNS.Interfaces, systemInfo)\n\t\t\tif err != nil {\n\t\t\t\tu <- err\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif firstSet {\n\t\t\t\tfirstSet = false\n\t\t\t} else {\n\t\t\t\ts.SetStatistics(statsEntry)\n\t\t\t}\n\t\t\tu <- nil\n\t\t\t// sleep to create distant CPU readings\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}()\n\tfor {\n\t\tselect {\n\t\tcase <-time.After(6 * time.Second):\n\t\t\t// zero out the values if we have not received an update within\n\t\t\t// the specified duration.\n\t\t\ts.SetErrorAndReset(errors.New(\"timeout waiting for stats\"))\n\t\t\t// if this is the first stat you get, release WaitGroup\n\t\t\tif getFirst {\n\t\t\t\tgetFirst = false\n\t\t\t\twaitFirst.Done()\n\t\t\t}\n\t\tcase err := <-u:\n\t\t\tif err != nil {\n\t\t\t\tif !errdefs.IsNotFound(err) && !strings.Contains(err.Error(), \"no such file or directory\") {\n\t\t\t\t\ts.SetError(err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\t// if this is the first stat you get, release WaitGroup\n\t\t\tif getFirst {\n\t\t\t\tgetFirst = false\n\t\t\t\twaitFirst.Done()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/container/stats_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/vishvananda/netlink\"\n\t\"github.com/vishvananda/netns\"\n\n\tv1 \"github.com/containerd/cgroups/v3/cgroup1/stats\"\n\tv2 \"github.com/containerd/cgroups/v3/cgroup2/stats\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/statsutil\"\n)\n\nconst (\n\t// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and\n\t// on Linux it's a constant which is safe to be hard coded,\n\t// so we can avoid using cgo here. For details, see:\n\t// https://github.com/containerd/cgroups/pull/12\n\tclockTicksPerSecond  = 100\n\tnanoSecondsPerSecond = 1e9\n)\n\n//nolint:nakedret\nfunc setContainerStatsAndRenderStatsEntry(previousStats *statsutil.ContainerStats, firstSet bool, anydata interface{}, pid int, interfaces []native.NetInterface, systemInfo statsutil.SystemInfo) (statsEntry statsutil.StatsEntry, err error) {\n\n\tvar (\n\t\tdata  *v1.Metrics\n\t\tdata2 *v2.Metrics\n\t)\n\n\tswitch v := anydata.(type) {\n\tcase *v1.Metrics:\n\t\tdata = v\n\tcase *v2.Metrics:\n\t\tdata2 = v\n\tdefault:\n\t\terr = errors.New(\"cannot convert metric data to cgroups.Metrics\")\n\t\treturn\n\t}\n\n\tvar nlinks []netlink.Link\n\n\tif !firstSet {\n\t\tvar (\n\t\t\tnlink    netlink.Link\n\t\t\tnlHandle *netlink.Handle\n\t\t\tns       netns.NsHandle\n\t\t)\n\n\t\tns, err = netns.GetFromPid(pid)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to retrieve the statistics in netns %s: %v\", ns, err)\n\t\t\treturn\n\t\t}\n\t\tdefer func() {\n\t\t\terr = ns.Close()\n\t\t}()\n\n\t\tnlHandle, err = netlink.NewHandleAt(ns)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to retrieve the statistics in netns %s: %v\", ns, err)\n\t\t\treturn\n\t\t}\n\t\tdefer nlHandle.Close()\n\n\t\tfor _, v := range interfaces {\n\t\t\tnlink, err = nlHandle.LinkByIndex(v.Index)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to retrieve the statistics for %s in netns %s: %v\", v.Name, ns, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t//exclude inactive interface\n\t\t\tif nlink.Attrs().Flags&net.FlagUp != 0 {\n\n\t\t\t\t//exclude loopback interface\n\t\t\t\tif nlink.Attrs().Flags&net.FlagLoopback != 0 || strings.HasPrefix(nlink.Attrs().Name, \"lo\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnlinks = append(nlinks, nlink)\n\t\t\t}\n\t\t}\n\t}\n\n\tif data != nil {\n\t\tif !firstSet {\n\t\t\tstatsEntry, err = statsutil.SetCgroupStatsFields(previousStats, data, nlinks, systemInfo)\n\t\t}\n\t\tpreviousStats.CgroupCPU = data.CPU.Usage.Total\n\t\tpreviousStats.CgroupSystem = systemInfo.SystemUsage\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t} else if data2 != nil {\n\t\tif !firstSet {\n\t\t\tstatsEntry, err = statsutil.SetCgroup2StatsFields(previousStats, data2, nlinks)\n\t\t}\n\t\tpreviousStats.Cgroup2CPU = data2.CPU.UsageUsec * 1000\n\t\tpreviousStats.Cgroup2System = data2.CPU.SystemUsec * 1000\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tpreviousStats.Time = time.Now()\n\n\treturn\n}\n\n// getSystemCPUUsage reads the system's CPU usage from /proc/stat and returns\n// the total CPU usage in nanoseconds and the number of CPUs.\nfunc getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, _ error) {\n\tf, err := os.Open(\"/proc/stat\")\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\tdefer f.Close()\n\n\treturn readSystemCPUUsage(f)\n}\n\n// readSystemCPUUsage parses CPU usage information from a reader providing\n// /proc/stat format data. It returns the total CPU usage in nanoseconds\n// and the number of CPUs. More:\n// https://github.com/moby/moby/blob/26db31fdab628a2345ed8f179e575099384166a9/daemon/stats_unix.go#L327-L368\nfunc readSystemCPUUsage(r io.Reader) (cpuUsage uint64, cpuNum uint32, _ error) {\n\trdr := bufio.NewReaderSize(r, 1024)\n\n\tfor {\n\t\tdata, isPartial, err := rdr.ReadLine()\n\n\t\tif err != nil {\n\t\t\treturn 0, 0, fmt.Errorf(\"error scanning /proc/stat file: %w\", err)\n\t\t}\n\t\t// Assume all cpu* records are at the start of the file, like glibc:\n\t\t// https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135\n\t\tif isPartial || len(data) < 4 {\n\t\t\tbreak\n\t\t}\n\t\tline := string(data)\n\t\tif line[:3] != \"cpu\" {\n\t\t\tbreak\n\t\t}\n\t\tif line[3] == ' ' {\n\t\t\tparts := strings.Fields(line)\n\t\t\tif len(parts) < 8 {\n\t\t\t\treturn 0, 0, fmt.Errorf(\"invalid number of cpu fields\")\n\t\t\t}\n\t\t\tvar totalClockTicks uint64\n\t\t\tfor _, i := range parts[1:8] {\n\t\t\t\tv, err := strconv.ParseUint(i, 10, 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, 0, fmt.Errorf(\"unable to convert value %s to int: %w\", i, err)\n\t\t\t\t}\n\t\t\t\ttotalClockTicks += v\n\t\t\t}\n\t\t\tcpuUsage = (totalClockTicks * nanoSecondsPerSecond) / clockTicksPerSecond\n\t\t}\n\t\tif '0' <= line[3] && line[3] <= '9' {\n\t\t\tcpuNum++\n\t\t}\n\t}\n\treturn cpuUsage, cpuNum, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/stats_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/statsutil\"\n)\n\nfunc setContainerStatsAndRenderStatsEntry(previousStats *statsutil.ContainerStats, firstSet bool, anydata interface{}, pid int, interfaces []native.NetInterface, systemInfo statsutil.SystemInfo) (statsutil.StatsEntry, error) {\n\treturn statsutil.StatsEntry{}, nil\n}\n\n// getSystemCPUUsage reads the system's CPU usage from /proc/stat and returns\n// the total CPU usage in nanoseconds and the number of CPUs.\nfunc getSystemCPUUsage() (uint64, uint32, error) {\n\treturn 0, 0, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/stop.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Stop stops a list of containers specified by `reqs`.\nfunc Stop(ctx context.Context, client *containerd.Client, reqs []string, opt types.ContainerStopOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tif err := cleanupNetwork(ctx, found.Container, opt.GOptions); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unable to cleanup network for container: %s\", found.Req)\n\t\t\t}\n\t\t\tif err := healthcheck.RemoveTransientHealthCheckFiles(ctx, found.Container); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unable to cleanup healthcheck timer for container: %s: %w\", found.Req, err)\n\t\t\t}\n\t\t\tif err := containerutil.Stop(ctx, found.Container, opt.Timeout, opt.Signal); err != nil {\n\t\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\t\tfmt.Fprintf(opt.Stderr, \"No such container: %s\\n\", found.Req)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err := fmt.Fprintln(opt.Stdout, found.Req)\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, reqs, true)\n}\n"
  },
  {
    "path": "pkg/cmd/container/top.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from:\n   - https://github.com/moby/moby/blob/v20.10.6/api/types/container/container_top.go\n   - https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go\n   Copyright (C) The Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.6/NOTICE\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// ContainerTopOKBody is from https://github.com/moby/moby/blob/v20.10.6/api/types/container/container_top.go\n//\n// ContainerTopOKBody OK response to ContainerTop operation\ntype ContainerTopOKBody struct { //nolint:revive\n\n\t// Each process running in the container, where each is process\n\t// is an array of values corresponding to the titles.\n\t//\n\t// Required: true\n\tProcesses [][]string `json:\"Processes\"`\n\n\t// The ps column titles\n\t// Required: true\n\tTitles []string `json:\"Titles\"`\n}\n\n// Top performs the equivalent of running `top` inside of container(s)\nfunc Top(ctx context.Context, client *containerd.Client, containers []string, opt types.ContainerTopOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\treturn containerTop(ctx, opt.Stdout, client, found.Container.ID(), opt.PsArgs)\n\t\t},\n\t}\n\n\tn, err := walker.Walk(ctx, containers[0])\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"no such container %s\", containers[0])\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/top_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from:\n   - https://github.com/moby/moby/blob/v20.10.6/api/types/container/container_top.go\n   - https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go\n   Copyright (C) The Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.6/NOTICE\n*/\n\npackage container\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n)\n\n// containerTop was inspired from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L133-L189\n//\n// ContainerTop lists the processes running inside of the given\n// container by calling ps with the given args, or with the flags\n// \"-ef\" if no args are given.  An error is returned if the container\n// is not found, or is not running, or if there are any problems\n// running ps, or parsing the output.\n// procList *ContainerTopOKBody\nfunc containerTop(ctx context.Context, stdio io.Writer, client *containerd.Client, id string, psArgs string) error {\n\tif psArgs == \"\" {\n\t\tpsArgs = \"-ef\"\n\t}\n\n\tif err := validatePSArgs(psArgs); err != nil {\n\t\treturn err\n\t}\n\n\tcontainer, err := client.LoadContainer(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status.Status != containerd.Running {\n\t\treturn nil\n\t}\n\n\t//TO DO handle restarting case: wait for container to restart and then launch top command\n\n\tprocs, err := task.Pids(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpsList := make([]uint32, 0, len(procs))\n\tfor _, ps := range procs {\n\t\tpsList = append(psList, ps.Pid)\n\t}\n\n\targs := strings.Split(psArgs, \" \")\n\tpids := psPidsArg(psList)\n\toutput, err := exec.Command(\"ps\", append(args, pids)...).Output()\n\tif err != nil {\n\t\t// some ps options (such as f) can't be used together with q,\n\t\t// so retry without it\n\t\toutput, err = exec.Command(\"ps\", args...).Output()\n\t\tif err != nil {\n\t\t\tif ee, ok := err.(*exec.ExitError); ok {\n\t\t\t\t// first line of stderr shows why ps failed\n\t\t\t\tline := bytes.SplitN(ee.Stderr, []byte{'\\n'}, 2)\n\t\t\t\tif len(line) > 0 && len(line[0]) > 0 {\n\t\t\t\t\treturn errors.New(string(line[0]))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\tprocList, err := parsePSOutput(output, psList)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw := tabwriter.NewWriter(stdio, 20, 1, 3, ' ', 0)\n\tfmt.Fprintln(w, strings.Join(procList.Titles, \"\\t\"))\n\n\tfor _, proc := range procList.Processes {\n\t\tfmt.Fprintln(w, strings.Join(proc, \"\\t\"))\n\t}\n\n\treturn w.Flush()\n}\n\n// appendProcess2ProcList is from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L49-L55\nfunc appendProcess2ProcList(procList *ContainerTopOKBody, fields []string) {\n\t// Make sure number of fields equals number of header titles\n\t// merging \"overhanging\" fields\n\tprocess := fields[:len(procList.Titles)-1]\n\tprocess = append(process, strings.Join(fields[len(procList.Titles)-1:], \" \"))\n\tprocList.Processes = append(procList.Processes, process)\n}\n\n// psPidsArg is from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L119-L131\n//\n// psPidsArg converts a slice of PIDs to a string consisting\n// of comma-separated list of PIDs prepended by \"-q\".\n// For example, psPidsArg([]uint32{1,2,3}) returns \"-q1,2,3\".\nfunc psPidsArg(pids []uint32) string {\n\tb := []byte{'-', 'q'}\n\tfor i, p := range pids {\n\t\tb = strconv.AppendUint(b, uint64(p), 10)\n\t\tif i < len(pids)-1 {\n\t\t\tb = append(b, ',')\n\t\t}\n\t}\n\treturn string(b)\n}\n\n// validatePSArgs is from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L19-L35\nfunc validatePSArgs(psArgs string) error {\n\t// NOTE: \\\\s does not detect unicode whitespaces.\n\t// So we use fieldsASCII instead of strings.Fields in parsePSOutput.\n\t// See https://github.com/docker/docker/pull/24358\n\t// nolint: gosimple\n\tre := regexp.MustCompile(`\\s+(\\S*)=\\s*(PID\\S*)`)\n\tfor _, group := range re.FindAllStringSubmatch(psArgs, -1) {\n\t\tif len(group) >= 3 {\n\t\t\tk := group[1]\n\t\t\tv := group[2]\n\t\t\tif k != \"pid\" {\n\t\t\t\treturn fmt.Errorf(\"specifying \\\"%s=%s\\\" is not allowed\", k, v)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// fieldsASCII is from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L37-L47\n//\n// fieldsASCII is similar to strings.Fields but only allows ASCII whitespaces\nfunc fieldsASCII(s string) []string {\n\tfn := func(r rune) bool {\n\t\tswitch r {\n\t\tcase '\\t', '\\n', '\\f', '\\r', ' ':\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\treturn strings.FieldsFunc(s, fn)\n}\n\n// hasPid is from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L57-L64\nfunc hasPid(procs []uint32, pid int) bool {\n\tfor _, p := range procs {\n\t\tif int(p) == pid {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// parsePSOutput is from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L66-L117\nfunc parsePSOutput(output []byte, procs []uint32) (*ContainerTopOKBody, error) {\n\tprocList := &ContainerTopOKBody{}\n\n\tlines := strings.Split(string(output), \"\\n\")\n\tprocList.Titles = fieldsASCII(lines[0])\n\n\tpidIndex := -1\n\tfor i, name := range procList.Titles {\n\t\tif name == \"PID\" {\n\t\t\tpidIndex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif pidIndex == -1 {\n\t\treturn nil, fmt.Errorf(\"couldn't find PID field in ps output\")\n\t}\n\n\t// loop through the output and extract the PID from each line\n\t// fixing #30580, be able to display thread line also when \"m\" option used\n\t// in \"docker top\" client command\n\tpreContainedPidFlag := false\n\tfor _, line := range lines[1:] {\n\t\tif len(line) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tfields := fieldsASCII(line)\n\n\t\tvar (\n\t\t\tp   int\n\t\t\terr error\n\t\t)\n\n\t\tif fields[pidIndex] == \"-\" {\n\t\t\tif preContainedPidFlag {\n\t\t\t\tappendProcess2ProcList(procList, fields)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tp, err = strconv.Atoi(fields[pidIndex])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unexpected pid '%s': %w\", fields[pidIndex], err)\n\t\t}\n\n\t\tif hasPid(procs, p) {\n\t\t\tpreContainedPidFlag = true\n\t\t\tappendProcess2ProcList(procList, fields)\n\t\t\tcontinue\n\t\t}\n\t\tpreContainedPidFlag = false\n\t}\n\treturn procList, nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/top_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\t\"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options\"\n\t\"github.com/docker/go-units\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/typeurl/v2\"\n)\n\n// containerTop was inspired from https://github.com/moby/moby/blob/master/daemon/top_windows.go\n//\n// ContainerTop lists the processes running inside of the given\n// container. An error is returned if the container\n// is not found, or is not running.\nfunc containerTop(ctx context.Context, stdio io.Writer, client *containerd.Client, id string, psArgs string) error {\n\tcontainer, err := client.LoadContainer(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tprocesses, err := task.Pids(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tprocList := &ContainerTopOKBody{}\n\tprocList.Titles = []string{\"Name\", \"PID\", \"CPU\", \"Private Working Set\"}\n\n\tfor _, j := range processes {\n\t\tvar info options.ProcessDetails\n\t\terr = typeurl.UnmarshalTo(j.Info, &info)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\td := time.Duration((info.KernelTime_100Ns + info.UserTime_100Ns) * 100) // Combined time in nanoseconds\n\t\tprocList.Processes = append(procList.Processes, []string{\n\t\t\tinfo.ImageName,\n\t\t\tfmt.Sprint(info.ProcessID),\n\t\t\tfmt.Sprintf(\"%02d:%02d:%02d.%03d\", int(d.Hours()), int(d.Minutes())%60, int(d.Seconds())%60, int(d.Nanoseconds()/1000000)%1000),\n\t\t\tunits.HumanSize(float64(info.MemoryWorkingSetPrivateBytes))})\n\n\t}\n\n\tw := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)\n\tfmt.Fprintln(w, strings.Join(procList.Titles, \"\\t\"))\n\n\tfor _, proc := range procList.Processes {\n\t\tfmt.Fprintln(w, strings.Join(proc, \"\\t\"))\n\t}\n\tw.Flush()\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/container/unpause.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Unpause unpauses all containers specified by `reqs`.\nfunc Unpause(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerUnpauseOptions) error {\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tif err := containerutil.Unpause(ctx, client, found.Container.ID(), (*config.Config)(&options.GOptions), options.NerdctlCmd, options.NerdctlArgs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t_, err := fmt.Fprintln(options.Stdout, found.Req)\n\t\t\treturn err\n\t\t},\n\t}\n\n\treturn walker.WalkAll(ctx, reqs, true)\n}\n"
  },
  {
    "path": "pkg/cmd/container/wait.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n)\n\n// Wait blocks until all the containers specified by reqs have stopped, then print their exit codes.\nfunc Wait(ctx context.Context, client *containerd.Client, reqs []string, options types.ContainerWaitOptions) error {\n\tvar containers []containerd.Container\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t}\n\t\t\tcontainers = append(containers, found.Container)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\t// check if all containers from `reqs` exist\n\tif err := walker.WalkAll(ctx, reqs, false); err != nil {\n\t\treturn err\n\t}\n\n\tvar errs []error\n\tw := options.Stdout\n\tfor _, container := range containers {\n\t\tif waitErr := waitContainer(ctx, w, container); waitErr != nil {\n\t\t\terrs = append(errs, waitErr)\n\t\t}\n\t}\n\treturn errors.Join(errs...)\n}\n\nfunc waitContainer(ctx context.Context, w io.Writer, container containerd.Container) error {\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatusC, err := task.Wait(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatus := <-statusC\n\tcode, _, err := status.Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Fprintln(w, code)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/convert.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/klauspost/compress/zstd\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\toverlaybdconvert \"github.com/containerd/accelerated-container-image/pkg/convertor\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n\t\"github.com/containerd/containerd/v2/core/images/converter/uncompress\"\n\t\"github.com/containerd/log\"\n\tnydusconvert \"github.com/containerd/nydus-snapshotter/pkg/converter\"\n\t\"github.com/containerd/platforms\"\n\t\"github.com/containerd/stargz-snapshotter/estargz\"\n\testargzconvert \"github.com/containerd/stargz-snapshotter/nativeconverter/estargz\"\n\testargzexternaltocconvert \"github.com/containerd/stargz-snapshotter/nativeconverter/estargz/externaltoc\"\n\tzstdchunkedconvert \"github.com/containerd/stargz-snapshotter/nativeconverter/zstdchunked\"\n\t\"github.com/containerd/stargz-snapshotter/recorder\"\n\testargzdecompressutil \"github.com/containerd/stargz-snapshotter/util/decompressutil\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\tconverterutil \"github.com/containerd/nerdctl/v2/pkg/imgutil/converter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/jobs\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/snapshotterutil\"\n)\n\nfunc Convert(ctx context.Context, client *containerd.Client, srcRawRef, targetRawRef string, options types.ImageConvertOptions) error {\n\tvar (\n\t\tconvertOpts = []converter.Opt{}\n\t)\n\tif srcRawRef == \"\" || targetRawRef == \"\" {\n\t\treturn errors.New(\"src and target image need to be specified\")\n\t}\n\n\tparsedReference, err := referenceutil.Parse(srcRawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsrcRef := parsedReference.String()\n\n\tparsedReference, err = referenceutil.Parse(targetRawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttargetRef := parsedReference.String()\n\n\tplatMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platforms)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconvertOpts = append(convertOpts, converter.WithPlatform(platMC))\n\n\t// Ensure all the layers are here: https://github.com/containerd/nerdctl/issues/3425\n\terr = EnsureAllContent(ctx, client, srcRef, platMC, options.GOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\testargz := options.Estargz\n\tzstd := options.Zstd\n\tzstdchunked := options.ZstdChunked\n\toverlaybd := options.Overlaybd\n\tnydus := options.Nydus\n\tsoci := options.Soci\n\tvar finalize func(ctx context.Context, cs content.Store, ref string, desc *ocispec.Descriptor) (*images.Image, error)\n\tif estargz || zstd || zstdchunked || overlaybd || nydus || soci {\n\t\tconvertCount := 0\n\t\tif estargz {\n\t\t\tconvertCount++\n\t\t}\n\t\tif zstd {\n\t\t\tconvertCount++\n\t\t}\n\t\tif zstdchunked {\n\t\t\tconvertCount++\n\t\t}\n\t\tif overlaybd {\n\t\t\tconvertCount++\n\t\t}\n\t\tif nydus {\n\t\t\tconvertCount++\n\t\t}\n\t\tif soci {\n\t\t\tconvertCount++\n\t\t}\n\n\t\tif convertCount > 1 {\n\t\t\treturn errors.New(\"options --estargz, --zstdchunked, --overlaybd, --nydus and --soci lead to conflict, only one of them can be used\")\n\t\t}\n\n\t\tvar convertFunc converter.ConvertFunc\n\t\tvar convertType string\n\t\tswitch {\n\t\tcase estargz:\n\t\t\tconvertFunc, finalize, err = getESGZConverter(options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconvertType = \"estargz\"\n\t\tcase zstd:\n\t\t\tconvertFunc, err = getZstdConverter(options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconvertType = \"zstd\"\n\t\tcase zstdchunked:\n\t\t\tconvertFunc, err = getZstdchunkedConverter(options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconvertType = \"zstdchunked\"\n\t\tcase overlaybd:\n\t\t\tobdOpts, err := getOBDConvertOpts(options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tobdOpts = append(obdOpts, overlaybdconvert.WithClient(client))\n\t\t\tobdOpts = append(obdOpts, overlaybdconvert.WithImageRef(srcRef))\n\t\t\tconvertFunc = overlaybdconvert.IndexConvertFunc(obdOpts...)\n\t\t\tconvertOpts = append(convertOpts, converter.WithIndexConvertFunc(convertFunc))\n\t\t\tconvertType = \"overlaybd\"\n\t\tcase nydus:\n\t\t\tnydusOpts, err := getNydusConvertOpts(options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconvertHooks := converter.ConvertHooks{\n\t\t\t\tPostConvertHook: nydusconvert.ConvertHookFunc(nydusconvert.MergeOption{\n\t\t\t\t\tWorkDir:          nydusOpts.WorkDir,\n\t\t\t\t\tBuilderPath:      nydusOpts.BuilderPath,\n\t\t\t\t\tFsVersion:        nydusOpts.FsVersion,\n\t\t\t\t\tChunkDictPath:    nydusOpts.ChunkDictPath,\n\t\t\t\t\tPrefetchPatterns: nydusOpts.PrefetchPatterns,\n\t\t\t\t\tOCI:              true,\n\t\t\t\t}),\n\t\t\t}\n\t\t\tconvertOpts = append(convertOpts, converter.WithIndexConvertFunc(\n\t\t\t\tconverter.IndexConvertFuncWithHook(\n\t\t\t\t\tnydusconvert.LayerConvertFunc(*nydusOpts),\n\t\t\t\t\ttrue,\n\t\t\t\t\tplatMC,\n\t\t\t\t\tconvertHooks,\n\t\t\t\t)),\n\t\t\t)\n\t\t\tconvertType = \"nydus\"\n\t\tcase soci:\n\t\t\t// Convert image to SOCI format\n\t\t\tconvertedRef, err := snapshotterutil.ConvertSociIndexV2(ctx, client, srcRef, targetRef, options.GOptions, options.SociOptions)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to convert image to SOCI format: %w\", err)\n\t\t\t}\n\t\t\tres := converterutil.ConvertedImageInfo{\n\t\t\t\tImage: convertedRef,\n\t\t\t}\n\t\t\treturn printConvertedImage(options.Stdout, options, res)\n\t\t}\n\n\t\tif convertType != \"overlaybd\" {\n\t\t\tconvertOpts = append(convertOpts, converter.WithLayerConvertFunc(convertFunc))\n\t\t}\n\t\tif !options.Oci {\n\t\t\tif nydus || overlaybd {\n\t\t\t\tlog.G(ctx).Warnf(\"option --%s should be used in conjunction with --oci, forcibly enabling on oci mediatype for %s conversion\", convertType, convertType)\n\t\t\t} else {\n\t\t\t\tlog.G(ctx).Warnf(\"option --%s should be used in conjunction with --oci\", convertType)\n\t\t\t}\n\t\t}\n\t\tif options.Uncompress {\n\t\t\treturn fmt.Errorf(\"option --%s conflicts with --uncompress\", convertType)\n\t\t}\n\t}\n\n\tif options.Uncompress {\n\t\tconvertOpts = append(convertOpts, converter.WithLayerConvertFunc(uncompress.LayerConvertFunc))\n\t}\n\n\tif options.Oci {\n\t\tconvertOpts = append(convertOpts, converter.WithDockerToOCI(true))\n\t}\n\n\tif options.ProgressOutput != nil {\n\t\tongoing := jobs.New(targetRef)\n\t\tif err := addDescriptorsToJobs(ctx, client, srcRef, platMC, ongoing); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tprogressCtx, cancelProgress := context.WithCancel(ctx)\n\t\tprogressDone := make(chan struct{})\n\n\t\tgo func() {\n\t\t\tjobs.ShowProgress(progressCtx, ongoing, client.ContentStore(), options.ProgressOutput)\n\t\t\tclose(progressDone)\n\t\t}()\n\t\tdefer func() {\n\t\t\tcancelProgress()\n\t\t\t<-progressDone\n\t\t}()\n\t}\n\n\t// converter.Convert() gains the lease by itself\n\tnewImg, err := converterutil.Convert(ctx, client, targetRef, srcRef, convertOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tres := converterutil.ConvertedImageInfo{\n\t\tImage: newImg.Name + \"@\" + newImg.Target.Digest.String(),\n\t}\n\tif finalize != nil {\n\t\tctx, done, err := client.WithLease(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer done(ctx)\n\t\tnewI, err := finalize(ctx, client.ContentStore(), targetRef, &newImg.Target)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tis := client.ImageService()\n\t\tfinimg, err := is.Update(ctx, *newI)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tres.ExtraImages = append(res.ExtraImages, finimg.Name+\"@\"+finimg.Target.Digest.String())\n\t}\n\treturn printConvertedImage(options.Stdout, options, res)\n}\n\nfunc getESGZConverter(options types.ImageConvertOptions) (convertFunc converter.ConvertFunc, finalize func(ctx context.Context, cs content.Store, ref string, desc *ocispec.Descriptor) (*images.Image, error), _ error) {\n\tif options.EstargzExternalToc && !options.GOptions.Experimental {\n\t\treturn nil, nil, fmt.Errorf(\"estargz-external-toc requires experimental mode to be enabled\")\n\t}\n\tif options.EstargzKeepDiffID && !options.GOptions.Experimental {\n\t\treturn nil, nil, fmt.Errorf(\"option --estargz-keep-diff-id must be specified with --estargz-external-toc\")\n\t}\n\tif options.EstargzExternalToc {\n\t\tif !options.EstargzKeepDiffID {\n\t\t\tesgzOpts, err := getESGZConvertOpts(options)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t\tconvertFunc, finalize = estargzexternaltocconvert.LayerConvertFunc(esgzOpts, options.EstargzCompressionLevel)\n\t\t} else {\n\t\t\tconvertFunc, finalize = estargzexternaltocconvert.LayerConvertLossLessFunc(estargzexternaltocconvert.LayerConvertLossLessConfig{\n\t\t\t\tCompressionLevel: options.EstargzCompressionLevel,\n\t\t\t\tChunkSize:        options.EstargzChunkSize,\n\t\t\t\tMinChunkSize:     options.EstargzMinChunkSize,\n\t\t\t})\n\t\t}\n\t} else {\n\t\tesgzOpts, err := getESGZConvertOpts(options)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tconvertFunc = estargzconvert.LayerConvertFunc(esgzOpts...)\n\t}\n\treturn convertFunc, finalize, nil\n}\n\nfunc addDescriptorsToJobs(ctx context.Context, client *containerd.Client, srcRef string, platMC platforms.MatchComparer, ongoing *jobs.Jobs) error {\n\timageService := client.ImageService()\n\timg, err := imageService.Get(ctx, srcRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprovider := containerdutil.NewProvider(client)\n\thandler := images.ChildrenHandler(provider)\n\tif platMC != nil {\n\t\thandler = images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\t\tif desc.Platform != nil && !platMC.Match(*desc.Platform) {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\treturn images.Children(ctx, provider, desc)\n\t\t})\n\t}\n\n\treturn images.Walk(ctx, images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\tongoing.Add(desc)\n\t\treturn handler(ctx, desc)\n\t}), img.Target)\n}\n\nfunc getESGZConvertOpts(options types.ImageConvertOptions) ([]estargz.Option, error) {\n\n\tesgzOpts := []estargz.Option{\n\t\testargz.WithCompressionLevel(options.EstargzCompressionLevel),\n\t\testargz.WithChunkSize(options.EstargzChunkSize),\n\t\testargz.WithMinChunkSize(options.EstargzMinChunkSize),\n\t}\n\n\tif options.EstargzRecordIn != \"\" {\n\t\tif !options.GOptions.Experimental {\n\t\t\treturn nil, fmt.Errorf(\"estargz-record-in requires experimental mode to be enabled\")\n\t\t}\n\n\t\tlog.L.Warn(\"--estargz-record-in flag is experimental and subject to change\")\n\t\tpaths, err := readPathsFromRecordFile(options.EstargzRecordIn)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tesgzOpts = append(esgzOpts, estargz.WithPrioritizedFiles(paths))\n\t\tvar ignored []string\n\t\tesgzOpts = append(esgzOpts, estargz.WithAllowPrioritizeNotFound(&ignored))\n\t}\n\tif options.EstargzGzipHelper != \"\" {\n\t\tgzipHelperFunc, err := estargzdecompressutil.GetGzipHelperFunc(options.EstargzGzipHelper)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tesgzOpts = append(esgzOpts, estargz.WithGzipHelperFunc(gzipHelperFunc))\n\t}\n\treturn esgzOpts, nil\n}\n\nfunc getZstdConverter(options types.ImageConvertOptions) (converter.ConvertFunc, error) {\n\treturn converterutil.ZstdLayerConvertFunc(options)\n}\n\nfunc getZstdchunkedConverter(options types.ImageConvertOptions) (converter.ConvertFunc, error) {\n\n\tesgzOpts := []estargz.Option{\n\t\testargz.WithChunkSize(options.ZstdChunkedChunkSize),\n\t}\n\n\tif options.ZstdChunkedRecordIn != \"\" {\n\t\tif !options.GOptions.Experimental {\n\t\t\treturn nil, fmt.Errorf(\"zstdchunked-record-in requires experimental mode to be enabled\")\n\t\t}\n\n\t\tlog.L.Warn(\"--zstdchunked-record-in flag is experimental and subject to change\")\n\t\tpaths, err := readPathsFromRecordFile(options.ZstdChunkedRecordIn)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tesgzOpts = append(esgzOpts, estargz.WithPrioritizedFiles(paths))\n\t\tvar ignored []string\n\t\tesgzOpts = append(esgzOpts, estargz.WithAllowPrioritizeNotFound(&ignored))\n\t}\n\treturn zstdchunkedconvert.LayerConvertFuncWithCompressionLevel(zstd.EncoderLevelFromZstd(options.ZstdChunkedCompressionLevel), esgzOpts...), nil\n}\n\nfunc getNydusConvertOpts(options types.ImageConvertOptions) (*nydusconvert.PackOption, error) {\n\tworkDir := options.NydusWorkDir\n\tif workDir == \"\" {\n\t\tvar err error\n\t\tworkDir, err = clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn &nydusconvert.PackOption{\n\t\tBuilderPath: options.NydusBuilderPath,\n\t\t// the path will finally be used is <NERDCTL_DATA_ROOT>/nydus-converter-<hash>,\n\t\t// for example: /var/lib/nerdctl/1935db59/nydus-converter-3269662176/,\n\t\t// and it will be deleted after the conversion\n\t\tWorkDir:          workDir,\n\t\tPrefetchPatterns: options.NydusPrefetchPatterns,\n\t\tCompressor:       options.NydusCompressor,\n\t\tFsVersion:        \"6\",\n\t}, nil\n}\n\nfunc getOBDConvertOpts(options types.ImageConvertOptions) ([]overlaybdconvert.Option, error) {\n\tobdOpts := []overlaybdconvert.Option{\n\t\toverlaybdconvert.WithFsType(options.OverlayFsType),\n\t\toverlaybdconvert.WithDbstr(options.OverlaydbDBStr),\n\t}\n\treturn obdOpts, nil\n}\n\nfunc readPathsFromRecordFile(filename string) ([]string, error) {\n\tr, err := os.Open(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer r.Close()\n\tdec := json.NewDecoder(r)\n\tvar paths []string\n\tadded := make(map[string]struct{})\n\tfor dec.More() {\n\t\tvar e recorder.Entry\n\t\tif err := dec.Decode(&e); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif _, ok := added[e.Path]; !ok {\n\t\t\tpaths = append(paths, e.Path)\n\t\t\tadded[e.Path] = struct{}{}\n\t\t}\n\t}\n\treturn paths, nil\n}\n\nfunc printConvertedImage(stdout io.Writer, options types.ImageConvertOptions, img converterutil.ConvertedImageInfo) error {\n\tswitch options.Format {\n\tcase \"json\":\n\t\tb, err := json.MarshalIndent(img, \"\", \"    \")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintln(stdout, string(b))\n\tdefault:\n\t\tfor i, e := range img.ExtraImages {\n\t\t\telems := strings.SplitN(e, \"@\", 2)\n\t\t\tif len(elems) < 2 {\n\t\t\t\tlog.L.Errorf(\"extra reference %q doesn't contain digest\", e)\n\t\t\t} else {\n\t\t\t\tlog.L.Infof(\"Extra image(%d) %s\", i, elems[0])\n\t\t\t}\n\t\t}\n\t\telems := strings.SplitN(img.Image, \"@\", 2)\n\t\tif len(elems) < 2 {\n\t\t\tlog.L.Errorf(\"reference %q doesn't contain digest\", img.Image)\n\t\t} else {\n\t\t\tfmt.Fprintln(stdout, elems[1])\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/crypt.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n\t\"github.com/containerd/imgcrypt/v2/images/encryption\"\n\t\"github.com/containerd/imgcrypt/v2/images/encryption/parsehelpers\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\tnerdconverter \"github.com/containerd/nerdctl/v2/pkg/imgutil/converter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc Crypt(ctx context.Context, client *containerd.Client, srcRawRef, targetRawRef string, encrypt bool, options types.ImageCryptOptions) error {\n\tvar convertOpts = []converter.Opt{}\n\tif srcRawRef == \"\" || targetRawRef == \"\" {\n\t\treturn errors.New(\"src and target image need to be specified\")\n\t}\n\n\tparsedRerefence, err := referenceutil.Parse(srcRawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsrcRef := parsedRerefence.String()\n\n\tparsedRerefence, err = referenceutil.Parse(targetRawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttargetRef := parsedRerefence.String()\n\n\tplatMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platforms)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconvertOpts = append(convertOpts, converter.WithPlatform(platMC))\n\n\timgcryptFlags, err := parseImgcryptFlags(options, encrypt)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsrcImg, err := client.ImageService().Get(ctx, srcRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlayerDescs, err := platformutil.LayerDescs(ctx, client.ContentStore(), srcImg.Target, platMC)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlayerFilter := func(desc ocispec.Descriptor) bool {\n\t\treturn true\n\t}\n\tvar convertFunc converter.ConvertFunc\n\tif encrypt {\n\t\tcc, err := parsehelpers.CreateCryptoConfig(imgcryptFlags, layerDescs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconvertFunc = encryption.GetImageEncryptConverter(&cc, layerFilter)\n\t} else {\n\t\tcc, err := parsehelpers.CreateDecryptCryptoConfig(imgcryptFlags, layerDescs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconvertFunc = encryption.GetImageDecryptConverter(&cc, layerFilter)\n\t}\n\t// we have to compose the DefaultIndexConvertFunc here to match platforms.\n\tconvertFunc = composeConvertFunc(converter.DefaultIndexConvertFunc(nil, false, platMC), convertFunc)\n\tconvertOpts = append(convertOpts, converter.WithIndexConvertFunc(convertFunc))\n\n\t// converter.Convert() gains the lease by itself\n\tnewImg, err := nerdconverter.Convert(ctx, client, targetRef, srcRef, convertOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Fprintln(options.Stdout, newImg.Target.Digest.String())\n\treturn nil\n}\n\n// parseImgcryptFlags corresponds to https://github.com/containerd/imgcrypt/blob/v1.1.2/cmd/ctr/commands/images/crypt_utils.go#L244-L252\nfunc parseImgcryptFlags(options types.ImageCryptOptions, encrypt bool) (parsehelpers.EncArgs, error) {\n\tvar a parsehelpers.EncArgs\n\n\ta.GPGHomedir = options.GpgHomeDir\n\ta.GPGVersion = options.GpgVersion\n\ta.Key = options.Keys\n\tif encrypt {\n\t\ta.Recipient = options.Recipients\n\t\tif len(a.Recipient) == 0 {\n\t\t\treturn a, errors.New(\"at least one recipient must be specified (e.g., --recipient=jwe:mypubkey.pem)\")\n\t\t}\n\t}\n\t// While --recipient can be specified only for `nerdctl image encrypt`,\n\t// --dec-recipient can be specified for both `nerdctl image encrypt` and `nerdctl image decrypt`.\n\ta.DecRecipient = options.DecRecipients\n\treturn a, nil\n}\n\nfunc composeConvertFunc(a, b converter.ConvertFunc) converter.ConvertFunc {\n\treturn func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {\n\t\tnewDesc, err := a(ctx, cs, desc)\n\t\tif err != nil {\n\t\t\treturn newDesc, err\n\t\t}\n\t\tif newDesc == nil {\n\t\t\treturn b(ctx, cs, desc)\n\t\t}\n\t\treturn b(ctx, cs, *newDesc)\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/image/ensure.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/fetch\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc EnsureAllContent(ctx context.Context, client *containerd.Client, srcName string, platMC platforms.MatchComparer, options types.GlobalCommandOptions) error {\n\t// Get the image from the srcName\n\timageService := client.ImageService()\n\timg, err := imageService.Get(ctx, srcName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprovider := containerdutil.NewProvider(client)\n\tsnapshotter := containerdutil.SnapshotService(client, options.Snapshotter)\n\t// Read the image\n\timagesList, _ := read(ctx, provider, snapshotter, img.Target)\n\t// Iterate through the list\n\tfor _, i := range imagesList {\n\t\tif platMC.Match(i.platform) {\n\t\t\terr = ensureOne(ctx, client, srcName, img.Target, i.platform, options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc ensureOne(ctx context.Context, client *containerd.Client, rawRef string, target ocispec.Descriptor, platform ocispec.Platform, options types.GlobalCommandOptions) error {\n\tparsedReference, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpltf := []ocispec.Platform{platform}\n\tplatformComparer := platformutil.NewMatchComparerFromOCISpecPlatformSlice(pltf)\n\n\t_, _, _, missing, err := images.Check(ctx, client.ContentStore(), target, platformComparer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(missing) > 0 {\n\t\t// Get a resolver\n\t\tvar dOpts []dockerconfigresolver.Opt\n\t\tif options.InsecureRegistry {\n\t\t\tlog.G(ctx).Warnf(\"skipping verifying HTTPS certs for %q\", parsedReference.Domain)\n\t\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t\t}\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(options.HostsDir))\n\t\tresolver, err := dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconfig := &fetch.Config{\n\t\t\tResolver:       resolver,\n\t\t\tRemoteOpts:     []containerd.RemoteOpt{},\n\t\t\tPlatforms:      pltf,\n\t\t\tProgressOutput: os.Stderr,\n\t\t}\n\n\t\terr = fetch.Fetch(ctx, client, rawRef, config)\n\n\t\tif err != nil {\n\t\t\t// In some circumstance (e.g. people just use 80 port to support pure http), the error will contain message like \"dial tcp <port>: connection refused\".\n\t\t\tif !errors.Is(err, http.ErrSchemeMismatch) && !errutil.IsErrConnectionRefused(err) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif options.InsecureRegistry {\n\t\t\t\tlog.G(ctx).WithError(err).Warnf(\"server %q does not seem to support HTTPS, falling back to plain HTTP\", parsedReference.Domain)\n\t\t\t\tdOpts = append(dOpts, dockerconfigresolver.WithPlainHTTP(true))\n\t\t\t\tresolver, err = dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tconfig.Resolver = resolver\n\t\t\t\treturn fetch.Fetch(ctx, client, rawRef, config)\n\t\t\t}\n\t\t\tlog.G(ctx).WithError(err).Errorf(\"server %q does not seem to support HTTPS\", parsedReference.Domain)\n\t\t\tlog.G(ctx).Info(\"Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)\")\n\t\t}\n\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/import.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"archive/tar\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\tpathpkg \"path\"\n\t\"time\"\n\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/leases\"\n\t\"github.com/containerd/containerd/v2/core/transfer\"\n\ttarchive \"github.com/containerd/containerd/v2/core/transfer/archive\"\n\ttransferimage \"github.com/containerd/containerd/v2/core/transfer/image\"\n\t\"github.com/containerd/containerd/v2/pkg/archive/compression\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/transferutil\"\n)\n\nfunc Import(ctx context.Context, client *containerd.Client, options types.ImageImportOptions) (string, error) {\n\tprefix := options.Reference\n\tif prefix == \"\" {\n\t\tprefix = fmt.Sprintf(\"import-%s\", time.Now().Format(\"2006-01-02\"))\n\t}\n\n\tparsed, err := referenceutil.Parse(prefix)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\timageName := parsed.String()\n\n\tplatUnpack := platforms.DefaultSpec()\n\tvar opts []transferimage.StoreOpt\n\tif options.Platform != \"\" {\n\t\tp, err := platforms.Parse(options.Platform)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tplatUnpack = p\n\t\topts = append(opts, transferimage.WithPlatforms(platUnpack))\n\t}\n\n\topts = append(opts, transferimage.WithUnpack(platUnpack, options.GOptions.Snapshotter))\n\topts = append(opts, transferimage.WithDigestRef(imageName, true, true))\n\n\tvar r io.ReadCloser\n\tif rc, ok := options.Stdin.(io.ReadCloser); ok {\n\t\tr = rc\n\t} else {\n\t\tr = io.NopCloser(options.Stdin)\n\t}\n\n\tconverted, cleanup, err := ensureOCIArchive(ctx, client, r, options, prefix)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer cleanup()\n\n\tiis := tarchive.NewImageImportStream(converted, \"\")\n\tis := transferimage.NewStore(\"\", opts...)\n\n\tpf, done := transferutil.ProgressHandler(ctx, os.Stderr)\n\tdefer done()\n\n\tif err := client.Transfer(ctx, iis, is, transfer.WithProgress(pf)); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn imageName, nil\n}\n\nfunc ensureOCIArchive(ctx context.Context, client *containerd.Client, r io.ReadCloser, options types.ImageImportOptions, prefix string) (io.ReadCloser, func(), error) {\n\tbuf := &bytes.Buffer{}\n\ttee := io.TeeReader(r, buf)\n\n\tisStandardArchive, err := detectStandardImageArchive(tee)\n\tif err != nil {\n\t\treturn nil, func() {}, err\n\t}\n\n\tcombined := io.NopCloser(io.MultiReader(buf, r))\n\tif isStandardArchive {\n\t\treturn combined, func() { r.Close() }, nil\n\t}\n\n\tconverted, err := convertRootfsToOCIArchive(ctx, client, combined, options, prefix)\n\tif err != nil {\n\t\tr.Close()\n\t\treturn nil, func() {}, err\n\t}\n\n\tcleanup := func() {\n\t\tr.Close()\n\t\tif converted != nil {\n\t\t\tconverted.Close()\n\t\t}\n\t}\n\n\treturn converted, cleanup, nil\n}\n\nfunc detectStandardImageArchive(r io.Reader) (bool, error) {\n\ttr := tar.NewReader(r)\n\tconst maxHeadersToCheck = 10\n\n\tfor i := 0; i < maxHeadersToCheck; i++ {\n\t\thdr, err := tr.Next()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tname := pathpkg.Clean(hdr.Name)\n\t\tif name == \"manifest.json\" || name == ocispec.ImageLayoutFile {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc convertRootfsToOCIArchive(ctx context.Context, client *containerd.Client, r io.ReadCloser, options types.ImageImportOptions, prefix string) (io.ReadCloser, error) {\n\tdefer r.Close()\n\n\tctx, done, err := client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer done(ctx)\n\n\tdecomp, err := compression.DecompressStream(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer decomp.Close()\n\n\tcs := client.ContentStore()\n\tref := randomRef(\"import-layer-\")\n\tw, err := content.OpenWriter(ctx, cs, content.WithRef(ref))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer w.Close()\n\n\tif err := w.Truncate(0); err != nil {\n\t\treturn nil, err\n\t}\n\n\tlayerDigest, diffID, layerSize, err := compressAndWriteLayer(ctx, w, decomp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\timgConfig, configDigest, err := buildImageConfig(diffID, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlayerContent, err := readLayerContent(ctx, cs, layerDigest, layerSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buildDockerArchive(imgConfig, configDigest, layerContent, layerDigest, prefix)\n}\n\nfunc compressAndWriteLayer(ctx context.Context, w content.Writer, r io.Reader) (digest.Digest, digest.Digest, int64, error) {\n\tdigester := digest.Canonical.Digester()\n\ttee := io.TeeReader(r, digester.Hash())\n\tpr, pw := io.Pipe()\n\tgz := gzip.NewWriter(pw)\n\n\tdoneCh := make(chan error, 1)\n\tgo func() {\n\t\tdefer func() {\n\t\t\t_ = gz.Close()\n\t\t}()\n\n\t\tif _, err := io.Copy(gz, tee); err != nil {\n\t\t\tdoneCh <- err\n\t\t\t_ = pw.CloseWithError(err)\n\t\t\treturn\n\t\t}\n\t\tif err := gz.Close(); err != nil {\n\t\t\tdoneCh <- err\n\t\t\t_ = pw.CloseWithError(err)\n\t\t\treturn\n\t\t}\n\t\tdoneCh <- pw.Close()\n\t}()\n\n\tn, err := io.Copy(w, pr)\n\tif err != nil {\n\t\treturn \"\", \"\", 0, err\n\t}\n\tif err := <-doneCh; err != nil {\n\t\treturn \"\", \"\", 0, err\n\t}\n\n\tdiffID := digester.Digest()\n\tlabels := map[string]string{\n\t\t\"containerd.io/uncompressed\": diffID.String(),\n\t}\n\tif err := w.Commit(ctx, n, \"\", content.WithLabels(labels)); err != nil && !errdefs.IsAlreadyExists(err) {\n\t\treturn \"\", \"\", 0, err\n\t}\n\n\treturn w.Digest(), diffID, n, nil\n}\n\nfunc buildImageConfig(diffID digest.Digest, options types.ImageImportOptions) ([]byte, digest.Digest, error) {\n\tociplat := platforms.DefaultSpec()\n\tif options.Platform != \"\" {\n\t\tif p, err := platforms.Parse(options.Platform); err == nil {\n\t\t\tociplat = p\n\t\t}\n\t}\n\n\tcreated := time.Now().UTC()\n\timgConfig := ocispec.Image{\n\t\tPlatform: ocispec.Platform{\n\t\t\tArchitecture: ociplat.Architecture,\n\t\t\tOS:           ociplat.OS,\n\t\t\tOSVersion:    ociplat.OSVersion,\n\t\t\tVariant:      ociplat.Variant,\n\t\t},\n\t\tCreated: &created,\n\t\tConfig:  ocispec.ImageConfig{},\n\t\tRootFS: ocispec.RootFS{\n\t\t\tType:    \"layers\",\n\t\t\tDiffIDs: []digest.Digest{diffID},\n\t\t},\n\t\tHistory: []ocispec.History{{\n\t\t\tCreated: &created,\n\t\t\tComment: options.Message,\n\t\t}},\n\t}\n\n\tconfigJSON, err := json.Marshal(imgConfig)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\treturn configJSON, digest.FromBytes(configJSON), nil\n}\n\nfunc readLayerContent(ctx context.Context, cs content.Store, layerDigest digest.Digest, size int64) ([]byte, error) {\n\tra, err := cs.ReaderAt(ctx, ocispec.Descriptor{Digest: layerDigest, Size: size})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer ra.Close()\n\n\tlayerContent := make([]byte, size)\n\tif _, err := ra.ReadAt(layerContent, 0); err != nil {\n\t\treturn nil, err\n\t}\n\treturn layerContent, nil\n}\n\nfunc buildDockerArchive(configJSON []byte, configDigest digest.Digest, layerContent []byte, layerDigest digest.Digest, prefix string) (io.ReadCloser, error) {\n\tlayerFileName := layerDigest.Encoded() + \".tar.gz\"\n\tconfigFileName := configDigest.Encoded() + \".json\"\n\n\tvar repoTags []string\n\tif parsed, err := referenceutil.Parse(prefix); err == nil && parsed.String() != \"\" {\n\t\trepoTags = []string{parsed.String()}\n\t}\n\n\tdockerManifest := []struct {\n\t\tConfig   string   `json:\"Config\"`\n\t\tRepoTags []string `json:\"RepoTags,omitempty\"`\n\t\tLayers   []string `json:\"Layers\"`\n\t}{{\n\t\tConfig:   configFileName,\n\t\tRepoTags: repoTags,\n\t\tLayers:   []string{layerFileName},\n\t}}\n\n\tdockerManifestJSON, err := json.Marshal(dockerManifest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbuf := &bytes.Buffer{}\n\ttw := tar.NewWriter(buf)\n\n\tfiles := []struct {\n\t\tname    string\n\t\tcontent []byte\n\t}{\n\t\t{\"manifest.json\", dockerManifestJSON},\n\t\t{configFileName, configJSON},\n\t\t{layerFileName, layerContent},\n\t}\n\n\tfor _, f := range files {\n\t\tif err := tw.WriteHeader(&tar.Header{\n\t\t\tName: f.name,\n\t\t\tMode: 0644,\n\t\t\tSize: int64(len(f.content)),\n\t\t}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif _, err := tw.Write(f.content); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := tw.Close(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn io.NopCloser(buf), nil\n}\n\nfunc randomRef(prefix string) string {\n\tvar b [6]byte\n\t_, _ = rand.Read(b[:])\n\treturn prefix + base64.RawURLEncoding.EncodeToString(b[:])\n}\n"
  },
  {
    "path": "pkg/cmd/image/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imageinspector\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc inspectIdentifier(ctx context.Context, client *containerd.Client, identifier string) ([]images.Image, string, string, error) {\n\t// Figure out what we have here - digest, tag, name\n\tparsedReference, err := referenceutil.Parse(identifier)\n\tif err != nil {\n\t\treturn nil, \"\", \"\", err\n\t}\n\tdigest := \"\"\n\tif parsedReference.Digest != \"\" {\n\t\tdigest = parsedReference.Digest.String()\n\t}\n\tname := parsedReference.Name()\n\ttag := parsedReference.Tag\n\n\t// Initialize filters\n\tvar filters []string\n\t// This will hold the final image list, if any\n\tvar imageList []images.Image\n\n\t// No digest in the request? Then assume it is a name\n\tif digest == \"\" {\n\t\tfilters = []string{fmt.Sprintf(\"name==%s:%s\", name, tag)}\n\t\t// Query it\n\t\timageList, err = client.ImageService().List(ctx, filters...)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", \"\", fmt.Errorf(\"containerd image service failed: %w\", err)\n\t\t}\n\t\t// Nothing? Then it could be a short id (aka truncated digest) - we are going to use this\n\t\tif len(imageList) == 0 {\n\t\t\tdigest = fmt.Sprintf(\"sha256:%s.*\", regexp.QuoteMeta(strings.TrimPrefix(identifier, \"sha256:\")))\n\t\t\tname = \"\"\n\t\t\ttag = \"\"\n\t\t} else {\n\t\t\t// Otherwise, we found one by name. Get the digest from it.\n\t\t\tdigest = imageList[0].Target.Digest.String()\n\t\t}\n\t}\n\n\t// At this point, we DO have a digest (or short id), so, that is what we are retrieving\n\tfilters = []string{fmt.Sprintf(\"target.digest~=^%s$\", digest)}\n\timageList, err = client.ImageService().List(ctx, filters...)\n\tif err != nil {\n\t\treturn nil, \"\", \"\", fmt.Errorf(\"containerd image service failed: %w\", err)\n\t}\n\n\t// TODO: docker does allow retrieving images by Id, so implement as a last ditch effort (probably look-up the store)\n\n\t// Return the list we found, along with normalized name and tag\n\treturn imageList, name, tag, nil\n}\n\n// Inspect prints detailed information of each image in `images`.\nfunc Inspect(ctx context.Context, client *containerd.Client, identifiers []string, options types.ImageInspectOptions) ([]any, error) {\n\t// Set a timeout\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\t// Will hold the final answers\n\tvar errs []error\n\tvar entries []interface{}\n\n\tsnapshotter := containerdutil.SnapshotService(client, options.GOptions.Snapshotter)\n\t// We have to query per provided identifier, as we need to post-process results for the case name + digest\n\tfor _, identifier := range identifiers {\n\t\tcandidateImageList, requestedName, requestedTag, err := inspectIdentifier(ctx, client, identifier)\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"%w: %s\", err, identifier))\n\t\t\tcontinue\n\t\t}\n\n\t\tvar validatedImage *dockercompat.Image\n\t\tvar repoTags []string\n\t\tvar repoDigests []string\n\n\t\t// Go through the candidates\n\t\tfor _, candidateImage := range candidateImageList {\n\t\t\t// Inspect the image\n\t\t\tcandidateNativeImage, err := imageinspector.Inspect(ctx, client, candidateImage, snapshotter)\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).WithField(\"name\", candidateImage.Name).Error(\"failure inspecting image\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// If native, we just add everything in there and that's it\n\t\t\tif options.Mode == \"native\" {\n\t\t\t\tentries = append(entries, candidateNativeImage)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// If dockercompat: does the candidate have a name? Get it if so\n\t\t\tparsedReference, err := referenceutil.Parse(candidateNativeImage.Image.Name)\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).WithField(\"name\", candidateNativeImage.Image.Name).Error(\"the found image has an unparsable name\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// If we were ALSO asked for a specific name on top of the digest, we need to make sure we keep only the image with that name\n\t\t\tif requestedName != \"\" {\n\t\t\t\t// If the candidate did not have a name, then we should ignore this one and continue\n\t\t\t\tif parsedReference.Name() == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Otherwise, the candidate has a name. If it is the one we want, store it and continue, otherwise, fall through\n\t\t\t\tcandidateTag := parsedReference.Tag\n\t\t\t\t// If the name had a digest, an empty tag is not normalized to latest, so, account for that here\n\t\t\t\tif requestedTag == \"\" {\n\t\t\t\t\trequestedTag = \"latest\"\n\t\t\t\t}\n\t\t\t\tif parsedReference.Name() == requestedName && candidateTag == requestedTag {\n\t\t\t\t\tvalidatedImage, err = dockercompat.ImageFromNative(candidateNativeImage)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.G(ctx).WithError(err).WithField(\"name\", candidateNativeImage.Image.Name).Error(\"could not get a docker compat version of the native image\")\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if validatedImage == nil {\n\t\t\t\t// Alternatively, we got a request by digest only, so, if we do not know about it already, store it and continue\n\t\t\t\tvalidatedImage, err = dockercompat.ImageFromNative(candidateNativeImage)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).WithField(\"name\", candidateNativeImage.Image.Name).Error(\"could not get a docker compat version of the native image\")\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Fallthrough cases:\n\t\t\t// - we got a request by digest, but we already had the image stored\n\t\t\t// - we got a request by name, and the name of the candidate did not match the requested name\n\t\t\t// Now, check if the candidate has a name - if it does, populate repoTags and repoDigests\n\t\t\tif parsedReference.Name() != \"\" {\n\t\t\t\ttag := parsedReference.Tag\n\t\t\t\tif tag == \"\" {\n\t\t\t\t\ttag = \"latest\"\n\t\t\t\t}\n\t\t\t\trepoTags = append(repoTags, fmt.Sprintf(\"%s:%s\", parsedReference.FamiliarName(), tag))\n\t\t\t\trepoDigests = append(repoDigests, fmt.Sprintf(\"%s@%s\", parsedReference.FamiliarName(), candidateImage.Target.Digest.String()))\n\t\t\t}\n\t\t}\n\n\t\t// Done iterating through candidates. Did we find anything that matches?\n\t\tif options.Mode == \"dockercompat\" {\n\t\t\tif validatedImage == nil {\n\t\t\t\terrs = append(errs, fmt.Errorf(\"no such image: %s\", identifier))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Then slap in the repoTags and repoDigests we found from the other candidates\n\t\t\tvalidatedImage.RepoTags = append(validatedImage.RepoTags, repoTags...)\n\t\t\tvalidatedImage.RepoDigests = append(validatedImage.RepoDigests, repoDigests...)\n\t\t\t// Store our image\n\t\t\t// foundImages[validatedDigest] = validatedImage\n\t\t\tentries = append(entries, validatedImage)\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn []any{}, fmt.Errorf(\"%d errors:\\n%w\", len(errs), errors.Join(errs...))\n\t}\n\n\treturn entries, nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/go-digest\"\n\t\"github.com/opencontainers/image-spec/identity\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\n// ListCommandHandler `List` and print images matching filters in `options`.\nfunc ListCommandHandler(ctx context.Context, client *containerd.Client, options *types.ImageListOptions) error {\n\timageList, err := List(ctx, client, options.Filters, options.NameAndRefFilter)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn printImages(ctx, client, imageList, options)\n}\n\n// List queries containerd client to get image list and only returns those matching given filters.\n//\n// Supported filters:\n// - before=<image>[:<tag>]: Images created before given image (exclusive)\n// - since=<image>[:<tag>]: Images created after given image (exclusive)\n// - label=<key>[=<value>]: Matches images based on the presence of a label alone or a label and a value\n// - dangling=true: Filter images by dangling\n// - reference=<image>[:<tag>]: Filter images by reference (Matches both docker compatible wildcard pattern and regexp\n//\n// nameAndRefFilter has the format of `name==(<image>[:<tag>])|ID`,\n// and they will be used when getting images from containerd,\n// while the remaining filters are only applied after getting images from containerd,\n// which means that having nameAndRefFilter may speed up the process if there are a lot of images in containerd.\nfunc List(ctx context.Context, client *containerd.Client, filters, nameAndRefFilter []string) ([]images.Image, error) {\n\tvar imageStore = client.ImageService()\n\timageList, err := imageStore.List(ctx, nameAndRefFilter...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(filters) > 0 {\n\t\tf, err := imgutil.ParseFilters(filters)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfilters := []imgutil.Filter{}\n\t\tif f.Dangling != nil && *f.Dangling {\n\t\t\tfilters = append(filters, imgutil.FilterDanglingImages())\n\t\t} else if f.Dangling != nil {\n\t\t\tfilters = append(filters, imgutil.FilterTaggedImages())\n\t\t}\n\n\t\tif len(f.Labels) > 0 {\n\t\t\tfilters = append(filters, imgutil.FilterByLabel(ctx, client, f.Labels))\n\t\t}\n\n\t\tif len(f.Reference) > 0 {\n\t\t\tfilters = append(filters, imgutil.FilterByReference(f.Reference))\n\t\t}\n\n\t\tif len(f.Before) > 0 || len(f.Since) > 0 {\n\t\t\tfilters = append(filters, imgutil.FilterByCreatedAt(ctx, client, f.Before, f.Since))\n\t\t}\n\n\t\timageList, err = imgutil.ApplyFilters(imageList, filters...)\n\t\tif err != nil {\n\t\t\treturn []images.Image{}, err\n\t\t}\n\t}\n\n\tsort.Slice(imageList, func(i, j int) bool {\n\t\treturn imageList[i].CreatedAt.After(imageList[j].CreatedAt)\n\t})\n\treturn imageList, nil\n}\n\ntype imagePrintable struct {\n\t// TODO: \"Containers\"\n\tCreatedAt    string\n\tCreatedSince string\n\tDigest       string // \"<none>\" or image target digest (i.e., index digest or manifest digest)\n\tID           string // image target digest (not config digest, unlike Docker), or its short form\n\tRepository   string\n\tTag          string // \"<none>\" or tag\n\tName         string // image name\n\tSize         string // the size of the unpacked snapshots.\n\tBlobSize     string // the size of the blobs in the content store (nerdctl extension)\n\t// TODO: \"SharedSize\", \"UniqueSize\"\n\tPlatform string // nerdctl extension\n}\n\nfunc printImages(ctx context.Context, client *containerd.Client, imageList []images.Image, options *types.ImageListOptions) error {\n\tw := options.Stdout\n\tvar finalImageList []images.Image\n\t/*\n\t\tthe same imageId under k8s.io is showing multiple results: repo:tag, repo:digest, configID.\n\t\tWe expect to display only repo:tag, consistent with other namespaces and CRI\n\t\te.g.\n\t\tnerdctl -n k8s.io images\n\t\tREPOSITORY    TAG       IMAGE ID        CREATED        PLATFORM       SIZE         BLOB SIZE\n\t\tcentos        7         be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\t\tcentos        <none>    be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\t\t<none>        <none>    be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\t\texpect:\n\t\tnerdctl --kube-hide-dupe -n k8s.io images\n\t\tREPOSITORY    TAG       IMAGE ID        CREATED        PLATFORM       SIZE         BLOB SIZE\n\t\tcentos        7         be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\t*/\n\tif options.GOptions.KubeHideDupe && options.GOptions.Namespace == \"k8s.io\" {\n\t\timageDigest := make(map[digest.Digest]bool)\n\t\tvar imageNoTag []images.Image\n\t\tfor _, img := range imageList {\n\t\t\tparsed, err := referenceutil.Parse(img.Name)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif parsed.Tag != \"\" {\n\t\t\t\tfinalImageList = append(finalImageList, img)\n\t\t\t\timageDigest[img.Target.Digest] = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\timageNoTag = append(imageNoTag, img)\n\t\t}\n\t\t//Ensure that dangling images without a repo:tag are displayed correctly.\n\t\tfor _, ima := range imageNoTag {\n\t\t\tif !imageDigest[ima.Target.Digest] {\n\t\t\t\tfinalImageList = append(finalImageList, ima)\n\t\t\t\timageDigest[ima.Target.Digest] = true\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfinalImageList = imageList\n\t}\n\tdigestsFlag := options.Digests\n\tif options.Format == \"wide\" {\n\t\tdigestsFlag = true\n\t}\n\tvar tmpl *template.Template\n\tswitch options.Format {\n\tcase \"\", \"table\", \"wide\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !options.Quiet {\n\t\t\tprintHeader := \"\"\n\t\t\tif options.Names {\n\t\t\t\tprintHeader += \"NAME\\t\"\n\t\t\t} else {\n\t\t\t\tprintHeader += \"REPOSITORY\\tTAG\\t\"\n\t\t\t}\n\t\t\tif digestsFlag {\n\t\t\t\tprintHeader += \"DIGEST\\t\"\n\t\t\t}\n\t\t\tprintHeader += \"IMAGE ID\\tCREATED\\tPLATFORM\\tSIZE\\tBLOB SIZE\"\n\t\t\tfmt.Fprintln(w, printHeader)\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\tif options.Quiet {\n\t\t\treturn errors.New(\"format and quiet must not be specified together\")\n\t\t}\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tprinter := &imagePrinter{\n\t\tw:           w,\n\t\tquiet:       options.Quiet,\n\t\tnoTrunc:     options.NoTrunc,\n\t\tdigestsFlag: digestsFlag,\n\t\tnamesFlag:   options.Names,\n\t\ttmpl:        tmpl,\n\t\tclient:      client,\n\t\tprovider:    containerdutil.NewProvider(client),\n\t\tsnapshotter: containerdutil.SnapshotService(client, options.GOptions.Snapshotter),\n\t}\n\n\tfor _, img := range finalImageList {\n\t\tif err := printer.printImage(ctx, img); err != nil {\n\t\t\tlog.G(ctx).Warn(err)\n\t\t}\n\t}\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n\ntype imagePrinter struct {\n\tw                                      io.Writer\n\tquiet, noTrunc, digestsFlag, namesFlag bool\n\ttmpl                                   *template.Template\n\tclient                                 *containerd.Client\n\tprovider                               content.Provider\n\tsnapshotter                            snapshots.Snapshotter\n}\n\ntype image struct {\n\tblobSize int64\n\tsize     int64\n\tplatform platforms.Platform\n\tconfig   *ocispec.Descriptor\n}\n\nfunc readManifest(ctx context.Context, provider content.Provider, snapshotter snapshots.Snapshotter, desc ocispec.Descriptor) (*image, error) {\n\t// Read the manifest blob from the descriptor\n\tmanifestData, err := containerdutil.ReadBlob(ctx, provider, desc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal as Manifest\n\tvar manifest ocispec.Manifest\n\tif err := json.Unmarshal(manifestData, &manifest); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Now, read the config\n\tconfigData, err := containerdutil.ReadBlob(ctx, provider, manifest.Config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal as Image\n\tvar config ocispec.Image\n\tif err := json.Unmarshal(configData, &config); err != nil {\n\t\tlog.G(ctx).Error(\"Error unmarshaling config\")\n\t\treturn nil, err\n\t}\n\n\t// If we are here, the image exists and is valid, so, do our size lookups\n\n\t// Aggregate the descriptor size, and blob size from the config and layers\n\tblobSize := desc.Size + manifest.Config.Size\n\tfor _, layerDescriptor := range manifest.Layers {\n\t\tblobSize += layerDescriptor.Size\n\t}\n\n\t// Get the platform\n\tplt := platforms.Normalize(ocispec.Platform{OS: config.OS, Architecture: config.Architecture, Variant: config.Variant})\n\n\t// Get the filesystem size for all layers\n\tchainID := identity.ChainID(config.RootFS.DiffIDs).String()\n\tsize := int64(0)\n\tif _, actualSize, err := imgutil.ResourceUsage(ctx, snapshotter, chainID); err == nil {\n\t\tsize = actualSize.Size\n\t}\n\n\treturn &image{\n\t\tblobSize: blobSize,\n\t\tsize:     size,\n\t\tplatform: plt,\n\t\tconfig:   &manifest.Config,\n\t}, nil\n}\n\nfunc readIndex(ctx context.Context, provider content.Provider, snapshotter snapshots.Snapshotter, desc ocispec.Descriptor) (map[string]*image, error) {\n\tdescs := map[string]*image{}\n\n\t// Read the index\n\tindexData, err := containerdutil.ReadBlob(ctx, provider, desc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal as Index\n\tvar index ocispec.Index\n\tif err := json.Unmarshal(indexData, &index); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Iterate over manifest descriptors and read them all\n\tfor _, manifestDescriptor := range index.Manifests {\n\t\tif isAttestationManifestDescriptor(manifestDescriptor) {\n\t\t\tcontinue\n\t\t}\n\n\t\tmanifest, err := readManifest(ctx, provider, snapshotter, manifestDescriptor)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tdescs[platforms.FormatAll(manifest.platform)] = manifest\n\t}\n\treturn descs, err\n}\n\nfunc read(ctx context.Context, provider content.Provider, snapshotter snapshots.Snapshotter, desc ocispec.Descriptor) (map[string]*image, error) {\n\tif images.IsManifestType(desc.MediaType) {\n\t\tmanifest, err := readManifest(ctx, provider, snapshotter, desc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdescs := map[string]*image{}\n\t\tdescs[platforms.FormatAll(manifest.platform)] = manifest\n\t\treturn descs, nil\n\t}\n\tif images.IsIndexType(desc.MediaType) {\n\t\treturn readIndex(ctx, provider, snapshotter, desc)\n\t}\n\treturn nil, fmt.Errorf(\"unknown media type: %s\", desc.MediaType)\n}\n\nfunc (x *imagePrinter) printImage(ctx context.Context, img images.Image) error {\n\tcandidateImages, err := read(ctx, x.provider, x.snapshotter, img.Target)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor platform, desc := range candidateImages {\n\t\tif err := x.printImageSinglePlatform(*desc.config, img, desc.blobSize, desc.size, desc.platform); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Debugf(\"failed to get platform %q of image %q\", platform, img.Name)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (x *imagePrinter) printImageSinglePlatform(desc ocispec.Descriptor, img images.Image, blobSize int64, size int64, plt platforms.Platform) error {\n\tvar (\n\t\trepository string\n\t\ttag        string\n\t)\n\t// cri plugin will create an image named digest of image's config, skip parsing.\n\tif x.namesFlag || desc.Digest.String() != img.Name {\n\t\trepository, tag = imgutil.ParseRepoTag(img.Name)\n\t}\n\n\tp := imagePrintable{\n\t\tCreatedAt:    img.CreatedAt.Round(time.Second).Local().String(), // format like \"2021-08-07 02:19:45 +0900 JST\"\n\t\tCreatedSince: formatter.TimeSinceInHuman(img.CreatedAt),\n\t\tDigest:       img.Target.Digest.String(),\n\t\tID:           img.Target.Digest.String(),\n\t\tRepository:   repository,\n\t\tTag:          tag,\n\t\tName:         img.Name,\n\t\tSize:         units.HumanSize(float64(size)),\n\t\tBlobSize:     units.HumanSize(float64(blobSize)),\n\t\tPlatform:     platforms.FormatAll(plt),\n\t}\n\tif p.Repository == \"\" {\n\t\tp.Repository = \"<none>\"\n\t}\n\tif p.Tag == \"\" {\n\t\tp.Tag = \"<none>\" // for Docker compatibility\n\t}\n\tif !x.noTrunc {\n\t\t// p.Digest does not need to be truncated\n\t\tp.ID = strings.Split(p.ID, \":\")[1][:12]\n\t}\n\tif x.tmpl != nil {\n\t\tvar b bytes.Buffer\n\t\tif err := x.tmpl.Execute(&b, p); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := fmt.Fprintln(x.w, b.String()); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if x.quiet {\n\t\tif _, err := fmt.Fprintln(x.w, p.ID); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tformat := \"\"\n\t\targs := []interface{}{}\n\t\tif x.namesFlag {\n\t\t\tformat += \"%s\\t\"\n\t\t\targs = append(args, p.Name)\n\t\t} else {\n\t\t\tformat += \"%s\\t%s\\t\"\n\t\t\targs = append(args, p.Repository, p.Tag)\n\t\t}\n\t\tif x.digestsFlag {\n\t\t\tformat += \"%s\\t\"\n\t\t\targs = append(args, p.Digest)\n\t\t}\n\n\t\tformat += \"%s\\t%s\\t%s\\t%s\\t%s\\n\"\n\t\targs = append(args, p.ID, p.CreatedSince, p.Platform, p.Size, p.BlobSize)\n\t\tif _, err := fmt.Fprintf(x.w, format, args...); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc isAttestationManifestDescriptor(desc ocispec.Descriptor) bool {\n\tconst manifestReferenceType = \"vnd.docker.reference.type\"\n\tconst attestationManifest = \"attestation-manifest\"\n\treturn desc.Annotations[manifestReferenceType] == attestationManifest\n}\n"
  },
  {
    "path": "pkg/cmd/image/prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/opencontainers/go-digest\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\n// Prune will remove all dangling images. If all is specified, will also remove all images not referenced by any container.\nfunc Prune(ctx context.Context, client *containerd.Client, options types.ImagePruneOptions) error {\n\tvar (\n\t\timageStore   = client.ImageService()\n\t\tcontentStore = client.ContentStore()\n\t)\n\n\tvar (\n\t\timagesToBeRemoved []images.Image\n\t\terr               error\n\t)\n\n\tfilters := []imgutil.Filter{}\n\tif len(options.Filters) > 0 {\n\t\tparsedFilters, err := imgutil.ParseFilters(options.Filters)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(parsedFilters.Labels) > 0 {\n\t\t\tfilters = append(filters, imgutil.FilterByLabel(ctx, client, parsedFilters.Labels))\n\t\t}\n\t\tif len(parsedFilters.Until) > 0 {\n\t\t\tfilters = append(filters, imgutil.FilterUntil(parsedFilters.Until))\n\t\t}\n\t}\n\n\tif options.All {\n\t\t// Remove all unused images; not just dangling ones\n\t\timagesToBeRemoved, err = imgutil.GetUnusedImages(ctx, client, filters...)\n\t} else {\n\t\t// Remove dangling images only\n\t\timagesToBeRemoved, err = imgutil.GetDanglingImages(ctx, client, filters...)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdelOpts := []images.DeleteOpt{images.SynchronousDelete()}\n\tremovedImages := make(map[string][]digest.Digest)\n\tfor _, image := range imagesToBeRemoved {\n\t\tdigests, err := image.RootFS(ctx, contentStore, platforms.DefaultStrict())\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to enumerate rootfs\")\n\t\t}\n\t\tif err := imageStore.Delete(ctx, image.Name, delOpts...); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to delete image %s\", image.Name)\n\t\t\tcontinue\n\t\t}\n\t\tremovedImages[image.Name] = digests\n\t}\n\n\tif len(removedImages) > 0 {\n\t\tfmt.Fprintln(options.Stdout, \"Deleted Images:\")\n\t\tfor image, digests := range removedImages {\n\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s\\n\", image)\n\t\t\tfor _, digest := range digests {\n\t\t\t\tfmt.Fprintf(options.Stdout, \"deleted: %s\\n\", digest)\n\t\t\t}\n\t\t}\n\t\tfmt.Fprintln(options.Stdout, \"\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/pull.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipfs\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signutil\"\n)\n\n// Pull pulls an image specified by `rawRef`.\nfunc Pull(ctx context.Context, client *containerd.Client, rawRef string, options types.ImagePullOptions) error {\n\t_, err := EnsureImage(ctx, client, rawRef, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// EnsureImage pulls an image either from ipfs or from registry.\nfunc EnsureImage(ctx context.Context, client *containerd.Client, rawRef string, options types.ImagePullOptions) (*imgutil.EnsuredImage, error) {\n\tvar ensured *imgutil.EnsuredImage\n\n\tparsedReference, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif parsedReference.Protocol != \"\" {\n\t\tif options.VerifyOptions.Provider != \"none\" {\n\t\t\treturn nil, errors.New(\"--verify flag is not supported on IPFS as of now\")\n\t\t}\n\n\t\tvar ipfsPath string\n\t\tif options.IPFSAddress != \"\" {\n\t\t\tdir, err := os.MkdirTemp(\"\", \"apidirtmp\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdefer os.RemoveAll(dir)\n\t\t\tif err := filesystem.WriteFile(filepath.Join(dir, \"api\"), []byte(options.IPFSAddress), 0600); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tipfsPath = dir\n\t\t}\n\n\t\tensured, err = ipfs.EnsureImage(ctx, client, string(parsedReference.Protocol), parsedReference.String(), ipfsPath, options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn ensured, nil\n\t}\n\n\tref, err := signutil.Verify(ctx, rawRef, options.GOptions.HostsDir, options.GOptions.Experimental, options.VerifyOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tensured, err = imgutil.EnsureImage(ctx, client, ref, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ensured, err\n}\n"
  },
  {
    "path": "pkg/cmd/image/push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/containerd/v2/core/remotes/docker\"\n\tdockerconfig \"github.com/containerd/containerd/v2/core/remotes/docker/config\"\n\t\"github.com/containerd/containerd/v2/pkg/reference\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\t\"github.com/containerd/stargz-snapshotter/estargz\"\n\t\"github.com/containerd/stargz-snapshotter/estargz/zstdchunked\"\n\testargzconvert \"github.com/containerd/stargz-snapshotter/nativeconverter/estargz\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\tnerdconverter \"github.com/containerd/nerdctl/v2/pkg/imgutil/converter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/push\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipfs\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/snapshotterutil\"\n)\n\n// Push pushes an image specified by `rawRef`.\nfunc Push(ctx context.Context, client *containerd.Client, rawRef string, options types.ImagePushOptions) error {\n\tparsedReference, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif parsedReference.Protocol != \"\" {\n\t\tif parsedReference.Protocol != referenceutil.IPFSProtocol {\n\t\t\treturn fmt.Errorf(\"ipfs scheme is only supported but got %q\", parsedReference.Protocol)\n\t\t}\n\t\tlog.G(ctx).Infof(\"pushing image %q to IPFS\", parsedReference)\n\n\t\t// Ensure all the layers are here: https://github.com/containerd/nerdctl/issues/3489\n\t\t// XXX what if the image is a CID, or only otherwise available on ipfs?\n\t\tplatMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platforms)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = EnsureAllContent(ctx, client, parsedReference.String(), platMC, options.GOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar ipfsPath string\n\t\tif options.IpfsAddress != \"\" {\n\t\t\tdir, err := os.MkdirTemp(\"\", \"apidirtmp\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer os.RemoveAll(dir)\n\t\t\tif err := filesystem.WriteFile(filepath.Join(dir, \"api\"), []byte(options.IpfsAddress), 0600); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tipfsPath = dir\n\t\t}\n\n\t\tvar layerConvert converter.ConvertFunc\n\t\tif options.Estargz {\n\t\t\tlayerConvert = eStargzConvertFunc()\n\t\t}\n\t\tc, err := ipfs.Push(ctx, client, parsedReference.String(), layerConvert, options.AllPlatforms, options.Platforms, options.IpfsEnsureImage, ipfsPath)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"ipfs push failed\")\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintln(options.Stdout, c)\n\t\treturn nil\n\t}\n\n\tparsedReference, err = referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\tref := parsedReference.String()\n\n\tplatMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platforms)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpushRef := ref\n\tif !options.AllPlatforms {\n\t\tpushRef = ref + \"-tmp-reduced-platform\"\n\t\t// Push fails with \"400 Bad Request\" when the manifest is multi-platform but we do not locally have multi-platform blobs.\n\t\t// So we create a tmp reduced-platform image to avoid the error.\n\t\t// Ensure all the layers are here: https://github.com/containerd/nerdctl/issues/3425\n\t\terr = EnsureAllContent(ctx, client, ref, platMC, options.GOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tplatImg, err := nerdconverter.Convert(ctx, client, pushRef, ref, converter.WithPlatform(platMC))\n\t\tif err != nil {\n\t\t\tif len(options.Platforms) == 0 {\n\t\t\t\treturn fmt.Errorf(\"failed to create a tmp single-platform image %q: %w\", pushRef, err)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to create a tmp reduced-platform image %q (platform=%v): %w\", pushRef, options.Platforms, err)\n\t\t}\n\t\tdefer client.ImageService().Delete(ctx, platImg.Name, images.SynchronousDelete())\n\t\tlog.G(ctx).Infof(\"pushing as a reduced-platform image (%s, %s)\", platImg.Target.MediaType, platImg.Target.Digest)\n\t}\n\n\tif options.Estargz {\n\t\tpushRef = ref + \"-tmp-esgz\"\n\t\tesgzImg, err := nerdconverter.Convert(ctx, client, pushRef, ref, converter.WithPlatform(platMC), converter.WithLayerConvertFunc(eStargzConvertFunc()))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert to eStargz: %v\", err)\n\t\t}\n\t\tdefer client.ImageService().Delete(ctx, esgzImg.Name, images.SynchronousDelete())\n\t\tlog.G(ctx).Infof(\"pushing as an eStargz image (%s, %s)\", esgzImg.Target.MediaType, esgzImg.Target.Digest)\n\t}\n\tif !options.AllowNondistributableArtifacts {\n\t\tif err := pushImageWithLocal(ctx, client, parsedReference, pushRef, ref, options, platMC); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// Transfer service is available in containerd 1.7, but full support is only in 2.0+\n\t\t// For containerd 1.7, use the legacy resolver-based push method for better compatibility\n\t\tuseTransferAPI := containerdutil.SupportsFullTransferService(ctx, client)\n\t\tif !useTransferAPI {\n\t\t\tlog.G(ctx).Debug(\"Detected containerd < 2.0, using legacy push method\")\n\t\t}\n\n\t\tif useTransferAPI {\n\t\t\tif err := imgutil.PushImageWithTransfer(ctx, client, parsedReference, pushRef, ref, options); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tif err := pushImageWithLocal(ctx, client, parsedReference, pushRef, ref, options, platMC); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\timg, err := client.ImageService().Get(ctx, pushRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\trefSpec, err := reference.Parse(pushRef)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsignRef := fmt.Sprintf(\"%s@%s\", refSpec.String(), img.Target.Digest.String())\n\tif err = signutil.Sign(signRef,\n\t\toptions.GOptions.Experimental,\n\t\toptions.SignOptions); err != nil {\n\t\treturn err\n\t}\n\tif options.GOptions.Snapshotter == \"soci\" {\n\t\tif err = snapshotterutil.CreateSociIndexV1(ref, options.GOptions, options.AllPlatforms, options.Platforms, options.SociOptions); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = snapshotterutil.PushSoci(ref, options.GOptions, options.AllPlatforms, options.Platforms); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif options.Quiet {\n\t\tfmt.Fprintln(options.Stdout, ref)\n\t}\n\treturn nil\n}\n\nfunc eStargzConvertFunc() converter.ConvertFunc {\n\tconvertToESGZ := estargzconvert.LayerConvertFunc()\n\treturn func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {\n\t\tif isReusableESGZ(ctx, cs, desc) {\n\t\t\tlog.L.Infof(\"reusing estargz %s without conversion\", desc.Digest)\n\t\t\treturn nil, nil\n\t\t}\n\t\tnewDesc, err := convertToESGZ(ctx, cs, desc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlog.L.Infof(\"converted %q to %s\", desc.MediaType, newDesc.Digest)\n\t\treturn newDesc, err\n\t}\n\n}\n\nfunc isReusableESGZ(ctx context.Context, cs content.Store, desc ocispec.Descriptor) bool {\n\tdgstStr, ok := desc.Annotations[estargz.TOCJSONDigestAnnotation]\n\tif !ok {\n\t\treturn false\n\t}\n\ttocdgst, err := digest.Parse(dgstStr)\n\tif err != nil {\n\t\treturn false\n\t}\n\tra, err := cs.ReaderAt(ctx, desc)\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer ra.Close()\n\tr, err := estargz.Open(io.NewSectionReader(ra, 0, desc.Size), estargz.WithDecompressors(new(zstdchunked.Decompressor)))\n\tif err != nil {\n\t\treturn false\n\t}\n\tif _, err := r.VerifyTOC(tocdgst); err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc pushImageWithLocal(ctx context.Context, client *containerd.Client, parsedReference *referenceutil.ImageReference, pushRef, rawRef string, options types.ImagePushOptions, platMC platforms.MatchComparer) error {\n\tref := parsedReference.String()\n\trefDomain := parsedReference.Domain\n\n\t// In order to push images where most layers are the same but the\n\t// repository name is different, it is necessary to refresh the\n\t// PushTracker. Otherwise, the MANIFEST_BLOB_UNKNOWN error will occur due\n\t// to the registry not creating the corresponding layer link file,\n\t// resulting in the failure of the entire image push.\n\tpushTracker := docker.NewInMemoryTracker()\n\n\tpushFunc := func(r remotes.Resolver) error {\n\t\treturn push.Push(ctx, client, r, pushTracker, options.Stdout, pushRef, ref, platMC, options.AllowNondistributableArtifacts, options.Quiet)\n\t}\n\n\tvar dOpts []dockerconfigresolver.Opt\n\tif options.GOptions.InsecureRegistry {\n\t\tlog.G(ctx).Warnf(\"skipping verifying HTTPS certs for %q\", refDomain)\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t}\n\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(options.GOptions.HostsDir))\n\n\tho, err := dockerconfigresolver.NewHostOptions(ctx, refDomain, dOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresolverOpts := docker.ResolverOptions{\n\t\tTracker: pushTracker,\n\t\tHosts:   dockerconfig.ConfigureHosts(ctx, *ho),\n\t}\n\n\tresolver := docker.NewResolver(resolverOpts)\n\tif err = pushFunc(resolver); err != nil {\n\t\t// In some circumstance (e.g. people just use 80 port to support pure http), the error will contain message like \"dial tcp <port>: connection refused\"\n\t\tif !errors.Is(err, http.ErrSchemeMismatch) && !errutil.IsErrConnectionRefused(err) {\n\t\t\treturn err\n\t\t}\n\t\tif options.GOptions.InsecureRegistry {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"server %q does not seem to support HTTPS, falling back to plain HTTP\", refDomain)\n\t\t\tdOpts = append(dOpts, dockerconfigresolver.WithPlainHTTP(true))\n\t\t\tresolver, err = dockerconfigresolver.New(ctx, refDomain, dOpts...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn pushFunc(resolver)\n\t\t}\n\t\tlog.G(ctx).WithError(err).Errorf(\"server %q does not seem to support HTTPS\", refDomain)\n\t\tlog.G(ctx).Info(\"Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)\")\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker\"\n)\n\n// Remove removes a list of `images`.\nfunc Remove(ctx context.Context, client *containerd.Client, args []string, options types.ImageRemoveOptions) error {\n\tvar delOpts []images.DeleteOpt\n\tif !options.Async {\n\t\tdelOpts = append(delOpts, images.SynchronousDelete())\n\t}\n\n\tcs := client.ContentStore()\n\tis := client.ImageService()\n\tcontainerList, err := client.Containers(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tusedImages := make(map[string]string)\n\trunningImages := make(map[string]string)\n\tfor _, container := range containerList {\n\t\timage, err := container.Image(ctx)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// if err != nil, simply go to `default`\n\t\tswitch cStatus, _ := containerutil.ContainerStatus(ctx, container); cStatus.Status {\n\t\tcase containerd.Running, containerd.Pausing, containerd.Paused:\n\t\t\trunningImages[image.Name()] = container.ID()\n\t\tdefault:\n\t\t\tusedImages[image.Name()] = container.ID()\n\t\t}\n\t}\n\n\twalker := &imagewalker.ImageWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found imagewalker.Found) error {\n\t\t\tif found.NameMatchIndex == -1 {\n\t\t\t\t// if found multiple images, return error unless in force-mode and\n\t\t\t\t// there is only 1 unique image.\n\t\t\t\tif found.MatchCount > 1 && !(options.Force && found.UniqueImages == 1) {\n\t\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t\t}\n\t\t\t} else if found.NameMatchIndex != found.MatchIndex {\n\t\t\t\t// when there is an image with a name matching the argument but the argument is a digest short id,\n\t\t\t\t// the deletion process is not performed.\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif cid, ok := runningImages[found.Image.Name]; ok {\n\t\t\t\tif options.Force {\n\t\t\t\t\t// This is a running image, so, we need to keep a ref on it so that containerd does not GC the layers\n\t\t\t\t\t// First create the new image with an empty name\n\t\t\t\t\toriginalName := found.Image.Name\n\t\t\t\t\tfound.Image.Name = \":\"\n\t\t\t\t\tif _, err = is.Create(ctx, found.Image); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\t// Now, delete the original\n\t\t\t\t\tif err = is.Delete(ctx, originalName, delOpts...); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s\\n\", originalName)\n\t\t\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s@%s\\n\", originalName, found.Image.Target.Digest.String())\n\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"conflict: unable to delete %s (cannot be forced) - image is being used by running container %s\", found.Req, cid)\n\t\t\t}\n\t\t\tif cid, ok := usedImages[found.Image.Name]; ok && !options.Force {\n\t\t\t\treturn fmt.Errorf(\"conflict: unable to delete %s (must be forced) - image is being used by stopped container %s\", found.Req, cid)\n\t\t\t}\n\t\t\t// digests is used only for emulating human-readable output of `docker rmi`\n\t\t\tdigests, err := found.Image.RootFS(ctx, cs, platforms.DefaultStrict())\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Warning(\"failed to enumerate rootfs\")\n\t\t\t}\n\n\t\t\tif err := is.Delete(ctx, found.Image.Name, delOpts...); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s@%s\\n\", found.Image.Name, found.Image.Target.Digest)\n\t\t\tfor _, digest := range digests {\n\t\t\t\tfmt.Fprintf(options.Stdout, \"Deleted: %s\\n\", digest)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tOnFoundCriRm: func(ctx context.Context, found imagewalker.Found) (bool, error) {\n\t\t\tif found.NameMatchIndex == -1 {\n\t\t\t\t// if found multiple images, return error unless in force-mode and\n\t\t\t\t// there is only 1 unique image.\n\t\t\t\tif found.MatchCount > 1 && !(options.Force && found.UniqueImages == 1) {\n\t\t\t\t\treturn false, fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t\t}\n\t\t\t} else if found.NameMatchIndex != found.MatchIndex {\n\t\t\t\t// when there is an image with a name matching the argument but the argument is a digest short id,\n\t\t\t\t// the deletion process is not performed.\n\t\t\t\treturn false, nil\n\t\t\t}\n\n\t\t\tif cid, ok := runningImages[found.Image.Name]; ok {\n\t\t\t\tif options.Force {\n\t\t\t\t\t// This is a running image, so, we need to keep a ref on it so that containerd does not GC the layers\n\t\t\t\t\t// First create the new image with an empty name\n\t\t\t\t\toriginalName := found.Image.Name\n\t\t\t\t\tfound.Image.Name = \":\"\n\t\t\t\t\tif _, err = is.Create(ctx, found.Image); err != nil {\n\t\t\t\t\t\treturn false, err\n\t\t\t\t\t}\n\n\t\t\t\t\t// Now, delete the original\n\t\t\t\t\tif err = is.Delete(ctx, originalName, delOpts...); err != nil {\n\t\t\t\t\t\treturn false, err\n\t\t\t\t\t}\n\n\t\t\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s\\n\", originalName)\n\t\t\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s@%s\\n\", originalName, found.Image.Target.Digest.String())\n\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t\treturn false, fmt.Errorf(\"conflict: unable to delete %s (cannot be forced) - image is being used by running container %s\", found.Req, cid)\n\t\t\t}\n\t\t\tif cid, ok := usedImages[found.Image.Name]; ok && !options.Force {\n\t\t\t\treturn false, fmt.Errorf(\"conflict: unable to delete %s (must be forced) - image is being used by stopped container %s\", found.Req, cid)\n\t\t\t}\n\t\t\t// digests is used only for emulating human-readable output of `docker rmi`\n\t\t\tdigests, err := found.Image.RootFS(ctx, cs, platforms.DefaultStrict())\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Warning(\"failed to enumerate rootfs\")\n\t\t\t}\n\n\t\t\tif err := is.Delete(ctx, found.Image.Name, delOpts...); err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tfmt.Fprintf(options.Stdout, \"Untagged: %s@%s\\n\", found.Image.Name, found.Image.Target.Digest)\n\t\t\tfor _, digest := range digests {\n\t\t\t\tfmt.Fprintf(options.Stdout, \"Deleted: %s\\n\", digest)\n\t\t\t}\n\t\t\treturn true, nil\n\t\t},\n\t}\n\n\tvar errs []string\n\tvar fatalErr bool\n\tfor _, req := range args {\n\t\tvar n int\n\t\tif options.GOptions.KubeHideDupe && options.GOptions.Namespace == \"k8s.io\" {\n\t\t\tn, err = walker.WalkCriRm(ctx, req)\n\t\t} else {\n\t\t\tn, err = walker.Walk(ctx, req)\n\t\t}\n\t\tif err != nil {\n\t\t\tfatalErr = true\n\t\t}\n\t\tif err == nil && n == 0 {\n\t\t\terr = fmt.Errorf(\"no such image: %s\", req)\n\t\t}\n\t\tif err != nil {\n\t\t\terrs = append(errs, err.Error())\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\tmsg := fmt.Sprintf(\"%d errors:\\n%s\", len(errs), strings.Join(errs, \"\\n\"))\n\t\tif !options.Force || fatalErr {\n\t\t\treturn errors.New(msg)\n\t\t}\n\t\tlog.G(ctx).Error(msg)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/save.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/distribution/reference\"\n\t\"github.com/opencontainers/go-digest\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/transfer\"\n\ttarchive \"github.com/containerd/containerd/v2/core/transfer/archive\"\n\ttransferimage \"github.com/containerd/containerd/v2/core/transfer/image\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/transferutil\"\n)\n\n// Save exports `images` to a `io.Writer` (e.g., a file writer, or os.Stdout) specified by `options.Stdout`.\nfunc Save(ctx context.Context, client *containerd.Client, images []string, options types.ImageSaveOptions) error {\n\timages = strutil.DedupeStrSlice(images)\n\n\tvar exportOpts []tarchive.ExportOpt\n\n\tif len(options.Platform) > 0 {\n\t\tfor _, ps := range options.Platform {\n\t\t\tp, err := platforms.Parse(ps)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid platform %q: %w\", ps, err)\n\t\t\t}\n\t\t\texportOpts = append(exportOpts, tarchive.WithPlatform(p))\n\t\t}\n\t}\n\tif options.AllPlatforms {\n\t\texportOpts = append(exportOpts, tarchive.WithAllPlatforms)\n\t}\n\n\tplatMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platform)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\timageService := client.ImageService()\n\tvar storeOpts []transferimage.StoreOpt\n\tfor _, img := range images {\n\t\tvar imageRef string\n\n\t\tvar dgst digest.Digest\n\t\tvar err error\n\t\tif dgst, err = digest.Parse(img); err != nil {\n\t\t\tif dgst, err = digest.Parse(\"sha256:\" + img); err != nil {\n\t\t\t\tnamed, err := reference.ParseNormalizedNamed(img)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"invalid image name %q: %w\", img, err)\n\t\t\t\t}\n\t\t\t\timageRef = reference.TagNameOnly(named).String()\n\t\t\t\terr = EnsureAllContent(ctx, client, imageRef, platMC, options.GOptions)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tstoreOpts = append(storeOpts, transferimage.WithExtraReference(imageRef))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfilters := []string{fmt.Sprintf(\"target.digest~=^%s$\", dgst.String())}\n\t\timageList, err := imageService.List(ctx, filters...)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to list images: %w\", err)\n\t\t}\n\t\tif len(imageList) == 0 {\n\t\t\treturn fmt.Errorf(\"image %q: not found\", img)\n\t\t}\n\n\t\timageRef = imageList[0].Name\n\t\terr = EnsureAllContent(ctx, client, imageRef, platMC, options.GOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstoreOpts = append(storeOpts, transferimage.WithExtraReference(imageRef))\n\t}\n\n\tw := nopWriteCloser{options.Stdout}\n\n\tpf, done := transferutil.ProgressHandler(ctx, os.Stderr)\n\tdefer done()\n\n\treturn client.Transfer(ctx,\n\t\ttransferimage.NewStore(\"\", storeOpts...),\n\t\ttarchive.NewImageExportStream(w, \"\", exportOpts...),\n\t\ttransfer.WithProgress(pf),\n\t)\n}\n\ntype nopWriteCloser struct {\n\tio.Writer\n}\n\nfunc (nopWriteCloser) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/image/tag.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage image\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\ttransferimage \"github.com/containerd/containerd/v2/core/transfer/image\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc Tag(ctx context.Context, client *containerd.Client, options types.ImageTagOptions) error {\n\tparsedSource, err := referenceutil.Parse(options.Source)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tparsedTarget, err := referenceutil.Parse(options.Target)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tplatMC, err := platformutil.NewMatchComparer(false, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = EnsureAllContent(ctx, client, parsedSource.String(), platMC, options.GOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsourceStore := transferimage.NewStore(parsedSource.String())\n\ttargetStore := transferimage.NewStore(parsedTarget.String())\n\n\treturn client.Transfer(ctx, sourceStore, targetStore)\n}\n"
  },
  {
    "path": "pkg/cmd/ipfs/registry_serve.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipfs\"\n)\n\nfunc RegistryServe(options types.IPFSRegistryServeOptions) error {\n\tvar ipfsPath string\n\tif options.IPFSAddress != \"\" {\n\t\tdir, err := os.MkdirTemp(\"\", \"apidirtmp\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer os.RemoveAll(dir)\n\t\tif err := filesystem.WriteFile(filepath.Join(dir, \"api\"), []byte(options.IPFSAddress), 0600); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tipfsPath = dir\n\t}\n\th, err := ipfs.NewRegistry(ipfs.RegistryOptions{\n\t\tIpfsPath:     ipfsPath,\n\t\tReadRetryNum: options.ReadRetryNum,\n\t\tReadTimeout:  options.ReadTimeout,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tlog.L.Infof(\"serving on %v\", options.ListenRegistry)\n\thttp.Handle(\"/\", h)\n\treturn http.ListenAndServe(options.ListenRegistry, nil)\n}\n"
  },
  {
    "path": "pkg/cmd/login/login.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"golang.org/x/net/context/ctxhttp\"\n\n\t\"github.com/containerd/containerd/v2/core/remotes/docker\"\n\t\"github.com/containerd/containerd/v2/core/remotes/docker/config\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n)\n\nconst unencryptedPasswordWarning = `WARNING: Your password will be stored unencrypted in %s.\nConfigure a credential helper to remove this warning. See\nhttps://docs.docker.com/engine/reference/commandline/login/#credentials-store\n`\n\nfunc Login(ctx context.Context, options types.LoginCommandOptions, stdout io.Writer) error {\n\tregistryURL, err := dockerconfigresolver.Parse(options.ServerAddress)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcredStore, err := dockerconfigresolver.NewCredentialsStore(\"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar responseIdentityToken string\n\n\tcredentials, err := credStore.Retrieve(registryURL, options.Username == \"\" && options.Password == \"\")\n\tcredentials.IdentityToken = \"\"\n\n\tif err == nil && credentials.Username != \"\" && credentials.Password != \"\" {\n\t\tresponseIdentityToken, err = loginClientSide(ctx, options.GOptions, registryURL, credentials)\n\t}\n\n\tif err != nil || credentials.Username == \"\" || credentials.Password == \"\" {\n\t\terr = promptUserForAuthentication(credentials, options.Username, options.Password, stdout)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tresponseIdentityToken, err = loginClientSide(ctx, options.GOptions, registryURL, credentials)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif responseIdentityToken != \"\" {\n\t\tcredentials.Password = \"\"\n\t\tcredentials.IdentityToken = responseIdentityToken\n\t}\n\n\t// Display a warning if we're storing the users password (not a token) and credentials store type is file.\n\tstorageFileLocation := credStore.FileStorageLocation(registryURL)\n\tif storageFileLocation != \"\" && credentials.Password != \"\" {\n\t\t_, err = fmt.Fprintln(stdout, fmt.Sprintf(unencryptedPasswordWarning, storageFileLocation))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = credStore.Store(registryURL, credentials)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error saving credentials: %w\", err)\n\t}\n\n\t// When the port is the https default (443), other clients cannot be expected to necessarily lookup the variants with port\n\t// so save it both with and without port.\n\t// This is the case for at least buildctl: https://github.com/containerd/nerdctl/issues/3748\n\tif registryURL.Port() == dockerconfigresolver.StandardHTTPSPort {\n\t\tregistryURL.Host = registryURL.Hostname()\n\t\terr = credStore.Store(registryURL, credentials)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error saving credentials: %w\", err)\n\t\t}\n\t}\n\n\t_, err = fmt.Fprintln(stdout, \"Login Succeeded\")\n\n\treturn err\n}\n\nfunc loginClientSide(ctx context.Context, globalOptions types.GlobalCommandOptions, registryURL *dockerconfigresolver.RegistryURL, credentials *dockerconfigresolver.Credentials) (string, error) {\n\thost := registryURL.Host\n\tvar dOpts []dockerconfigresolver.Opt\n\tif globalOptions.InsecureRegistry {\n\t\tlog.G(ctx).Warnf(\"skipping verifying HTTPS certs for %q\", host)\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t}\n\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(globalOptions.HostsDir))\n\n\tauthCreds := func(acArg string) (string, string, error) {\n\t\tif acArg == host {\n\t\t\tif credentials.RegistryToken != \"\" {\n\t\t\t\t// Even containerd/CRI does not support RegistryToken as of v1.4.3,\n\t\t\t\t// so, nobody is actually using RegistryToken?\n\t\t\t\tlog.G(ctx).Warnf(\"RegistryToken (for %q) is not supported yet (FIXME)\", host)\n\t\t\t}\n\t\t\treturn credentials.Username, credentials.Password, nil\n\t\t}\n\t\treturn \"\", \"\", fmt.Errorf(\"expected acArg to be %q, got %q\", host, acArg)\n\t}\n\n\tdOpts = append(dOpts, dockerconfigresolver.WithAuthCreds(authCreds))\n\tho, err := dockerconfigresolver.NewHostOptions(ctx, host, dOpts...)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfetchedRefreshTokens := make(map[string]string) // key: req.URL.Host\n\t// onFetchRefreshToken is called when tryLoginWithRegHost calls rh.Authorizer.Authorize()\n\tonFetchRefreshToken := func(ctx context.Context, s string, req *http.Request) {\n\t\tfetchedRefreshTokens[req.URL.Host] = s\n\t}\n\tho.AuthorizerOpts = append(ho.AuthorizerOpts, docker.WithFetchRefreshToken(onFetchRefreshToken))\n\tregHosts, err := config.ConfigureHosts(ctx, *ho)(host)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tlog.G(ctx).Debugf(\"len(regHosts)=%d\", len(regHosts))\n\tif len(regHosts) == 0 {\n\t\treturn \"\", fmt.Errorf(\"got empty []docker.RegistryHost for %q\", host)\n\t}\n\tfor i, rh := range regHosts {\n\t\terr = tryLoginWithRegHost(ctx, rh)\n\t\tif err != nil && globalOptions.InsecureRegistry && (errors.Is(err, http.ErrSchemeMismatch) || errutil.IsErrConnectionRefused(err)) {\n\t\t\trh.Scheme = \"http\"\n\t\t\terr = tryLoginWithRegHost(ctx, rh)\n\t\t}\n\t\tidentityToken := fetchedRefreshTokens[rh.Host] // can be empty\n\t\tif err == nil {\n\t\t\treturn identityToken, nil\n\t\t}\n\t\tlog.G(ctx).WithError(err).WithField(\"i\", i).Error(\"failed to call tryLoginWithRegHost\")\n\t}\n\treturn \"\", err\n}\n\nfunc tryLoginWithRegHost(ctx context.Context, rh docker.RegistryHost) error {\n\tif rh.Authorizer == nil {\n\t\treturn errors.New(\"got nil Authorizer\")\n\t}\n\tif rh.Path == \"/v2\" {\n\t\t// If the path is using /v2 endpoint but lacks trailing slash add it\n\t\t// https://docs.docker.com/registry/spec/api/#detail. Acts as a workaround\n\t\t// for containerd issue https://github.com/containerd/containerd/blob/2986d5b077feb8252d5d2060277a9c98ff8e009b/remotes/docker/config/hosts.go#L110\n\t\trh.Path = \"/v2/\"\n\t}\n\tu := url.URL{\n\t\tScheme: rh.Scheme,\n\t\tHost:   rh.Host,\n\t\tPath:   rh.Path,\n\t}\n\tvar ress []*http.Response\n\tfor i := 0; i < 10; i++ {\n\t\treq, err := http.NewRequest(http.MethodGet, u.String(), nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor k, v := range rh.Header.Clone() {\n\t\t\tfor _, vv := range v {\n\t\t\t\treq.Header.Add(k, vv)\n\t\t\t}\n\t\t}\n\t\tif err := rh.Authorizer.Authorize(ctx, req); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to call rh.Authorizer.Authorize: %w\", err)\n\t\t}\n\t\tres, err := ctxhttp.Do(ctx, rh.Client, req)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to call rh.Client.Do: %w\", err)\n\t\t}\n\t\tress = append(ress, res)\n\t\tif res.StatusCode == 401 {\n\t\t\tif err := rh.Authorizer.AddResponses(ctx, ress); err != nil && !errdefs.IsNotImplemented(err) {\n\t\t\t\treturn fmt.Errorf(\"failed to call rh.Authorizer.AddResponses: %w\", err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif res.StatusCode/100 != 2 {\n\t\t\treturn fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"too many 401 (probably)\")\n}\n"
  },
  {
    "path": "pkg/cmd/login/prompt.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n)\n\nvar (\n\t// User did not provide non-empty credentials when prompted for it\n\tErrUsernameIsRequired = errors.New(\"username is required\")\n\tErrPasswordIsRequired = errors.New(\"password is required\")\n\n\t// System errors - not a terminal, failure to read, etc\n\tErrReadingUsername        = errors.New(\"unable to read username\")\n\tErrReadingPassword        = errors.New(\"unable to read password\")\n\tErrNotATerminal           = errors.New(\"stdin is not a terminal (Hint: use `nerdctl login --username=USERNAME --password-stdin`)\")\n\tErrCannotAllocateTerminal = errors.New(\"error allocating terminal\")\n)\n\n// promptUserForAuthentication will prompt the user for credentials if needed\n// It might error with any of the errors defined above.\nfunc promptUserForAuthentication(credentials *dockerconfigresolver.Credentials, username, password string, stdout io.Writer) error {\n\tvar err error\n\n\t// If the provided username is empty...\n\tif username = strings.TrimSpace(username); username == \"\" {\n\t\t// Use the one we know of (from the store)\n\t\tusername = credentials.Username\n\t\t// If the one from the store was empty as well, prompt and read the username\n\t\tif username == \"\" {\n\t\t\t_, _ = fmt.Fprint(stdout, \"Enter Username: \")\n\t\t\tusername, err = readUsername()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tusername = strings.TrimSpace(username)\n\t\t\t// If it still is empty, that is an error\n\t\t\tif username == \"\" {\n\t\t\t\treturn ErrUsernameIsRequired\n\t\t\t}\n\t\t}\n\t}\n\n\t// If password was NOT passed along, ask for it\n\tif password == \"\" {\n\t\t_, _ = fmt.Fprint(stdout, \"Enter Password: \")\n\t\tpassword, err = readPassword()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, _ = fmt.Fprintln(stdout)\n\t\tpassword = strings.TrimSpace(password)\n\n\t\t// If nothing was provided, error out\n\t\tif password == \"\" {\n\t\t\treturn ErrPasswordIsRequired\n\t\t}\n\t}\n\n\t// Attach non-empty credentials to the auth object and return\n\tcredentials.Username = username\n\tcredentials.Password = password\n\n\treturn nil\n}\n\n// readUsername will try to read from user input\n// It might error with:\n// - ErrNotATerminal\n// - ErrReadingUsername\nfunc readUsername() (string, error) {\n\tfd := os.Stdin\n\tif !term.IsTerminal(int(fd.Fd())) {\n\t\treturn \"\", ErrNotATerminal\n\t}\n\n\tusername, err := bufio.NewReader(fd).ReadString('\\n')\n\tif err != nil {\n\t\treturn \"\", errors.Join(ErrReadingUsername, err)\n\t}\n\n\treturn strings.TrimSpace(username), nil\n}\n"
  },
  {
    "path": "pkg/cmd/login/prompt_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"syscall\"\n\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/log\"\n)\n\nfunc readPassword() (string, error) {\n\tfd := syscall.Stdin\n\tif !term.IsTerminal(fd) {\n\t\ttty, err := os.Open(\"/dev/tty\")\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Join(ErrCannotAllocateTerminal, err)\n\t\t}\n\t\tdefer func() {\n\t\t\terr = tty.Close()\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Error(\"failed closing tty\")\n\t\t\t}\n\t\t}()\n\t\tfd = int(tty.Fd())\n\t}\n\n\tbytePassword, err := term.ReadPassword(fd)\n\tif err != nil {\n\t\treturn \"\", errors.Join(ErrReadingPassword, err)\n\t}\n\n\treturn string(bytePassword), nil\n}\n"
  },
  {
    "path": "pkg/cmd/login/prompt_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage login\n\nimport (\n\t\"errors\"\n\t\"syscall\"\n\n\t\"golang.org/x/term\"\n)\n\nfunc readPassword() (string, error) {\n\tfd := int(syscall.Stdin)\n\tif !term.IsTerminal(fd) {\n\t\treturn \"\", ErrNotATerminal\n\t}\n\n\tbytePassword, err := term.ReadPassword(fd)\n\tif err != nil {\n\t\treturn \"\", errors.Join(ErrReadingPassword, err)\n\t}\n\n\treturn string(bytePassword), nil\n}\n"
  },
  {
    "path": "pkg/cmd/logout/logout.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logout\n\nimport (\n\t\"context\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n)\n\nfunc Logout(ctx context.Context, logoutServer string) (map[string]error, error) {\n\treg, err := dockerconfigresolver.Parse(logoutServer)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcredentialsStore, err := dockerconfigresolver.NewCredentialsStore(\"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn credentialsStore.Erase(reg)\n}\n\nfunc ShellCompletion() ([]string, error) {\n\tcredentialsStore, err := dockerconfigresolver.NewCredentialsStore(\"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn credentialsStore.ShellCompletion(), nil\n}\n"
  },
  {
    "path": "pkg/cmd/manifest/annotate.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifeststore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nfunc Annotate(ctx context.Context, listRef string, manifestRef string, options types.ManifestAnnotateOptions) error {\n\tparsedListRef, err := referenceutil.Parse(listRef)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse list reference: %w\", err)\n\t}\n\n\tparsedManifestRef, err := referenceutil.Parse(manifestRef)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse manifest reference: %w\", err)\n\t}\n\n\tmanifestStore, err := manifeststore.NewStore(options.GOptions.DataRoot)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create manifest store: %w\", err)\n\t}\n\n\timageManifest, err := manifestStore.Get(parsedListRef, parsedManifestRef)\n\tif err != nil {\n\t\tif errors.Is(err, store.ErrNotFound) {\n\t\t\treturn fmt.Errorf(\"manifest for image %s does not exist in %s\", manifestRef, listRef)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to get manifest: %w\", err)\n\t}\n\n\tif imageManifest.Descriptor.Platform == nil {\n\t\timageManifest.Descriptor.Platform = new(ocispec.Platform)\n\t}\n\n\tif options.Os != \"\" {\n\t\timageManifest.Descriptor.Platform.OS = options.Os\n\t}\n\n\tif options.Arch != \"\" {\n\t\timageManifest.Descriptor.Platform.Architecture = options.Arch\n\t}\n\n\tif options.Variant != \"\" {\n\t\timageManifest.Descriptor.Platform.Variant = options.Variant\n\t}\n\n\tif options.OsVersion != \"\" {\n\t\timageManifest.Descriptor.Platform.OSVersion = options.OsVersion\n\t}\n\n\tfor _, osFeature := range options.OsFeatures {\n\t\timageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)\n\t}\n\n\treturn manifestStore.Save(parsedListRef, parsedManifestRef, imageManifest)\n}\n\nfunc appendIfUnique(list []string, str string) []string {\n\tfor _, s := range list {\n\t\tif s == str {\n\t\t\treturn list\n\t\t}\n\t}\n\treturn append(list, str)\n}\n"
  },
  {
    "path": "pkg/cmd/manifest/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifeststore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\n// Create creates a local manifest list/index\nfunc Create(ctx context.Context, listRef string, manifestRefs []string, options types.ManifestCreateOptions) (string, error) {\n\tparsedListRef, err := referenceutil.Parse(listRef)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse list reference: %w\", err)\n\t}\n\n\tmanifestStore, err := manifeststore.NewStore(options.GOptions.DataRoot)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create manifest store: %w\", err)\n\t}\n\n\texistingManifests, err := manifestStore.GetList(parsedListRef)\n\tif err == nil && len(existingManifests) > 0 && !options.Amend {\n\t\treturn \"\", fmt.Errorf(\"refusing to amend an existing manifest list with no --amend flag\")\n\t}\n\n\tfor _, manifestRef := range manifestRefs {\n\t\tparsedRef, err := referenceutil.Parse(manifestRef)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to parse manifest reference %s: %w\", manifestRef, err)\n\t\t}\n\n\t\tmanifest, desc, rawData, err := manifestutil.GetManifest(ctx, parsedRef, options.GOptions, options.Insecure)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to fetch manifest %s: %w\", manifestRef, err)\n\t\t}\n\n\t\t// Check if the manifest is manifest list\n\t\tif desc.MediaType == images.MediaTypeDockerSchema2ManifestList || desc.MediaType == ocispec.MediaTypeImageIndex {\n\t\t\treturn \"\", fmt.Errorf(\"%s is a manifest list\", manifestRef)\n\t\t}\n\n\t\timageManifest, err := manifestutil.CreateManifestEntry(parsedRef, desc, rawData)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to create manifest entry for %s: %w\", manifestRef, err)\n\t\t}\n\n\t\t// Get platform information from config\n\t\tif desc.MediaType == ocispec.MediaTypeImageManifest || desc.MediaType == images.MediaTypeDockerSchema2Manifest {\n\t\t\tplatform, err := manifestutil.GetPlatform(ctx, parsedRef.Domain, options.GOptions, options.Insecure, manifestRef, manifest)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to extract platform for %s: %w\", manifestRef, err)\n\t\t\t}\n\t\t\timageManifest.Descriptor.Platform = platform\n\t\t}\n\n\t\tif err := manifestStore.Save(parsedListRef, parsedRef, &imageManifest); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to store manifest %s: %w\", manifestRef, err)\n\t\t}\n\t}\n\n\treturn parsedListRef.String(), nil\n}\n"
  },
  {
    "path": "pkg/cmd/manifest/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifesttypes\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc Inspect(ctx context.Context, rawRef string, options types.ManifestInspectOptions) ([]interface{}, error) {\n\tparsedRef, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse reference: %w\", err)\n\t}\n\n\tmanifest, desc, rawData, err := manifestutil.GetManifest(ctx, parsedRef, options.GOptions, options.Insecure)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif options.Verbose {\n\t\treturn formatVerboseOutput(ctx, parsedRef, manifest, desc, rawData, options.Insecure)\n\t}\n\n\t// Return manifest wrapped in array for formatting compatibility\n\treturn []interface{}{manifest}, nil\n}\n\n// formatVerboseOutput formats manifest data in Docker-compatible verbose format\nfunc formatVerboseOutput(ctx context.Context, parsedRef *referenceutil.ImageReference, manifest interface{}, desc ocispec.Descriptor, rawData []byte, insecure bool) ([]interface{}, error) {\n\tswitch desc.MediaType {\n\tcase ocispec.MediaTypeImageIndex:\n\t\tindex, ok := manifest.(manifesttypes.OCIIndexStruct)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"expected ocispec.Index for OCI index\")\n\t\t}\n\t\treturn verboseEntriesForManifests(ctx, parsedRef, index.Manifests, insecure)\n\n\tcase images.MediaTypeDockerSchema2ManifestList:\n\t\tdi, ok := manifest.(manifesttypes.DockerManifestListStruct)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"expected DockerManifestListStruct for Docker manifest list\")\n\t\t}\n\t\treturn verboseEntriesForManifests(ctx, parsedRef, di.Manifests, insecure)\n\n\tdefault:\n\t\tentry, err := manifestutil.CreateManifestEntry(parsedRef, desc, rawData)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []interface{}{entry}, nil\n\t}\n}\n\n// verboseEntriesForManifests fetches and formats verbose entries for a list of descriptors\nfunc verboseEntriesForManifests(ctx context.Context, parsedRef *referenceutil.ImageReference, manifests []ocispec.Descriptor, insecure bool) ([]interface{}, error) {\n\n\tresolver, err := manifestutil.CreateResolver(ctx, parsedRef.Domain, types.GlobalCommandOptions{}, insecure)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create resolver: %w\", err)\n\t}\n\n\tfetcher, err := resolver.Fetcher(ctx, parsedRef.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create fetcher: %w\", err)\n\t}\n\n\tentries := make([]interface{}, 0, len(manifests))\n\n\tfor _, mdesc := range manifests {\n\t\trc, err := fetcher.Fetch(ctx, mdesc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer rc.Close()\n\n\t\tdata, err := io.ReadAll(rc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tentry, err := manifestutil.CreateManifestEntry(parsedRef, mdesc, data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\n\treturn entries, nil\n}\n"
  },
  {
    "path": "pkg/cmd/manifest/push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifeststore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifesttypes\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc Push(ctx context.Context, listRef string, options types.ManifestPushOptions) error {\n\tparsedTargetRef, err := referenceutil.Parse(listRef)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse target reference %s: %w\", listRef, err)\n\t}\n\n\tmanifestStore, err := manifeststore.NewStore(options.GOptions.DataRoot)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create manifest store: %w\", err)\n\t}\n\n\tmanifests, err := manifestStore.GetList(parsedTargetRef)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get manifests: %w\", err)\n\t}\n\n\tif len(manifests) == 0 {\n\t\treturn fmt.Errorf(\"no manifests found for %s\", listRef)\n\t}\n\n\tresolver, err := manifestutil.CreateResolver(ctx, parsedTargetRef.Domain, options.GOptions, options.Insecure)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create resolver: %w\", err)\n\t}\n\n\tif err := pushIndividualManifests(ctx, resolver, manifests, parsedTargetRef, options); err != nil {\n\t\treturn fmt.Errorf(\"failed to push individual manifests: %w\", err)\n\t}\n\n\tmanifestList, err := buildManifestList(manifests)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to build manifest list: %w\", err)\n\t}\n\n\tdigest, err := pushManifestList(ctx, resolver, parsedTargetRef, manifestList)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to push manifest list: %w\", err)\n\t}\n\n\tfmt.Fprintln(options.Stdout, digest)\n\n\tif options.Purge {\n\t\tif err := manifestStore.Remove(parsedTargetRef); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to remove manifest list from store: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc buildManifestList(manifests []*manifesttypes.DockerManifestEntry) (manifesttypes.DockerManifestList, error) {\n\tif len(manifests) == 0 {\n\t\treturn manifesttypes.DockerManifestList{}, fmt.Errorf(\"no manifests to build list from\")\n\t}\n\n\tvar descriptors []manifesttypes.DockerManifestDescriptor\n\tuseOCIIndex := false\n\n\tfor _, manifest := range manifests {\n\t\tif manifest.Descriptor.Platform == nil ||\n\t\t\tmanifest.Descriptor.Platform.Architecture == \"\" ||\n\t\t\tmanifest.Descriptor.Platform.OS == \"\" {\n\t\t\treturn manifesttypes.DockerManifestList{}, fmt.Errorf(\"manifest %s must have an OS and Architecture to be pushed to a registry\", manifest.Ref)\n\t\t}\n\n\t\tif manifest.Descriptor.MediaType == ocispec.MediaTypeImageManifest {\n\t\t\tuseOCIIndex = true\n\t\t}\n\n\t\tdescriptors = append(descriptors, manifesttypes.DockerManifestDescriptor{\n\t\t\tMediaType: manifest.Descriptor.MediaType,\n\t\t\tSize:      manifest.Descriptor.Size,\n\t\t\tDigest:    manifest.Descriptor.Digest,\n\t\t\tPlatform:  *manifest.Descriptor.Platform,\n\t\t})\n\t}\n\tmanifestList := manifesttypes.DockerManifestList{\n\t\tSchemaVersion: 2,\n\t\tMediaType:     images.MediaTypeDockerSchema2ManifestList,\n\t\tManifests:     descriptors,\n\t}\n\tif useOCIIndex {\n\t\tmanifestList.MediaType = ocispec.MediaTypeImageIndex\n\t}\n\n\treturn manifestList, nil\n}\n\nfunc pushIndividualManifests(ctx context.Context, resolver remotes.Resolver, manifests []*manifesttypes.DockerManifestEntry, targetRef *referenceutil.ImageReference, options types.ManifestPushOptions) error {\n\ttargetDomain := targetRef.Domain\n\ttargetRepo := targetRef.Path\n\n\tfor _, manifest := range manifests {\n\t\tmanifestRef, err := referenceutil.Parse(manifest.Ref)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse manifest reference %s: %w\", manifest.Ref, err)\n\t\t}\n\n\t\tif manifestRef.Domain != targetDomain {\n\t\t\treturn fmt.Errorf(\"cannot use source images from a different registry than the target image: %s != %s\", manifestRef.Domain, targetDomain)\n\t\t}\n\n\t\tvar targetManifestRef string\n\t\tif manifestRef.Domain != targetDomain {\n\t\t\ttargetManifestRef = fmt.Sprintf(\"%s/%s@%s\", targetDomain, manifestRef.Path, manifest.Descriptor.Digest)\n\t\t} else {\n\t\t\ttargetManifestRef = fmt.Sprintf(\"%s/%s@%s\", targetDomain, targetRepo, manifest.Descriptor.Digest)\n\t\t}\n\n\t\tif err := pushManifest(ctx, resolver, targetManifestRef, manifest); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to push manifest %s: %w\", targetManifestRef, err)\n\t\t}\n\n\t\tfmt.Fprintf(options.Stdout, \"Pushed ref %s with digest: %s\\n\", targetManifestRef, manifest.Descriptor.Digest)\n\t}\n\n\treturn nil\n}\n\nfunc pushManifest(ctx context.Context, resolver remotes.Resolver, ref string, manifest *manifesttypes.DockerManifestEntry) error {\n\trawData, err := base64.StdEncoding.DecodeString(manifest.Raw)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to decode manifest data: %w\", err)\n\t}\n\n\tpusher, err := resolver.Pusher(ctx, ref)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create pusher: %w\", err)\n\t}\n\n\twriter, err := pusher.Push(ctx, manifest.Descriptor)\n\tif err != nil {\n\t\tif errdefs.IsAlreadyExists(err) || strings.Contains(err.Error(), \"already exists\") {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to create content writer: %w\", err)\n\t}\n\tdefer writer.Close()\n\n\tif _, err := writer.Write(rawData); err != nil {\n\t\treturn fmt.Errorf(\"failed to write manifest data: %w\", err)\n\t}\n\n\tif err := writer.Commit(ctx, manifest.Descriptor.Size, manifest.Descriptor.Digest); err != nil {\n\t\tif errdefs.IsAlreadyExists(err) || strings.Contains(err.Error(), \"already exists\") {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to commit manifest: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc pushManifestList(ctx context.Context, resolver remotes.Resolver, targetRef *referenceutil.ImageReference, manifestList manifesttypes.DockerManifestList) (digest.Digest, error) {\n\tdata, err := json.MarshalIndent(manifestList, \"\", \"   \")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal manifest list: %w\", err)\n\t}\n\n\tdgst := digest.FromBytes(data)\n\n\tdesc := ocispec.Descriptor{\n\t\tMediaType: manifestList.MediaType,\n\t\tSize:      int64(len(data)),\n\t\tDigest:    dgst,\n\t}\n\n\tpusher, err := resolver.Pusher(ctx, targetRef.String())\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create pusher: %w\", err)\n\t}\n\n\twriter, err := pusher.Push(ctx, desc)\n\tif err != nil {\n\t\tif errdefs.IsAlreadyExists(err) || strings.Contains(err.Error(), \"already exists\") {\n\t\t\treturn dgst, nil\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"failed to create content writer: %w\", err)\n\t}\n\tdefer writer.Close()\n\n\tif _, err := writer.Write(data); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to write manifest list data: %w\", err)\n\t}\n\n\tif err := writer.Commit(ctx, desc.Size, desc.Digest); err != nil {\n\t\tif errdefs.IsAlreadyExists(err) || strings.Contains(err.Error(), \"already exists\") {\n\t\t\treturn dgst, nil\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"failed to commit manifest list: %w\", err)\n\t}\n\n\treturn dgst, nil\n}\n"
  },
  {
    "path": "pkg/cmd/manifest/rm.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifeststore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\nfunc Remove(ctx context.Context, ref string, options types.GlobalCommandOptions) error {\n\tparsedRef, err := referenceutil.Parse(ref)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse reference: %w\", err)\n\t}\n\tmanifestStore, err := manifeststore.NewStore(options.DataRoot)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create manifest store: %w\", err)\n\t}\n\t_, err = manifestStore.GetList(parsedRef)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"not found\") {\n\t\t\treturn manifestutil.NewNoSuchManifestError(parsedRef.String())\n\t\t}\n\t\treturn err\n\t}\n\terr = manifestStore.Remove(parsedRef)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to remove manifest list: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/common.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/compose-spec/compose-go/v2/errdefs\"\n\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n)\n\nfunc objectWithLabelArgs(args []string) map[string]string {\n\tif len(args) >= 1 {\n\t\treturn labelArgs(args)\n\t}\n\treturn nil\n}\n\n// labelArgs returns a map of label key,value pairs.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/commands.go#L229-L241\nfunc labelArgs(labelStrings []string) map[string]string {\n\tlabels := make(map[string]string, len(labelStrings))\n\tfor _, label := range labelStrings {\n\t\tkey, value, ok := strings.Cut(label, \"=\")\n\t\tif !ok {\n\t\t\tvalue = \"true\"\n\t\t}\n\t\tlabels[key] = value\n\t}\n\n\treturn labels\n}\n\n// namespaceExists checks if the namespace exists\nfunc namespaceExists(ctx context.Context, store namespaces.Store, namespace string) error {\n\tnsList, err := store.List(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif slices.Contains(nsList, namespace) {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"namespace %s: %w\", namespace, errdefs.ErrNotFound)\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\nfunc Create(ctx context.Context, client *containerd.Client, namespace string, options types.NamespaceCreateOptions) error {\n\tlabelsArg := objectWithLabelArgs(options.Labels)\n\tnamespaces := client.NamespaceService()\n\treturn namespaces.Create(ctx, namespace, labelsArg)\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\nfunc Inspect(ctx context.Context, client *containerd.Client, inspectedNamespaces []string, options types.NamespaceInspectOptions) error {\n\tresult := []interface{}{}\n\twarns := []error{}\n\n\tfor _, ns := range inspectedNamespaces {\n\t\tctx = namespaces.WithNamespace(ctx, ns)\n\t\tnamespaceService := client.NamespaceService()\n\t\tif err := namespaceExists(ctx, namespaceService, ns); err != nil {\n\t\t\twarns = append(warns, err)\n\t\t\tcontinue\n\t\t}\n\t\tlabels, err := namespaceService.Labels(ctx, ns)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnsInspect := native.Namespace{\n\t\t\tName:   ns,\n\t\t\tLabels: &labels,\n\t\t}\n\t\tresult = append(result, nsInspect)\n\t}\n\tif err := formatter.FormatSlice(options.Format, options.Stdout, result); err != nil {\n\t\treturn err\n\t}\n\tfor _, warn := range warns {\n\t\tlog.G(ctx).Warn(warn)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\nfunc List(ctx context.Context, client *containerd.Client, options types.NamespaceListOptions) error {\n\tnsStore := client.NamespaceService()\n\tnsList, err := nsStore.List(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw := options.Stdout\n\tvar tmpl *template.Template\n\tnamespaceList := []namespace{}\n\tfor _, ns := range nsList {\n\t\tctx = namespaces.WithNamespace(ctx, ns)\n\t\tvar numContainers, numImages, numVolumes int\n\n\t\tcontainers, err := client.Containers(ctx)\n\t\tif err != nil {\n\t\t\tlog.L.Warn(err)\n\t\t}\n\t\tnumContainers = len(containers)\n\n\t\timages, err := client.ImageService().List(ctx)\n\t\tif err != nil {\n\t\t\tlog.L.Warn(err)\n\t\t}\n\t\tnumImages = len(images)\n\n\t\tvolStore, err := volumestore.New(dataStore, ns)\n\t\tif err != nil {\n\t\t\tlog.L.Warn(err)\n\t\t} else {\n\t\t\tnumVolumes, err = volStore.Count()\n\t\t\tif err != nil {\n\t\t\t\tlog.L.Warn(err)\n\t\t\t}\n\t\t}\n\n\t\tlabels, err := client.NamespaceService().Labels(ctx, ns)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnamespaceList = append(namespaceList, namespace{\n\t\t\tName:       ns,\n\t\t\tContainers: numContainers,\n\t\t\tImages:     numImages,\n\t\t\tVolumes:    numVolumes,\n\t\t\tLabels:     labels,\n\t\t})\n\t}\n\n\tswitch options.Format {\n\tcase \"\", \"table\", \"wide\":\n\t\tif !options.Quiet {\n\t\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\t\t// no \"NETWORKS\", because networks are global objects\n\t\t\tfmt.Fprintln(w, \"NAME\\tCONTAINERS\\tIMAGES\\tVOLUMES\\tLABELS\")\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\tif options.Quiet {\n\t\t\treturn errors.New(\"format and quiet must not be specified together\")\n\t\t}\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, namespace := range namespaceList {\n\t\tif tmpl != nil {\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, namespace); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := fmt.Fprintln(w, b.String()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if options.Quiet {\n\t\t\tif _, err := fmt.Fprintln(w, namespace.Name); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tformat := \"%s\\t%d\\t%d\\t%d\\t%v\\t\\n\"\n\t\t\tvar labelStrings []string\n\t\t\tfor k, v := range namespace.Labels {\n\t\t\t\tlabelStrings = append(labelStrings, strings.Join([]string{k, v}, \"=\"))\n\t\t\t}\n\t\t\tsort.Strings(labelStrings)\n\t\t\targs := []interface{}{}\n\t\t\targs = append(args, namespace.Name, namespace.Containers, namespace.Images, namespace.Volumes, strings.Join(labelStrings, \",\"))\n\t\t\tif _, err := fmt.Fprintf(w, format, args...); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n\ntype namespace struct {\n\tName       string\n\tContainers int\n\tImages     int\n\tVolumes    int\n\tLabels     map[string]string\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/namespace_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/containerd/containerd/v2/core/runtime/opts\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n)\n\nfunc namespaceDeleteOpts(cgroup bool) ([]namespaces.DeleteOpts, error) {\n\tvar delOpts []namespaces.DeleteOpts\n\tif cgroup {\n\t\tdelOpts = append(delOpts, opts.WithNamespaceCgroupDeletion)\n\t}\n\treturn delOpts, nil\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/namespace_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n)\n\nfunc namespaceDeleteOpts(cgroup bool) ([]namespaces.DeleteOpts, error) {\n\tvar delOpts []namespaces.DeleteOpts\n\treturn delOpts, nil\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\nfunc Remove(ctx context.Context, client *containerd.Client, deletedNamespaces []string, options types.NamespaceRemoveOptions) error {\n\tvar exitErr error\n\topts, err := namespaceDeleteOpts(options.CGroup)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnamespaces := client.NamespaceService()\n\tfor _, target := range deletedNamespaces {\n\t\tif err := namespaces.Delete(ctx, target, opts...); err != nil {\n\t\t\tif !errdefs.IsNotFound(err) {\n\t\t\t\tif exitErr == nil {\n\t\t\t\t\texitErr = fmt.Errorf(\"unable to delete %s\", target)\n\t\t\t\t}\n\t\t\t\tlog.G(ctx).WithError(err).Errorf(\"unable to delete %v\", target)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\t_, err := fmt.Fprintln(options.Stdout, target)\n\t\treturn err\n\t}\n\treturn exitErr\n}\n"
  },
  {
    "path": "pkg/cmd/namespace/update.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namespace\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\nfunc Update(ctx context.Context, client *containerd.Client, namespace string, options types.NamespaceUpdateOptions) error {\n\tlabelsArg := objectWithLabelArgs(options.Labels)\n\tnamespaces := client.NamespaceService()\n\tif err := namespaceExists(ctx, namespaces, namespace); err != nil {\n\t\treturn err\n\t}\n\tfor k, v := range labelsArg {\n\t\tif err := namespaces.SetLabel(ctx, namespace, k, v); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/network/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\nfunc Create(options types.NetworkCreateOptions, stdout io.Writer) error {\n\tif len(options.Subnets) == 0 {\n\t\tif options.Gateway != \"\" || options.IPRange != \"\" {\n\t\t\treturn fmt.Errorf(\"cannot set gateway or ip-range without subnet, specify --subnet manually\")\n\t\t}\n\t\toptions.Subnets = []string{\"\"}\n\t}\n\n\te, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))\n\tif err != nil {\n\t\treturn err\n\t}\n\tnet, err := e.CreateNetwork(options)\n\tif err != nil {\n\t\tif errdefs.IsAlreadyExists(err) {\n\t\t\treturn fmt.Errorf(\"network with name %s already exists\", options.Name)\n\t\t}\n\t\treturn err\n\t}\n\t_, err = fmt.Fprintln(stdout, *net.NerdctlID)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/cmd/network/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerinspector\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\nfunc Inspect(ctx context.Context, client *containerd.Client, options types.NetworkInspectOptions) error {\n\tif options.Mode != \"native\" && options.Mode != \"dockercompat\" {\n\t\treturn fmt.Errorf(\"unknown mode %q\", options.Mode)\n\t}\n\n\tcniEnv, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar result []interface{}\n\tnetLists, errs := cniEnv.ListNetworksMatch(options.Networks, true)\n\n\tfor req, netList := range netLists {\n\t\tif len(netList) > 1 {\n\t\t\terrs = append(errs, fmt.Errorf(\"multiple IDs found with provided prefix: %s\", req))\n\t\t\tcontinue\n\t\t}\n\t\tif len(netList) == 0 {\n\t\t\terrs = append(errs, fmt.Errorf(\"no network found matching: %s\", req))\n\t\t\tcontinue\n\t\t}\n\n\t\tnetwork := netList[0]\n\n\t\tvar filters = []string{fmt.Sprintf(`labels.%q~=\"\\\\\\\"%s\\\\\\\"\"`, labels.Networks, network.Name)}\n\t\tfilteredContainers, err := client.Containers(ctx, filters...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar containers []*native.Container\n\t\tfor _, container := range filteredContainers {\n\t\t\tnativeContainer, err := containerinspector.Inspect(ctx, container)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif nativeContainer.Process == nil || nativeContainer.Process.Status.Status != containerd.Running {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcontainers = append(containers, nativeContainer)\n\t\t}\n\n\t\tr := &native.Network{\n\t\t\tCNI:           json.RawMessage(network.Bytes),\n\t\t\tNerdctlID:     network.NerdctlID,\n\t\t\tNerdctlLabels: network.NerdctlLabels,\n\t\t\tFile:          network.File,\n\t\t\tContainers:    containers,\n\t\t}\n\t\tswitch options.Mode {\n\t\tcase \"native\":\n\t\t\tresult = append(result, r)\n\t\tcase \"dockercompat\":\n\t\t\tcompat, err := dockercompat.NetworkFromNative(r)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresult = append(result, compat)\n\t\t}\n\t}\n\n\tif len(result) > 0 {\n\t\tif formatErr := formatter.FormatSlice(options.Format, options.Stdout, result); formatErr != nil {\n\t\t\tlog.G(ctx).Error(formatErr)\n\t\t}\n\t\terr = nil\n\t} else {\n\t\terr = errors.New(\"unable to find any network matching the provided request\")\n\t}\n\n\tfor _, unErr := range errs {\n\t\tlog.G(ctx).Error(unErr)\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/cmd/network/list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\ntype networkPrintable struct {\n\tID     string // empty for non-nerdctl networks\n\tName   string\n\tLabels string\n\t// TODO: \"CreatedAt\", \"Driver\", \"IPv6\", \"Internal\", \"Scope\"\n\tfile string\n}\n\nfunc List(ctx context.Context, options types.NetworkListOptions) error {\n\tglobalOptions := options.GOptions\n\tquiet := options.Quiet\n\tformat := options.Format\n\tw := options.Stdout\n\tfilters := options.Filters\n\tvar tmpl *template.Template\n\n\tswitch format {\n\tcase \"\", \"table\", \"wide\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !quiet {\n\t\t\tfmt.Fprintln(w, \"NETWORK ID\\tNAME\\tFILE\")\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\tif quiet {\n\t\t\treturn errors.New(\"format and quiet must not be specified together\")\n\t\t}\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\te, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))\n\tif err != nil {\n\t\treturn err\n\t}\n\tnetConfigs, err := e.NetworkList()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlabelFilterFuncs, nameFilterFuncs, err := getNetworkFilterFuncs(filters)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(filters) > 0 {\n\t\tfiltered := make([]*netutil.NetworkConfig, 0)\n\t\tfor _, net := range netConfigs {\n\t\t\tif networkMatchesFilter(net, labelFilterFuncs, nameFilterFuncs) {\n\t\t\t\tfiltered = append(filtered, net)\n\t\t\t}\n\t\t}\n\t\tnetConfigs = filtered\n\t}\n\n\tpp := make([]networkPrintable, len(netConfigs))\n\tfor i, n := range netConfigs {\n\t\tp := networkPrintable{\n\t\t\tName: n.Name,\n\t\t\tfile: n.File,\n\t\t}\n\t\tif n.NerdctlID != nil {\n\t\t\tp.ID = *n.NerdctlID\n\t\t\tif len(p.ID) > 12 {\n\t\t\t\tp.ID = p.ID[:12]\n\t\t\t}\n\t\t}\n\t\tif n.NerdctlLabels != nil {\n\t\t\tp.Labels = formatter.FormatLabels(*n.NerdctlLabels)\n\t\t}\n\t\tpp[i] = p\n\t}\n\n\t// append pseudo networks\n\tif len(filters) == 0 { // filter a pseudo networks is meanless\n\t\tpp = append(pp, []networkPrintable{\n\t\t\t{\n\t\t\t\tName: \"host\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"none\",\n\t\t\t},\n\t\t}...)\n\t}\n\n\tfor _, p := range pp {\n\t\tif tmpl != nil {\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, p); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err = fmt.Fprintln(w, b.String()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if quiet {\n\t\t\tif p.ID != \"\" {\n\t\t\t\tfmt.Fprintln(w, p.ID)\n\t\t\t}\n\t\t} else {\n\t\t\tfmt.Fprintf(w, \"%s\\t%s\\t%s\\n\", p.ID, p.Name, p.file)\n\t\t}\n\t}\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n\nfunc getNetworkFilterFuncs(filters []string) ([]func(*map[string]string) bool, []func(string) bool, error) {\n\tlabelFilterFuncs := make([]func(*map[string]string) bool, 0)\n\tnameFilterFuncs := make([]func(string) bool, 0)\n\n\tfor _, filter := range filters {\n\t\tif strings.HasPrefix(filter, \"name\") || strings.HasPrefix(filter, \"label\") {\n\t\t\tfilter, value, ok := strings.Cut(filter, \"=\")\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch filter {\n\t\t\tcase \"name\":\n\t\t\t\tre, err := regexp.Compile(value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t\tnameFilterFuncs = append(nameFilterFuncs, func(name string) bool {\n\t\t\t\t\treturn re.MatchString(name)\n\t\t\t\t})\n\t\t\tcase \"label\":\n\t\t\t\tk, v, hasValue := strings.Cut(value, \"=\")\n\t\t\t\tlabelFilterFuncs = append(labelFilterFuncs, func(labels *map[string]string) bool {\n\t\t\t\t\tif labels == nil {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tval, ok := (*labels)[k]\n\t\t\t\t\tif !ok || (hasValue && val != v) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn labelFilterFuncs, nameFilterFuncs, nil\n}\n\nfunc networkMatchesFilter(net *netutil.NetworkConfig, labelFilterFuncs []func(*map[string]string) bool, nameFilterFuncs []func(string) bool) bool {\n\tfor _, labelFilterFunc := range labelFilterFuncs {\n\t\tif !labelFilterFunc(net.NerdctlLabels) {\n\t\t\treturn false\n\t\t}\n\t}\n\tfor _, nameFilterFunc := range nameFilterFuncs {\n\t\tif !nameFilterFunc(net.Name) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "pkg/cmd/network/prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc Prune(ctx context.Context, client *containerd.Client, options types.NetworkPruneOptions) error {\n\te, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tusedNetworks, err := netutil.UsedNetworks(ctx, client)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnetworkConfigs, err := e.NetworkList()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar removedNetworks []string // nolint: prealloc\n\tfor _, net := range networkConfigs {\n\t\tif strutil.InStringSlice(options.NetworkDriversToKeep, net.Name) {\n\t\t\tcontinue\n\t\t}\n\t\tif net.NerdctlID == nil || net.File == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := usedNetworks[net.Name]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tif err := e.RemoveNetwork(net); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Errorf(\"failed to remove network %s\", net.Name)\n\t\t\tcontinue\n\t\t}\n\t\tremovedNetworks = append(removedNetworks, net.Name)\n\t}\n\n\tif len(removedNetworks) > 0 {\n\t\tfmt.Fprintln(options.Stdout, \"Deleted Networks:\")\n\t\tfor _, name := range removedNetworks {\n\t\t\tfmt.Fprintln(options.Stdout, name)\n\t\t}\n\t\tfmt.Fprintln(options.Stdout, \"\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/network/remove.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage network\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\nfunc Remove(ctx context.Context, client *containerd.Client, options types.NetworkRemoveOptions) error {\n\tcniEnv, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tusedNetworkInfo, err := netutil.UsedNetworks(ctx, client)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar result []string\n\tnetLists, errs := cniEnv.ListNetworksMatch(options.Networks, false)\n\n\tfor req, netList := range netLists {\n\t\tif len(netList) > 1 {\n\t\t\terrs = append(errs, fmt.Errorf(\"multiple IDs found with provided prefix: %s\", req))\n\t\t\tcontinue\n\t\t}\n\t\tif len(netList) == 0 {\n\t\t\terrs = append(errs, fmt.Errorf(\"no network found matching: %s\", req))\n\t\t\tcontinue\n\t\t}\n\t\tnetwork := netList[0]\n\t\tif value, ok := usedNetworkInfo[network.Name]; ok {\n\t\t\terrs = append(errs, fmt.Errorf(\"network %q is in use by container %q\", req, value))\n\t\t\tcontinue\n\t\t}\n\t\tif network.Name == \"bridge\" {\n\t\t\terrs = append(errs, errors.New(\"cannot remove pre-defined network bridge\"))\n\t\t\tcontinue\n\t\t}\n\t\tif network.File == \"\" {\n\t\t\terrs = append(errs, fmt.Errorf(\"%s is a pre-defined network and cannot be removed\", req))\n\t\t\tcontinue\n\t\t}\n\t\tif network.NerdctlID == nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"%s is managed outside nerdctl and cannot be removed\", req))\n\t\t\tcontinue\n\t\t}\n\t\tif err := cniEnv.RemoveNetwork(network); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t} else {\n\t\t\tresult = append(result, req)\n\t\t}\n\t}\n\tfor _, unErr := range errs {\n\t\tlog.G(ctx).Error(unErr)\n\t}\n\tif len(result) > 0 {\n\t\tfor _, id := range result {\n\t\t\tfmt.Fprintln(options.Stdout, id)\n\t\t}\n\t\terr = nil\n\t} else {\n\t\terr = errors.New(\"no network could be removed\")\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/cmd/search/search.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage search\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\tdockerconfig \"github.com/containerd/containerd/v2/core/remotes/docker/config\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\ntype SearchResult struct {\n\tDescription string `json:\"description\"`\n\tIsOfficial  bool   `json:\"is_official\"`\n\tName        string `json:\"name\"`\n\tStarCount   int    `json:\"star_count\"`\n}\n\nfunc Search(ctx context.Context, term string, options types.SearchOptions) error {\n\t// Validate filters before making HTTP request\n\tfilterMap, err := validateAndParseFilters(options.Filters)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tregistryHost, searchTerm := splitReposSearchTerm(term)\n\n\tparsedRef, err := referenceutil.Parse(registryHost)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Debugf(\"failed to parse registry host %q, using as-is\", registryHost)\n\t} else {\n\t\tregistryHost = parsedRef.Domain\n\t}\n\n\tvar dOpts []dockerconfigresolver.Opt\n\n\tif options.GOptions.InsecureRegistry {\n\t\tlog.G(ctx).Warnf(\"skipping verifying HTTPS certs for %q\", registryHost)\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t}\n\n\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(options.GOptions.HostsDir))\n\n\thostOpts, err := dockerconfigresolver.NewHostOptions(ctx, registryHost, dOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create host options: %w\", err)\n\t}\n\n\tusername, password, err := hostOpts.Credentials(registryHost)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Debug(\"no credentials found, searching anonymously\")\n\t}\n\n\tscheme := \"https\"\n\tif hostOpts.DefaultScheme != \"\" {\n\t\tscheme = hostOpts.DefaultScheme\n\t}\n\n\tsearchURL := buildSearchURL(registryHost, searchTerm, scheme)\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", searchURL, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif username != \"\" && password != \"\" {\n\t\treq.SetBasicAuth(username, password)\n\t}\n\n\tclient := createHTTPClient(hostOpts)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn fmt.Errorf(\"search failed with status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar searchResp struct {\n\t\tResults []SearchResult `json:\"results\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil {\n\t\treturn fmt.Errorf(\"failed to decode search response: %w\", err)\n\t}\n\n\tfilteredResults := applyFilters(searchResp.Results, filterMap, options.Limit)\n\n\treturn printSearchResults(options.Stdout, filteredResults, options)\n}\n\nfunc splitReposSearchTerm(reposName string) (registryHost string, searchTerm string) {\n\tnameParts := strings.SplitN(reposName, \"/\", 2)\n\tif len(nameParts) == 1 ||\n\t\t(!strings.Contains(nameParts[0], \".\") &&\n\t\t\t!strings.Contains(nameParts[0], \":\") &&\n\t\t\tnameParts[0] != \"localhost\") {\n\t\t// No registry specified, use docker.io\n\t\t// For \"library/alpine\", the search term should be \"alpine\"\n\t\t// For \"alpine\", the search term should be \"alpine\"\n\t\tif len(nameParts) == 2 && nameParts[0] == \"library\" {\n\t\t\treturn \"docker.io\", nameParts[1]\n\t\t}\n\t\treturn \"docker.io\", reposName\n\t}\n\treturn nameParts[0], nameParts[1]\n}\n\nfunc buildSearchURL(registryHost, term, scheme string) string {\n\thost := registryHost\n\tif host == \"docker.io\" {\n\t\thost = \"index.docker.io\"\n\t}\n\n\tu := url.URL{\n\t\tScheme: scheme,\n\t\tHost:   host,\n\t\tPath:   \"/v1/search\",\n\t}\n\tq := u.Query()\n\tq.Set(\"q\", term)\n\tu.RawQuery = q.Encode()\n\n\treturn u.String()\n}\n\nfunc createHTTPClient(hostOpts *dockerconfig.HostOptions) *http.Client {\n\tif hostOpts != nil && hostOpts.DefaultTLS != nil {\n\t\treturn &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: hostOpts.DefaultTLS,\n\t\t\t},\n\t\t}\n\t}\n\treturn http.DefaultClient\n}\n\nfunc validateFilterValue(key, value string) error {\n\tswitch key {\n\tcase \"stars\":\n\t\tif _, err := strconv.Atoi(value); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid filter 'stars=%s'\", value)\n\t\t}\n\tcase \"is-official\":\n\t\tif _, err := strconv.ParseBool(value); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid filter 'is-official=%s'\", value)\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid filter '%s'\", key)\n\t}\n\treturn nil\n}\n\n// validateAndParseFilters validates and parses filters before making HTTP request\nfunc validateAndParseFilters(filters []string) (map[string]string, error) {\n\tfilterMap := make(map[string]string)\n\tfor _, f := range filters {\n\t\tparts := strings.SplitN(f, \"=\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"bad format of filter (expected name=value)\")\n\t\t}\n\t\tkey := parts[0]\n\t\tvalue := parts[1]\n\t\tif err := validateFilterValue(key, value); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfilterMap[key] = value\n\t}\n\treturn filterMap, nil\n}\n\nfunc applyFilters(results []SearchResult, filterMap map[string]string, limit int) []SearchResult {\n\tfiltered := make([]SearchResult, 0, len(results))\n\n\tfor _, r := range results {\n\t\tif val, ok := filterMap[\"is-official\"]; ok {\n\t\t\tb, _ := strconv.ParseBool(val)\n\t\t\tif b != r.IsOfficial {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif val, ok := filterMap[\"stars\"]; ok {\n\t\t\tstars, _ := strconv.Atoi(val)\n\t\t\tif r.StarCount < stars {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfiltered = append(filtered, r)\n\t}\n\n\t// Apply limit after filtering, but maintain original order from API\n\tif limit > 0 && len(filtered) > limit {\n\t\tfiltered = filtered[:limit]\n\t}\n\n\treturn filtered\n}\n\nfunc truncateDescription(desc string, noTrunc bool) string {\n\tif !noTrunc && len(desc) > 45 {\n\t\treturn formatter.Ellipsis(desc, 45)\n\t}\n\treturn desc\n}\n\nfunc printSearchResults(stdout io.Writer, results []SearchResult, options types.SearchOptions) error {\n\tfor i := range results {\n\t\tresults[i].Description = truncateDescription(results[i].Description, options.NoTrunc)\n\t}\n\n\tif options.Format != \"\" {\n\t\ttmpl, err := formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, r := range results {\n\t\t\tif err := tmpl.Execute(stdout, r); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfmt.Fprintln(stdout)\n\t\t}\n\t\treturn nil\n\t}\n\n\tw := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)\n\tfmt.Fprintln(w, \"NAME\\tDESCRIPTION\\tSTARS\\tOFFICIAL\")\n\n\tfor _, r := range results {\n\t\tdesc := strings.ReplaceAll(r.Description, \"\\n\", \" \")\n\t\tdesc = strings.ReplaceAll(desc, \"\\t\", \" \")\n\n\t\tofficial := \"\"\n\t\tif r.IsOfficial {\n\t\t\tofficial = \"[OK]\"\n\t\t}\n\t\tfmt.Fprintf(w, \"%s\\t%s\\t%d\\t%s\\n\", r.Name, desc, r.StarCount, official)\n\t}\n\treturn w.Flush()\n}\n"
  },
  {
    "path": "pkg/cmd/system/events.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"text/template\"\n\t\"time\"\n\n\t_ \"github.com/containerd/containerd/api/events\" // Register grpc event types\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/events\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/typeurl/v2\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\n// EventOut contains information about an event.\ntype EventOut struct {\n\tTimestamp time.Time\n\tID        string\n\tNamespace string\n\tTopic     string\n\tStatus    Status\n\tEvent     string\n}\n\ntype Status string\n\nconst (\n\tSTART   Status = \"start\"\n\tUNKNOWN Status = \"unknown\"\n)\n\nvar statuses = [...]Status{START, UNKNOWN}\n\nfunc isStatus(status string) bool {\n\tstatus = strings.ToLower(status)\n\n\tfor _, supportedStatus := range statuses {\n\t\tif string(supportedStatus) == status {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc TopicToStatus(topic string) Status {\n\tif strings.Contains(strings.ToLower(topic), string(START)) {\n\t\treturn START\n\t}\n\n\treturn UNKNOWN\n}\n\n// EventFilter for filtering events\ntype EventFilter func(*EventOut) bool\n\n// generateEventFilter is similar to Podman implementation:\n// https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L11\nfunc generateEventFilter(filter, filterValue string) (func(e *EventOut) bool, error) {\n\tswitch strings.ToUpper(filter) {\n\tcase \"EVENT\", \"STATUS\":\n\t\treturn func(e *EventOut) bool {\n\t\t\tif !isStatus(string(e.Status)) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn strings.EqualFold(string(e.Status), filterValue)\n\t\t}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"%s is an invalid or unsupported filter\", filter)\n}\n\n// parseFilter is similar to Podman implementation:\n// https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L96\nfunc parseFilter(filter string) (string, string, error) {\n\tfilterSplit := strings.SplitN(filter, \"=\", 2)\n\tif len(filterSplit) != 2 {\n\t\treturn \"\", \"\", fmt.Errorf(\"%s is an invalid filter\", filter)\n\t}\n\treturn filterSplit[0], filterSplit[1], nil\n}\n\n// applyFilters is similar to Podman implementation:\n// https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L106\nfunc applyFilters(event *EventOut, filterMap map[string][]EventFilter) bool {\n\tfor _, filters := range filterMap {\n\t\tmatch := false\n\t\tfor _, filter := range filters {\n\t\t\tif filter(event) {\n\t\t\t\tmatch = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !match {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// generateEventFilters is similar to Podman implementation:\n// https://github.com/containers/podman/blob/189d862d54b3824c74bf7474ddfed6de69ec5a09/libpod/events/filters.go#L11\nfunc generateEventFilters(filters []string) (map[string][]EventFilter, error) {\n\tfilterMap := make(map[string][]EventFilter)\n\tfor _, filter := range filters {\n\t\tkey, val, err := parseFilter(filter)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfilterFunc, err := generateEventFilter(key, val)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfilterSlice := filterMap[key]\n\t\tfilterSlice = append(filterSlice, filterFunc)\n\t\tfilterMap[key] = filterSlice\n\t}\n\n\treturn filterMap, nil\n}\n\n// Events is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/events/events.go\nfunc Events(ctx context.Context, client *containerd.Client, options types.SystemEventsOptions) error {\n\teventsClient := client.EventService()\n\teventsCh, errCh := eventsClient.Subscribe(ctx)\n\tvar tmpl *template.Template\n\tswitch options.Format {\n\tcase \"\":\n\t\ttmpl = nil\n\tcase \"raw\", \"table\", \"wide\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\", \\\"table\\\", and \\\"wide\\\"\")\n\tdefault:\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfilterMap, err := generateEventFilters(options.Filters)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tvar e *events.Envelope\n\t\tselect {\n\t\tcase e = <-eventsCh:\n\t\tcase err := <-errCh:\n\t\t\treturn err\n\t\t}\n\t\tif e != nil {\n\t\t\tvar out []byte\n\t\t\tvar id string\n\t\t\tif e.Event != nil {\n\t\t\t\tv, err := typeurl.UnmarshalAny(e.Event)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).Warn(\"cannot unmarshal an event from Any\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tout, err = json.Marshal(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).Warn(\"cannot marshal Any into JSON\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar data map[string]interface{}\n\t\t\terr := json.Unmarshal(out, &data)\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Warn(\"cannot marshal Any into JSON\")\n\t\t\t} else {\n\t\t\t\t_, ok := data[\"container_id\"]\n\t\t\t\tif ok {\n\t\t\t\t\tid = data[\"container_id\"].(string)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\teOut := EventOut{e.Timestamp, id, e.Namespace, e.Topic, TopicToStatus(e.Topic), string(out)}\n\t\t\tmatch := applyFilters(&eOut, filterMap)\n\t\t\tif match {\n\t\t\t\tif tmpl != nil {\n\t\t\t\t\tvar b bytes.Buffer\n\t\t\t\t\tif err := tmpl.Execute(&b, eOut); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif _, err := fmt.Fprintln(options.Stdout, b.String()+\"\\n\"); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif _, err := fmt.Fprintln(\n\t\t\t\t\t\toptions.Stdout,\n\t\t\t\t\t\te.Timestamp,\n\t\t\t\t\t\te.Namespace,\n\t\t\t\t\t\te.Topic,\n\t\t\t\t\t\tstring(out),\n\t\t\t\t\t); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/system/info.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/docker/go-units\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\n\t\"github.com/containerd/containerd/api/services/introspection/v1\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc Info(ctx context.Context, client *containerd.Client, options types.SystemInfoOptions) error {\n\tvar (\n\t\ttmpl *template.Template\n\t\terr  error\n\t)\n\tif options.Format != \"\" {\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar (\n\t\tinfoNative *native.Info\n\t\tinfoCompat *dockercompat.Info\n\t)\n\tswitch options.Mode {\n\tcase \"native\":\n\t\tdi, err := infoutil.NativeDaemonInfo(ctx, client)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfoNative, err = fulfillNativeInfo(di, options.GOptions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase \"dockercompat\":\n\t\tinfoCompat, err = infoutil.Info(ctx, client, options.GOptions.Snapshotter, options.GOptions.CgroupManager)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfoCompat.Plugins.Log = logging.Drivers()\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown mode %q\", options.Mode)\n\t}\n\n\tif tmpl != nil {\n\t\tvar x interface{} = infoNative\n\t\tif infoCompat != nil {\n\t\t\tx = infoCompat\n\t\t}\n\t\tw := options.Stdout\n\t\tif err := tmpl.Execute(w, x); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = fmt.Fprintln(w)\n\t\treturn err\n\t}\n\n\tswitch options.Mode {\n\tcase \"native\":\n\t\treturn prettyPrintInfoNative(options.Stdout, infoNative)\n\tcase \"dockercompat\":\n\t\treturn prettyPrintInfoDockerCompat(options.Stdout, options.Stderr, infoCompat, options.GOptions)\n\t}\n\treturn nil\n}\n\nfunc fulfillNativeInfo(di *native.DaemonInfo, globalOptions types.GlobalCommandOptions) (*native.Info, error) {\n\tinfo := &native.Info{\n\t\tDaemon: di,\n\t}\n\tinfo.Namespace = globalOptions.Namespace\n\tinfo.Snapshotter = globalOptions.Snapshotter\n\tinfo.CgroupManager = globalOptions.CgroupManager\n\tinfo.Rootless = rootlessutil.IsRootless()\n\treturn info, nil\n}\n\nfunc prettyPrintInfoNative(w io.Writer, info *native.Info) error {\n\tfmt.Fprintf(w, \"Namespace:          %s\\n\", info.Namespace)\n\tfmt.Fprintf(w, \"Snapshotter:        %s\\n\", info.Snapshotter)\n\tfmt.Fprintf(w, \"Cgroup Manager:     %s\\n\", info.CgroupManager)\n\tfmt.Fprintf(w, \"Rootless:           %v\\n\", info.Rootless)\n\tfmt.Fprintf(w, \"containerd Version: %s (%s)\\n\", info.Daemon.Version.Version, info.Daemon.Version.Revision)\n\tfmt.Fprintf(w, \"containerd UUID:    %s\\n\", info.Daemon.Server.UUID)\n\tvar disabledPlugins, enabledPlugins []*introspection.Plugin\n\tfor _, f := range info.Daemon.Plugins.Plugins {\n\t\tif f.InitErr == nil {\n\t\t\tenabledPlugins = append(enabledPlugins, f)\n\t\t} else {\n\t\t\tdisabledPlugins = append(disabledPlugins, f)\n\t\t}\n\t}\n\tsorter := func(x []*introspection.Plugin) func(int, int) bool {\n\t\treturn func(i, j int) bool {\n\t\t\treturn x[i].Type+\".\"+x[i].ID < x[j].Type+\".\"+x[j].ID\n\t\t}\n\t}\n\tsort.Slice(enabledPlugins, sorter(enabledPlugins))\n\tsort.Slice(disabledPlugins, sorter(disabledPlugins))\n\tfmt.Fprintln(w, \"containerd Plugins:\")\n\tfor _, f := range enabledPlugins {\n\t\tfmt.Fprintf(w, \" - %s.%s\\n\", f.Type, f.ID)\n\t}\n\tfmt.Fprintf(w, \"containerd Plugins (disabled):\\n\")\n\tfor _, f := range disabledPlugins {\n\t\tfmt.Fprintf(w, \" - %s.%s\\n\", f.Type, f.ID)\n\t}\n\treturn nil\n}\n\nfunc prettyPrintInfoDockerCompat(stdout io.Writer, stderr io.Writer, info *dockercompat.Info, globalOptions types.GlobalCommandOptions) error {\n\tw := stdout\n\tdebug := globalOptions.Debug\n\tfmt.Fprintf(w, \"Client:\\n\")\n\tfmt.Fprintf(w, \" Namespace:\\t%s\\n\", globalOptions.Namespace)\n\tfmt.Fprintf(w, \" Debug Mode:\\t%v\\n\", debug)\n\tfmt.Fprintln(w)\n\tfmt.Fprintf(w, \"Server:\\n\")\n\tfmt.Fprintf(w, \" Server Version: %s\\n\", info.ServerVersion)\n\t// Storage Driver is not really Server concept for nerdctl, but mimics `docker info` output\n\tfmt.Fprintf(w, \" Storage Driver: %s\\n\", info.Driver)\n\tfmt.Fprintf(w, \" Logging Driver: %s\\n\", info.LoggingDriver)\n\tprintF(w, \" Cgroup Driver: \", info.CgroupDriver)\n\tprintF(w, \" Cgroup Version: \", info.CgroupVersion)\n\tfmt.Fprintf(w, \" Plugins:\\n\")\n\tfmt.Fprintf(w, \"  Log:     %s\\n\", strings.Join(info.Plugins.Log, \" \"))\n\tfmt.Fprintf(w, \"  Storage: %s\\n\", strings.Join(info.Plugins.Storage, \" \"))\n\n\t// print Security options\n\tprintSecurityOptions(w, info.SecurityOptions)\n\n\tfmt.Fprintf(w, \" Kernel Version:   %s\\n\", info.KernelVersion)\n\tfmt.Fprintf(w, \" Operating System: %s\\n\", info.OperatingSystem)\n\tfmt.Fprintf(w, \" OSType:           %s\\n\", info.OSType)\n\tfmt.Fprintf(w, \" Architecture:     %s\\n\", info.Architecture)\n\tfmt.Fprintf(w, \" CPUs:             %d\\n\", info.NCPU)\n\tfmt.Fprintf(w, \" Total Memory:     %s\\n\", units.BytesSize(float64(info.MemTotal)))\n\tfmt.Fprintf(w, \" Name:             %s\\n\", info.Name)\n\tfmt.Fprintf(w, \" ID:               %s\\n\", info.ID)\n\n\tfmt.Fprintln(w)\n\tif len(info.Warnings) > 0 {\n\t\tfmt.Fprintln(stderr, strings.Join(info.Warnings, \"\\n\"))\n\t}\n\treturn nil\n}\n\nfunc printF(w io.Writer, label string, dockerCompatInfo string) {\n\tif dockerCompatInfo == \"\" {\n\t\treturn\n\t}\n\tfmt.Fprintf(w, \"%s%s\\n\", label, dockerCompatInfo)\n}\n\nfunc printSecurityOptions(w io.Writer, securityOptions []string) {\n\tif len(securityOptions) == 0 {\n\t\treturn\n\t}\n\n\tfmt.Fprintf(w, \" Security Options:\\n\")\n\tfor _, s := range securityOptions {\n\t\tm, err := strutil.ParseCSVMap(s)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"unparsable security option %q\", s)\n\t\t\tcontinue\n\t\t}\n\t\tname := m[\"name\"]\n\t\tif name == \"\" {\n\t\t\tlog.L.Warnf(\"unparsable security option %q\", s)\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Fprintf(w, \"  %s\\n\", name)\n\t\tfor k, v := range m {\n\t\t\tif k == \"name\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"   %s: %s\\n\", cases.Title(language.English).String(k), v)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/system/prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage system\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/builder\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/container\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/network\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/volume\"\n)\n\n// Prune will remove all unused containers, networks,\n// images (dangling only or both dangling and unreferenced), and optionally, volumes.\nfunc Prune(ctx context.Context, client *containerd.Client, options types.SystemPruneOptions) error {\n\tif err := container.Prune(ctx, client, types.ContainerPruneOptions{\n\t\tGOptions: options.GOptions,\n\t\tStdout:   options.Stdout,\n\t}); err != nil {\n\t\treturn err\n\t}\n\tif err := network.Prune(ctx, client, types.NetworkPruneOptions{\n\t\tGOptions:             options.GOptions,\n\t\tNetworkDriversToKeep: options.NetworkDriversToKeep,\n\t\tStdout:               options.Stdout,\n\t}); err != nil {\n\t\treturn err\n\t}\n\tif options.Volumes {\n\t\tif err := volume.Prune(ctx, client, types.VolumePruneOptions{\n\t\t\tGOptions: options.GOptions,\n\t\t\tAll:      false,\n\t\t\tForce:    true,\n\t\t\tStdout:   options.Stdout,\n\t\t}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := image.Prune(ctx, client, types.ImagePruneOptions{\n\t\tStdout:   options.Stdout,\n\t\tGOptions: options.GOptions,\n\t\tAll:      options.All,\n\t}); err != nil {\n\t\treturn nil\n\t}\n\n\tif options.BuildKitHost != \"\" {\n\t\tprunedObjects, err := builder.Prune(ctx, types.BuilderPruneOptions{\n\t\t\tStderr:       options.Stderr,\n\t\t\tGOptions:     options.GOptions,\n\t\t\tAll:          options.All,\n\t\t\tBuildKitHost: options.BuildKitHost,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(prunedObjects) > 0 {\n\t\t\tfmt.Fprintln(options.Stdout, \"Deleted build cache objects:\")\n\t\t\tfor _, item := range prunedObjects {\n\t\t\t\tfmt.Fprintln(options.Stdout, item.ID)\n\t\t\t}\n\t\t}\n\t}\n\n\t// TODO: print total reclaimed space\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/volume/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/docker/docker/pkg/stringid\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nfunc Create(name string, options types.VolumeCreateOptions) (*native.Volume, error) {\n\tif name == \"\" {\n\t\tname = stringid.GenerateRandomID()\n\t\toptions.Labels = append(options.Labels, labels.AnonymousVolumes+\"=\")\n\t}\n\tvolStore, err := Store(options.GOptions.Namespace, options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlabels := strutil.DedupeStrSlice(options.Labels)\n\tvol, err := volStore.Create(name, labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfmt.Fprintln(options.Stdout, name)\n\treturn vol, nil\n}\n"
  },
  {
    "path": "pkg/cmd/volume/inspect.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n)\n\nfunc Inspect(ctx context.Context, volumes []string, options types.VolumeInspectOptions) error {\n\tvolStore, err := Store(options.GOptions.Namespace, options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresult := []interface{}{}\n\n\twarns := []error{}\n\tfor _, name := range volumes {\n\t\tvar vol, err = volStore.Get(name, options.Size)\n\t\tif err != nil {\n\t\t\twarns = append(warns, err)\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, vol)\n\t}\n\terr = formatter.FormatSlice(options.Format, options.Stdout, result)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, warn := range warns {\n\t\tlog.G(ctx).Warn(warn)\n\t}\n\n\tif len(warns) != 0 {\n\t\treturn errors.New(\"some volumes could not be inspected\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/volume/list.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"text/template\"\n\n\t\"github.com/containerd/containerd/v2/pkg/progress\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\ntype volumePrintable struct {\n\tDriver     string\n\tLabels     string\n\tMountpoint string\n\tName       string\n\tScope      string\n\tSize       string\n\t// TODO: \"Links\"\n}\n\nfunc List(options types.VolumeListOptions) error {\n\tif options.Quiet && options.Size {\n\t\tlog.L.Warn(\"cannot use --size and --quiet together, ignoring --size\")\n\t\toptions.Size = false\n\t}\n\tsizeFilter := hasSizeFilter(options.Filters)\n\tif sizeFilter && options.Quiet {\n\t\tlog.L.Warn(\"cannot use --filter=size and --quiet together, ignoring --filter=size\")\n\t\toptions.Filters = removeSizeFilters(options.Filters)\n\t}\n\tif sizeFilter && !options.Size {\n\t\tlog.L.Warn(\"should use --filter=size and --size together\")\n\t\toptions.Size = true\n\t}\n\n\tvols, err := Volumes(\n\t\toptions.GOptions.Namespace,\n\t\toptions.GOptions.DataRoot,\n\t\toptions.GOptions.Address,\n\t\toptions.Size,\n\t\toptions.Filters,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn lsPrintOutput(vols, options)\n}\n\nfunc hasSizeFilter(filters []string) bool {\n\tfor _, filter := range filters {\n\t\tif strings.HasPrefix(filter, \"size\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc removeSizeFilters(filters []string) []string {\n\tvar res []string\n\tfor _, filter := range filters {\n\t\tif !strings.HasPrefix(filter, \"size\") {\n\t\t\tres = append(res, filter)\n\t\t}\n\t}\n\treturn res\n}\n\nfunc lsPrintOutput(vols map[string]native.Volume, options types.VolumeListOptions) error {\n\tw := options.Stdout\n\tvar tmpl *template.Template\n\tswitch options.Format {\n\tcase \"\", \"table\", \"wide\":\n\t\tw = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)\n\t\tif !options.Quiet {\n\t\t\tif options.Size {\n\t\t\t\tfmt.Fprintln(w, \"VOLUME NAME\\tDIRECTORY\\tSIZE\")\n\t\t\t} else {\n\t\t\t\tfmt.Fprintln(w, \"VOLUME NAME\\tDIRECTORY\")\n\t\t\t}\n\t\t}\n\tcase \"raw\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\"\")\n\tdefault:\n\t\tif options.Quiet {\n\t\t\treturn errors.New(\"format and quiet must not be specified together\")\n\t\t}\n\t\tvar err error\n\t\ttmpl, err = formatter.ParseTemplate(options.Format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, v := range vols {\n\t\tp := volumePrintable{\n\t\t\tDriver:     \"local\",\n\t\t\tLabels:     \"\",\n\t\t\tMountpoint: v.Mountpoint,\n\t\t\tName:       v.Name,\n\t\t\tScope:      \"local\",\n\t\t}\n\t\tif v.Labels != nil {\n\t\t\tp.Labels = formatter.FormatLabels(*v.Labels)\n\t\t}\n\t\tif options.Size {\n\t\t\tp.Size = progress.Bytes(v.Size).String()\n\t\t}\n\t\tif tmpl != nil {\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, p); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := fmt.Fprintln(w, b.String()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if options.Quiet {\n\t\t\tfmt.Fprintln(w, p.Name)\n\t\t} else if options.Size {\n\t\t\tfmt.Fprintf(w, \"%s\\t%s\\t%s\\n\", p.Name, p.Mountpoint, p.Size)\n\t\t} else {\n\t\t\tfmt.Fprintf(w, \"%s\\t%s\\n\", p.Name, p.Mountpoint)\n\t\t}\n\t}\n\tif f, ok := w.(formatter.Flusher); ok {\n\t\treturn f.Flush()\n\t}\n\treturn nil\n}\n\n// Volumes returns volumes that match the given filters.\n//\n// Supported filters:\n//   - label=<key>=<value>: Match volumes by label on both key and value.\n//     If value is left empty, match all volumes with key regardless of its value.\n//   - name=<value>: Match all volumes with a name containing the value string.\n//   - size=<value>: Match all volumes with a size meets the value.\n//     Size operand can be >=, <=, >, <, = and value must be an integer.\n//\n// Unsupported filters:\n//   - dangling=true: Filter volumes by dangling.\n//   - driver=local: Filter volumes by driver.\nfunc Volumes(ns string, dataRoot string, address string, volumeSize bool, filters []string) (map[string]native.Volume, error) {\n\tvolStore, err := Store(ns, dataRoot, address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvols, err := volStore.List(volumeSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlabelFilterFuncs, nameFilterFuncs, sizeFilterFuncs, isFilter, err := getVolumeFilterFuncs(filters)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !isFilter {\n\t\treturn vols, nil\n\t}\n\tfor k, v := range vols {\n\t\tif !volumeMatchesFilter(v, labelFilterFuncs, nameFilterFuncs, sizeFilterFuncs) {\n\t\t\tdelete(vols, k)\n\t\t}\n\t}\n\treturn vols, nil\n}\n\nfunc getVolumeFilterFuncs(filters []string) ([]func(*map[string]string) bool, []func(string) bool, []func(int64) bool, bool, error) {\n\tisFilter := len(filters) > 0\n\tlabelFilterFuncs := make([]func(*map[string]string) bool, 0)\n\tnameFilterFuncs := make([]func(string) bool, 0)\n\tsizeFilterFuncs := make([]func(int64) bool, 0)\n\tsizeOperators := []struct {\n\t\tOperand string\n\t\tCompare func(int64, int64) bool\n\t}{\n\t\t{\">=\", func(size, volumeSize int64) bool {\n\t\t\treturn volumeSize >= size\n\t\t}},\n\t\t{\"<=\", func(size, volumeSize int64) bool {\n\t\t\treturn volumeSize <= size\n\t\t}},\n\t\t{\">\", func(size, volumeSize int64) bool {\n\t\t\treturn volumeSize > size\n\t\t}},\n\t\t{\"<\", func(size, volumeSize int64) bool {\n\t\t\treturn volumeSize < size\n\t\t}},\n\t\t{\"=\", func(size, volumeSize int64) bool {\n\t\t\treturn volumeSize == size\n\t\t}},\n\t}\n\tfor _, filter := range filters {\n\t\tif strings.HasPrefix(filter, \"name\") || strings.HasPrefix(filter, \"label\") {\n\t\t\tfilter, value, ok := strings.Cut(filter, \"=\")\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch filter {\n\t\t\tcase \"name\":\n\t\t\t\tre, err := regexp.Compile(value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, nil, nil, false, err\n\t\t\t\t}\n\t\t\t\tnameFilterFuncs = append(nameFilterFuncs, func(name string) bool {\n\t\t\t\t\treturn re.MatchString(name)\n\t\t\t\t})\n\t\t\tcase \"label\":\n\t\t\t\tk, v, hasValue := strings.Cut(value, \"=\")\n\t\t\t\tlabelFilterFuncs = append(labelFilterFuncs, func(labels *map[string]string) bool {\n\t\t\t\t\tif labels == nil {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tval, ok := (*labels)[k]\n\t\t\t\t\tif !ok || (hasValue && val != v) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(filter, \"size\") {\n\t\t\tfor _, sizeOperator := range sizeOperators {\n\t\t\t\tif subs := strings.SplitN(filter, sizeOperator.Operand, 2); len(subs) == 2 {\n\t\t\t\t\tv, err := strconv.Atoi(subs[1])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, nil, false, err\n\t\t\t\t\t}\n\t\t\t\t\tsizeFilterFuncs = append(sizeFilterFuncs, func(size int64) bool {\n\t\t\t\t\t\treturn sizeOperator.Compare(int64(v), size)\n\t\t\t\t\t})\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn labelFilterFuncs, nameFilterFuncs, sizeFilterFuncs, isFilter, nil\n}\n\nfunc volumeMatchesFilter(vol native.Volume, labelFilterFuncs []func(*map[string]string) bool, nameFilterFuncs []func(string) bool, sizeFilterFuncs []func(int64) bool) bool {\n\tfor _, labelFilterFunc := range labelFilterFuncs {\n\t\tif !labelFilterFunc(vol.Labels) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif !anyMatch(vol.Name, nameFilterFuncs) {\n\t\treturn false\n\t}\n\n\tfor _, sizeFilterFunc := range sizeFilterFuncs {\n\t\tif !sizeFilterFunc(vol.Size) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc anyMatch[T any](vol T, filters []func(T) bool) bool {\n\tif len(filters) == 0 {\n\t\treturn true\n\t}\n\tfor _, f := range filters {\n\t\tif f(vol) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/cmd/volume/prune.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc Prune(ctx context.Context, client *containerd.Client, options types.VolumePruneOptions) error {\n\t// Get the volume store and lock it until we are done.\n\t// This will prevent racing new containers from being created or removed until we are done with the cleanup of volumes\n\tvolStore, err := Store(options.GOptions.Namespace, options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar toRemove []string // nolint: prealloc\n\n\terr = volStore.Prune(func(volumes []*native.Volume) ([]string, error) {\n\t\t// Get containers and see which volumes are used\n\t\tcontainers, err := client.Containers(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tusedVolumesList, err := usedVolumes(ctx, containers)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, volume := range volumes {\n\t\t\tif _, ok := usedVolumesList[volume.Name]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !options.All {\n\t\t\t\tif volume.Labels == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tval, ok := (*volume.Labels)[labels.AnonymousVolumes]\n\t\t\t\t// skip the named volume and only remove the anonymous volume\n\t\t\t\tif !ok || val != \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\ttoRemove = append(toRemove, volume.Name)\n\t\t}\n\n\t\treturn toRemove, nil\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(toRemove) > 0 {\n\t\tfmt.Fprintln(options.Stdout, \"Deleted Volumes:\")\n\t\tfmt.Fprintln(options.Stdout, strings.Join(toRemove, \"\\n\"))\n\t\tfmt.Fprintln(options.Stdout, \"\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/volume/rm.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil\"\n)\n\nfunc Remove(ctx context.Context, client *containerd.Client, volumes []string, options types.VolumeRemoveOptions) error {\n\tvolStore, err := Store(options.GOptions.Namespace, options.GOptions.DataRoot, options.GOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcontainers, err := client.Containers(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Note: to avoid racy behavior, this is called by volStore.Remove *inside a lock*\n\tremovableVolumes := func() (volumeNames []string, cannotRemove []error, err error) {\n\t\tusedVolumesList, err := usedVolumes(ctx, containers)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tfor _, name := range volumes {\n\t\t\tif _, ok := usedVolumesList[name]; ok {\n\t\t\t\tcannotRemove = append(cannotRemove, fmt.Errorf(\"volume %q is in use (%w)\", name, errdefs.ErrFailedPrecondition))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvolumeNames = append(volumeNames, name)\n\t\t}\n\n\t\treturn volumeNames, cannotRemove, nil\n\t}\n\n\tremovedNames, cannotRemove, err := volStore.Remove(removableVolumes)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Otherwise, output on stdout whatever was successful\n\tfor _, name := range removedNames {\n\t\tfmt.Fprintln(options.Stdout, name)\n\t}\n\t// Log the rest\n\tfor _, volErr := range cannotRemove {\n\t\tlog.G(ctx).Warn(volErr)\n\t}\n\tif len(cannotRemove) > 0 {\n\t\treturn errors.New(\"some volumes could not be removed\")\n\t}\n\treturn nil\n}\n\nfunc usedVolumes(ctx context.Context, containers []containerd.Container) (map[string]struct{}, error) {\n\tusedVolumesList := make(map[string]struct{})\n\tfor _, c := range containers {\n\t\tl, err := c.Labels(ctx)\n\t\tif err != nil {\n\t\t\t// Containerd note: there is no guarantee that the containers we got from the list still exist at this point\n\t\t\t// If that is the case, just ignore and move on\n\t\t\tif errors.Is(err, errdefs.ErrNotFound) {\n\t\t\t\tlog.G(ctx).Debugf(\"container %q is gone - ignoring\", c.ID())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tmountsJSON, ok := l[labels.Mounts]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar mounts []dockercompat.MountPoint\n\t\terr = json.Unmarshal([]byte(mountsJSON), &mounts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, m := range mounts {\n\t\t\tif m.Type == mountutil.Volume {\n\t\t\t\tusedVolumesList[m.Name] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn usedVolumesList, nil\n}\n"
  },
  {
    "path": "pkg/cmd/volume/volume.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage volume\n\nimport (\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\n// Store returns a volume store\n// that corresponds to a directory like `/var/lib/nerdctl/1935db59/volumes/default`\nfunc Store(ns string, dataRoot string, address string) (volumestore.VolumeStore, error) {\n\tdataStore, err := clientutil.DataStore(dataRoot, address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn volumestore.New(dataStore, ns)\n}\n"
  },
  {
    "path": "pkg/composer/build.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n)\n\ntype BuildOptions struct {\n\tArgs     []string // --build-arg strings\n\tNoCache  bool\n\tProgress string\n}\n\nfunc (c *Composer) Build(ctx context.Context, bo BuildOptions, services []string) error {\n\treturn c.project.ForEachService(services, func(names string, svc *types.ServiceConfig) error {\n\t\tps, err := serviceparser.Parse(c.project, *svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif ps.Build != nil {\n\t\t\treturn c.buildServiceImage(ctx, ps.Image, ps.Build, ps.Unparsed.Platform, bo)\n\t\t}\n\t\treturn nil\n\t}, types.IgnoreDependencies)\n}\n\nfunc (c *Composer) buildServiceImage(ctx context.Context, image string, b *serviceparser.Build, platform string, bo BuildOptions) error {\n\tlog.G(ctx).Infof(\"Building image %s\", image)\n\n\tvar args []string // nolint: prealloc\n\tif platform != \"\" {\n\t\targs = append(args, \"--platform=\"+platform)\n\t}\n\tfor _, a := range bo.Args {\n\t\targs = append(args, \"--build-arg=\"+a)\n\t}\n\tif bo.NoCache {\n\t\targs = append(args, \"--no-cache\")\n\t}\n\tif bo.Progress != \"\" {\n\t\targs = append(args, \"--progress=\"+bo.Progress)\n\t}\n\n\tif b.DockerfileInline != \"\" {\n\t\t// if DockerfileInline is specified, write it to a temporary file\n\t\t// and use -f flag to use that docker file with project's ctxdir\n\t\ttmpFile, err := os.CreateTemp(\"\", \"inline-dockerfile-*.Dockerfile\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create temp file for DockerfileInline: %w\", err)\n\t\t}\n\t\tdefer os.Remove(tmpFile.Name())\n\t\tdefer tmpFile.Close()\n\n\t\tif _, err := tmpFile.Write([]byte(b.DockerfileInline)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write DockerfileInline: %w\", err)\n\t\t}\n\t\tb.BuildArgs = append(b.BuildArgs, \"-f=\"+tmpFile.Name())\n\t}\n\n\targs = append(args, b.BuildArgs...)\n\n\tcmd := c.createNerdctlCmd(ctx, append([]string{\"build\"}, args...)...)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"error while building image %s: %w\", image, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/composer.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os/exec\"\n\n\tcomposecli \"github.com/compose-spec/compose-go/v2/cli\"\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/reflectutil\"\n)\n\n// Options groups the command line options recommended for a Compose implementation (ProjectOptions) and extra options for nerdctl\ntype Options struct {\n\tProject          string // empty for default\n\tProjectDirectory string\n\tConfigPaths      []string\n\tProfiles         []string\n\tServices         []string\n\tEnvFile          string\n\tNerdctlCmd       string\n\tNerdctlArgs      []string\n\tNetworkInUse     func(ctx context.Context, netName string) (bool, error)\n\tNetworkExists    func(string) (bool, error)\n\tVolumeExists     func(string) (bool, error)\n\tImageExists      func(ctx context.Context, imageName string) (bool, error)\n\tEnsureImage      func(ctx context.Context, imageName, pullMode, platform string, ps *serviceparser.Service, quiet bool) error\n\tDebugPrintFull   bool // full debug print, may leak secret env var to logs\n\tExperimental     bool // enable experimental features\n\tIPFSAddress      string\n}\n\nfunc New(o Options, client *containerd.Client, cfg *config.Config) (*Composer, error) {\n\tif o.NerdctlCmd == \"\" {\n\t\treturn nil, errors.New(\"got empty nerdctl cmd\")\n\t}\n\tif o.NetworkExists == nil || o.VolumeExists == nil || o.EnsureImage == nil {\n\t\treturn nil, errors.New(\"got empty functions\")\n\t}\n\n\tif o.Project != \"\" {\n\t\tif err := identifiers.ValidateDockerCompat(o.Project); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid project name: %w\", err)\n\t\t}\n\t}\n\n\tvar optionsFn []composecli.ProjectOptionsFn\n\toptionsFn = append(optionsFn,\n\t\tcomposecli.WithOsEnv,\n\t\tcomposecli.WithWorkingDirectory(o.ProjectDirectory),\n\t)\n\tif o.EnvFile != \"\" {\n\t\toptionsFn = append(optionsFn,\n\t\t\tcomposecli.WithEnvFiles(o.EnvFile),\n\t\t)\n\t}\n\toptionsFn = append(optionsFn,\n\t\tcomposecli.WithConfigFileEnv,\n\t\tcomposecli.WithDefaultConfigPath,\n\t\tcomposecli.WithEnvFiles(),\n\t\tcomposecli.WithDotEnv,\n\t\tcomposecli.WithName(o.Project),\n\t\tcomposecli.WithProfiles(o.Profiles),\n\t)\n\n\tprojectOptions, err := composecli.NewProjectOptions(o.ConfigPaths, optionsFn...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproject, err := projectOptions.LoadProject(context.TODO())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif o.DebugPrintFull {\n\t\tprojectJSON, _ := json.MarshalIndent(project, \"\", \"    \")\n\t\tlog.L.Debug(\"printing project JSON\")\n\t\tlog.L.Debugf(\"%s\", projectJSON)\n\t}\n\n\tif unknown := reflectutil.UnknownNonEmptyFields(project,\n\t\t\"Name\",\n\t\t\"WorkingDir\",\n\t\t\"Environment\",\n\t\t\"Services\",\n\t\t\"Networks\",\n\t\t\"Volumes\",\n\t\t\"Secrets\",\n\t\t\"Configs\",\n\t\t\"ComposeFiles\"); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: %+v\", unknown)\n\t}\n\n\tc := &Composer{\n\t\tOptions: o,\n\t\tproject: project,\n\t\tclient:  client,\n\t\tconfig:  cfg,\n\t}\n\n\treturn c, nil\n}\n\ntype Composer struct {\n\tOptions\n\tproject *compose.Project\n\tclient  *containerd.Client\n\tconfig  *config.Config\n}\n\nfunc (c *Composer) createNerdctlCmd(ctx context.Context, args ...string) *exec.Cmd {\n\treturn exec.CommandContext(ctx, c.NerdctlCmd, append(c.NerdctlArgs, args...)...)\n}\n\nfunc (c *Composer) runNerdctlCmd(ctx context.Context, args ...string) error {\n\tcmd := c.createNerdctlCmd(ctx, args...)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\tif out, err := cmd.CombinedOutput(); err != nil {\n\t\treturn fmt.Errorf(\"error while executing %v: %q: %w\", cmd.Args, string(out), err)\n\t}\n\treturn nil\n}\n\n// Services returns the parsed Service objects in dependency order.\nfunc (c *Composer) Services(ctx context.Context, svcs ...string) ([]*serviceparser.Service, error) {\n\tvar services []*serviceparser.Service\n\n\tif err := c.project.ForEachService(svcs, func(name string, svc *compose.ServiceConfig) error {\n\t\tparsed, err := serviceparser.Parse(c.project, *svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tservices = append(services, parsed)\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\treturn services, nil\n}\n\n// ServiceNames returns service names in dependency order.\nfunc (c *Composer) ServiceNames(svcs ...string) ([]string, error) {\n\tvar names []string\n\tif err := c.project.ForEachService(svcs, func(name string, svc *compose.ServiceConfig) error {\n\t\tnames = append(names, svc.Name)\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\treturn names, nil\n}\n"
  },
  {
    "path": "pkg/composer/config.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/docker/compose/blob/v2.2.2/pkg/compose/hash.go\n   Copyright (C) Docker Compose authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/docker/compose/blob/v2.2.2/NOTICE\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\t\"github.com/opencontainers/go-digest\"\n\t\"go.yaml.in/yaml/v3\"\n)\n\ntype ConfigOptions struct {\n\tServices bool\n\tVolumes  bool\n\tHash     string\n}\n\nfunc (c *Composer) Config(ctx context.Context, w io.Writer, co ConfigOptions) error {\n\tif co.Services {\n\t\tfor _, service := range c.project.Services {\n\t\t\tfmt.Fprintln(w, service.Name)\n\t\t}\n\t\treturn nil\n\t}\n\tif co.Volumes {\n\t\tfor volume := range c.project.Volumes {\n\t\t\tfmt.Fprintln(w, volume)\n\t\t}\n\t\treturn nil\n\t}\n\tif co.Hash != \"\" {\n\t\tvar services []string\n\t\tif co.Hash != \"*\" {\n\t\t\tservices = strings.Split(co.Hash, \",\")\n\t\t}\n\t\treturn c.project.ForEachService(services, func(names string, svc *types.ServiceConfig) error {\n\t\t\thash, err := ServiceHash(*svc)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = fmt.Fprintf(w, \"%s %s\\n\", svc.Name, hash)\n\t\t\treturn err\n\t\t})\n\t}\n\tprojectYAML, err := yaml.Marshal(c.project)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Fprintf(w, \"%s\", projectYAML)\n\treturn nil\n}\n\n// ServiceHash is from https://github.com/docker/compose/blob/v2.2.2/pkg/compose/hash.go#L28-L38\nfunc ServiceHash(o types.ServiceConfig) (string, error) {\n\t// remove the Build config when generating the service hash\n\to.Build = nil\n\to.PullPolicy = \"\"\n\to.Scale = new(int)\n\t*(o.Scale) = 1\n\tbytes, err := json.Marshal(o)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn digest.SHA256.FromBytes(bytes).Encoded(), nil\n}\n"
  },
  {
    "path": "pkg/composer/container.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc (c *Composer) Containers(ctx context.Context, services ...string) ([]containerd.Container, error) {\n\tprojectLabel := fmt.Sprintf(\"labels.%q==%s\", labels.ComposeProject, c.project.Name)\n\tfilters := []string{}\n\tfor _, service := range services {\n\t\tfilters = append(filters, fmt.Sprintf(\"%s,labels.%q==%s\", projectLabel, labels.ComposeService, service))\n\t}\n\tif len(services) == 0 {\n\t\tfilters = append(filters, projectLabel)\n\t}\n\tlog.G(ctx).Debugf(\"filters: %v\", filters)\n\tcontainers, err := c.client.Containers(ctx, filters...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn containers, nil\n}\n\nfunc (c *Composer) containerExists(ctx context.Context, name, service string) (bool, error) {\n\t// get list of containers for service\n\tcontainers, err := c.Containers(ctx, service)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, container := range containers {\n\t\tcontainerLabels, err := container.Labels(ctx)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif name == containerLabels[labels.Name] {\n\t\t\t// container exists\n\t\t\treturn true, nil\n\t\t}\n\t}\n\t// container doesn't exist\n\treturn false, nil\n}\n\nfunc (c *Composer) containerID(ctx context.Context, name, service string) (string, error) {\n\t// get list of containers for service\n\tcontainers, err := c.Containers(ctx, service)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tfor _, container := range containers {\n\t\tcontainerLabels, err := container.Labels(ctx)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif name == containerLabels[labels.Name] {\n\t\t\t// container exists\n\t\t\treturn container.ID(), nil\n\t\t}\n\t}\n\t// container doesn't exist\n\treturn \"\", nil\n}\n"
  },
  {
    "path": "pkg/composer/copy.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/docker/docker/pkg/system\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\ntype CopyOptions struct {\n\tSource      string\n\tDestination string\n\tIndex       int\n\tFollowLink  bool\n\tDryRun      bool\n}\n\ntype copyDirection int\n\nconst (\n\tfromService copyDirection = 0\n\ttoService   copyDirection = 1\n)\n\nfunc (c *Composer) Copy(ctx context.Context, co CopyOptions) error {\n\tsrcService, srcPath := splitCpArg(co.Source)\n\tdestService, dstPath := splitCpArg(co.Destination)\n\tvar serviceName string\n\tvar direction copyDirection\n\n\tif srcService != \"\" && destService != \"\" {\n\t\treturn errors.New(\"copying between services is not supported\")\n\t}\n\tif srcService == \"\" && destService == \"\" {\n\t\treturn errors.New(\"unknown copy direction\")\n\t}\n\n\tif srcService != \"\" {\n\t\tdirection = fromService\n\t\tserviceName = srcService\n\t}\n\tif destService != \"\" {\n\t\tdirection = toService\n\t\tserviceName = destService\n\t}\n\n\tcontainers, err := c.listContainersTargetedForCopy(ctx, co.Index, direction, serviceName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, container := range containers {\n\t\targs := []string{\"cp\"}\n\t\tif co.FollowLink {\n\t\t\targs = append(args, \"--follow-link\")\n\t\t}\n\t\tif direction == fromService {\n\t\t\targs = append(args, fmt.Sprintf(\"%s:%s\", container.ID(), srcPath), dstPath)\n\t\t}\n\t\tif direction == toService {\n\t\t\targs = append(args, srcPath, fmt.Sprintf(\"%s:%s\", container.ID(), dstPath))\n\t\t}\n\t\terr := c.logCopyMsg(ctx, container, direction, srcService, srcPath, destService, dstPath, co.DryRun)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !co.DryRun {\n\t\t\tif err := c.runNerdctlCmd(ctx, args...); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Composer) logCopyMsg(ctx context.Context, container containerd.Container, direction copyDirection, srcService string, srcPath string, destService string, dstPath string, dryRun bool) error {\n\tcontainerLabels, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainerName := containerLabels[labels.Name]\n\tmsg := \"\"\n\tif dryRun {\n\t\tmsg = \"DRY-RUN MODE - \"\n\t}\n\tif direction == fromService {\n\t\tmsg = msg + fmt.Sprintf(\"copy %s:%s to %s\", containerName, srcPath, dstPath)\n\t}\n\tif direction == toService {\n\t\tmsg = msg + fmt.Sprintf(\"copy %s to %s:%s\", srcPath, containerName, dstPath)\n\t}\n\tlog.G(ctx).Info(msg)\n\treturn nil\n}\n\nfunc (c *Composer) listContainersTargetedForCopy(ctx context.Context, index int, direction copyDirection, serviceName string) ([]containerd.Container, error) {\n\tvar containers []containerd.Container\n\tvar err error\n\n\tcontainers, err = c.Containers(ctx, serviceName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif index > 0 {\n\t\tif index > len(containers) {\n\t\t\treturn nil, fmt.Errorf(\"index (%d) out of range: only %d running instances from service %s\",\n\t\t\t\tindex, len(containers), serviceName)\n\t\t}\n\t\tcontainer := containers[index-1]\n\t\treturn []containerd.Container{container}, nil\n\t}\n\n\tif len(containers) < 1 {\n\t\treturn nil, fmt.Errorf(\"no container found for service %q\", serviceName)\n\t}\n\tif direction == fromService {\n\t\treturn containers[:1], err\n\n\t}\n\treturn containers, err\n}\n\n// https://github.com/docker/compose/blob/v2.21.0/pkg/compose/cp.go#L307\nfunc splitCpArg(arg string) (container, path string) {\n\tif system.IsAbs(arg) {\n\t\t// Explicit local absolute path, e.g., `C:\\foo` or `/foo`.\n\t\treturn \"\", arg\n\t}\n\n\tparts := strings.SplitN(arg, \":\", 2)\n\n\tif len(parts) == 1 || strings.HasPrefix(parts[0], \".\") {\n\t\t// Either there's no `:` in the arg\n\t\t// OR it's an explicit local relative path like `./file:name.txt`.\n\t\treturn \"\", arg\n\t}\n\n\treturn parts[0], parts[1]\n}\n"
  },
  {
    "path": "pkg/composer/create.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\n// FYI: https://github.com/docker/compose/blob/v2.14.1/pkg/api/api.go#L423\nconst (\n\t// RecreateNever specifies never recreating existing service containers\n\tRecreateNever = \"never\"\n\t// RecreateForce specifies always force-recreating service containers\n\tRecreateForce = \"force\"\n\t// RecreateDiverged specifies only recreating service containers which diverges from compose model.\n\t// (Unimplemented, currently equal to `RecreateNever`) In docker-compose,\n\t// service config is hashed and stored in a label.\n\t// FYI: https://github.com/docker/compose/blob/v2.14.1/pkg/compose/convergence.go#L244\n\tRecreateDiverged = \"diverged\"\n)\n\n// CreateOptions stores all option input from `nerdctl compose create`\ntype CreateOptions struct {\n\tBuild         bool\n\tNoBuild       bool\n\tForceRecreate bool\n\tNoRecreate    bool\n\tPull          *string\n}\n\nfunc (opts CreateOptions) recreateStrategy() string {\n\tswitch {\n\tcase opts.ForceRecreate:\n\t\treturn RecreateForce\n\tcase opts.NoRecreate:\n\t\treturn RecreateNever\n\tdefault:\n\t\treturn RecreateDiverged\n\t}\n}\n\n// Create creates containers for given services.\nfunc (c *Composer) Create(ctx context.Context, opt CreateOptions, services []string) error {\n\t// preprocess services based on options (for all project services, in case\n\t// there are dependencies not in `services`)\n\tfor i, service := range c.project.Services {\n\t\tif opt.Pull != nil {\n\t\t\tservice.PullPolicy = *opt.Pull\n\t\t}\n\t\tif opt.Build && service.Build != nil {\n\t\t\tservice.PullPolicy = types.PullPolicyBuild\n\t\t}\n\t\tif opt.NoBuild {\n\t\t\tservice.Build = nil\n\t\t\tif service.Image == \"\" {\n\t\t\t\tservice.Image = fmt.Sprintf(\"%s_%s\", c.project.Name, service.Name)\n\t\t\t}\n\t\t}\n\t\tc.project.Services[i] = service\n\t}\n\n\t// prepare other components (networks, volumes, configs)\n\tfor shortName := range c.project.Networks {\n\t\tif err := c.upNetwork(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName := range c.project.Volumes {\n\t\tif err := c.upVolume(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName, secret := range c.project.Secrets {\n\t\tobj := types.FileObjectConfig(secret)\n\t\tif err := validateFileObjectConfig(obj, shortName, \"service\", c.project); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName, config := range c.project.Configs {\n\t\tobj := types.FileObjectConfig(config)\n\t\tif err := validateFileObjectConfig(obj, shortName, \"config\", c.project); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// ensure images\n\t// TODO: parallelize loop for ensuring images (make sure not to mess up tty)\n\tparsedServices, err := c.Services(ctx, services...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, ps := range parsedServices {\n\t\tif err := c.ensureServiceImage(ctx, ps, !opt.NoBuild, opt.Build, BuildOptions{}, false, \"\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, ps := range parsedServices {\n\t\tif err := c.createService(ctx, ps, opt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Composer) createService(ctx context.Context, ps *serviceparser.Service, opt CreateOptions) error {\n\trecreate := opt.recreateStrategy()\n\tvar runEG errgroup.Group\n\tfor _, container := range ps.Containers {\n\t\tcontainer := container\n\t\trunEG.Go(func() error {\n\t\t\t_, err := c.createServiceContainer(ctx, ps, container, recreate)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\treturn runEG.Wait()\n}\n\n// createServiceContainer must be called after ensureServiceImage\n// createServiceContainer returns container ID\n// TODO(djdongjin): refactor needed:\n// 1. the logic is similar to `upServiceContainer`, need to decouple some of the logic.\n// 2. ideally, `compose up` should equal to `compose create` + `compose start`, we should decouple and reuse the logic in `compose up`.\n// 3. it'll be easier to refactor after related `compose` logic are moved to `pkg` from `cmd`.\nfunc (c *Composer) createServiceContainer(ctx context.Context, service *serviceparser.Service, container serviceparser.Container, recreate string) (string, error) {\n\t// check if container already exists\n\texists, err := c.containerExists(ctx, container.Name, service.Unparsed.Name)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while checking for containers with name %q: %w\", container.Name, err)\n\t}\n\n\t// delete container if it already exists and force-recreate is enabled\n\tif exists {\n\t\tif recreate != RecreateForce {\n\t\t\tlog.G(ctx).Infof(\"Container %s exists, skipping\", container.Name)\n\t\t\treturn \"\", nil\n\t\t}\n\n\t\tlog.G(ctx).Debugf(\"Container %q already exists and force-created is enabled, deleting\", container.Name)\n\t\tdelCmd := c.createNerdctlCmd(ctx, \"rm\", \"-f\", container.Name)\n\t\tif err = delCmd.Run(); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"could not delete container %q: %w\", container.Name, err)\n\t\t}\n\t\tlog.G(ctx).Infof(\"Re-creating container %s\", container.Name)\n\t} else {\n\t\tlog.G(ctx).Infof(\"Creating container %s\", container.Name)\n\t}\n\n\ttempDir, err := os.MkdirTemp(os.TempDir(), \"compose-\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while creating/re-creating container %s: %w\", container.Name, err)\n\t}\n\tdefer os.RemoveAll(tempDir)\n\tcidFilename := filepath.Join(tempDir, \"cid\")\n\n\t//add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels\n\tcurrentHash, err := ServiceHash(*service.Unparsed)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed computing service hash for %s: %w\", container.Name, err)\n\t}\n\tcontainer.RunArgs = append([]string{\n\t\t\"--cidfile=\" + cidFilename,\n\t\tfmt.Sprintf(\"-l=%s=%s\", labels.ComposeProject, c.project.Name),\n\t\tfmt.Sprintf(\"-l=%s=%s\", labels.ComposeService, service.Unparsed.Name),\n\t\tfmt.Sprintf(\"-l=%s=%s\", labels.ComposeConfigHash, currentHash),\n\t}, container.RunArgs...)\n\n\tcmd := c.createNerdctlCmd(ctx, append([]string{\"create\"}, container.RunArgs...)...)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\n\t// FIXME\n\tif service.Unparsed.StdinOpen != service.Unparsed.Tty {\n\t\treturn \"\", fmt.Errorf(\"currently StdinOpen(-i) and Tty(-t) should be same\")\n\t}\n\n\terr = cmd.Run()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while creating container %s: %w\", container.Name, err)\n\t}\n\n\tcid, err := filesystem.ReadFile(cidFilename)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while creating container %s: %w\", container.Name, err)\n\t}\n\treturn strings.TrimSpace(string(cid)), nil\n}\n"
  },
  {
    "path": "pkg/composer/down.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\ntype DownOptions struct {\n\tRemoveVolumes bool\n\tRemoveOrphans bool\n}\n\nfunc (c *Composer) Down(ctx context.Context, downOptions DownOptions) error {\n\tserviceNames, err := c.ServiceNames()\n\tif err != nil {\n\t\treturn err\n\t}\n\t// reverse dependency order\n\tfor _, svc := range strutil.ReverseStrSlice(serviceNames) {\n\t\tcontainers, err := c.Containers(ctx, svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// use default Options to stop service containers.\n\t\tif err := c.stopContainers(ctx, containers, StopOptions{}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := c.removeContainers(ctx, containers, RemoveOptions{Stop: true, Volumes: downOptions.RemoveVolumes}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// remove orphan containers\n\tparsedServices, err := c.Services(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\torphans, err := c.getOrphanContainers(ctx, parsedServices)\n\tif err != nil && downOptions.RemoveOrphans {\n\t\treturn fmt.Errorf(\"error getting orphaned containers: %w\", err)\n\t}\n\tif len(orphans) > 0 {\n\t\tif downOptions.RemoveOrphans {\n\t\t\tif err := c.removeContainers(ctx, orphans, RemoveOptions{Stop: true, Volumes: downOptions.RemoveVolumes}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error removeing orphaned containers: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.G(ctx).Warnf(\"found %d orphaned containers: %v, you can run this command with the --remove-orphans flag to clean it up\", len(orphans), containerShortIDs(orphans))\n\t\t}\n\t}\n\n\tfor shortName := range c.project.Networks {\n\t\tif err := c.downNetwork(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif downOptions.RemoveVolumes {\n\t\tfor shortName := range c.project.Volumes {\n\t\t\tif err := c.downVolume(ctx, shortName); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Composer) downNetwork(ctx context.Context, shortName string) error {\n\tnet, ok := c.project.Networks[shortName]\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid network name %q\", shortName)\n\t}\n\tif net.External {\n\t\t// NOP\n\t\treturn nil\n\t}\n\t// shortName is like \"default\", fullName is like \"compose-wordpress_default\"\n\tfullName := net.Name\n\tnetExists, err := c.NetworkExists(fullName)\n\tif err != nil {\n\t\treturn err\n\t} else if netExists {\n\t\tnetUsed, err := c.NetworkInUse(ctx, fullName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif netUsed {\n\t\t\treturn fmt.Errorf(\"network %s is in use\", fullName)\n\t\t}\n\n\t\tlog.G(ctx).Infof(\"Removing network %s\", fullName)\n\t\tif err := c.runNerdctlCmd(ctx, \"network\", \"rm\", fullName); err != nil {\n\t\t\tlog.G(ctx).Warn(err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Composer) downVolume(ctx context.Context, shortName string) error {\n\tvol, ok := c.project.Volumes[shortName]\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid volume name %q\", shortName)\n\t}\n\tif vol.External {\n\t\t// NOP\n\t\treturn nil\n\t}\n\t// shortName is like \"db_data\", fullName is like \"compose-wordpress_db_data\"\n\tfullName := vol.Name\n\t// FIXME: this is racy. See note in up_volume.go\n\tvolExists, err := c.VolumeExists(fullName)\n\tif err != nil {\n\t\treturn err\n\t} else if volExists {\n\t\tlog.G(ctx).Infof(\"Removing volume %s\", fullName)\n\t\tif err := c.runNerdctlCmd(ctx, \"volume\", \"rm\", \"-f\", fullName); err != nil {\n\t\t\tlog.G(ctx).Warn(err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/exec.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\n// ExecOptions stores options passed from users as flags and args.\ntype ExecOptions struct {\n\tServiceName string\n\tIndex       int\n\t// params to be passed to `nerdctl exec`\n\tDetach      bool\n\tInteractive bool\n\tTty         bool\n\tPrivileged  bool\n\tUser        string\n\tWorkDir     string\n\tEnv         []string\n\tArgs        []string\n}\n\n// Exec executes a given command on a running container specified by\n// `ServiceName` (and `Index` if it has multiple instances).\nfunc (c *Composer) Exec(ctx context.Context, eo ExecOptions) error {\n\t// Exec does not need to lock and should allow concurrency.\n\tif err := Unlock(); err != nil {\n\t\treturn err\n\t}\n\n\tcontainers, err := c.Containers(ctx, eo.ServiceName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"fail to get containers for service %s: %w\", eo.ServiceName, err)\n\t}\n\tif len(containers) == 0 {\n\t\treturn fmt.Errorf(\"no running containers from service %s\", eo.ServiceName)\n\t}\n\tif eo.Index > len(containers) {\n\t\treturn fmt.Errorf(\"index (%d) out of range: only %d running instances from service %s\",\n\t\t\teo.Index, len(containers), eo.ServiceName)\n\t}\n\tif len(containers) == 1 {\n\t\treturn c.exec(ctx, containers[0], eo)\n\t}\n\t// The order of the containers is not consistently ascending\n\t// we need to re-sort them.\n\tsort.SliceStable(containers, func(i, j int) bool {\n\t\tinfoI, _ := containers[i].Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tinfoJ, _ := containers[j].Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tsegsI := strings.Split(infoI.Labels[labels.Name], serviceparser.Separator)\n\t\tsegsJ := strings.Split(infoJ.Labels[labels.Name], serviceparser.Separator)\n\t\tindexI, _ := strconv.Atoi(segsI[len(segsI)-1])\n\t\tindexJ, _ := strconv.Atoi(segsJ[len(segsJ)-1])\n\t\treturn indexI < indexJ\n\t})\n\treturn c.exec(ctx, containers[eo.Index-1], eo)\n}\n\n// exec constructs/executes the `nerdctl exec` command to be executed on the given container.\nfunc (c *Composer) exec(ctx context.Context, container containerd.Container, eo ExecOptions) error {\n\targs := []string{\n\t\t\"exec\",\n\t\tfmt.Sprintf(\"--detach=%t\", eo.Detach),\n\t\tfmt.Sprintf(\"--interactive=%t\", eo.Interactive),\n\t\tfmt.Sprintf(\"--tty=%t\", eo.Tty),\n\t\tfmt.Sprintf(\"--privileged=%t\", eo.Privileged),\n\t}\n\tif eo.User != \"\" {\n\t\targs = append(args, \"--user\", eo.User)\n\t}\n\tif eo.WorkDir != \"\" {\n\t\targs = append(args, \"--workdir\", eo.WorkDir)\n\t}\n\tfor _, e := range eo.Env {\n\t\targs = append(args, \"--env\", e)\n\t}\n\targs = append(args, container.ID())\n\targs = append(args, eo.Args...)\n\tcmd := c.createNerdctlCmd(ctx, args...)\n\n\tif eo.Interactive {\n\t\tcmd.Stdin = os.Stdin\n\t}\n\tif !eo.Detach {\n\t\tcmd.Stdout = os.Stdout\n\t\tcmd.Stderr = os.Stderr\n\t}\n\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Executing %v\", cmd.Args)\n\t}\n\treturn cmd.Run()\n}\n"
  },
  {
    "path": "pkg/composer/kill.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/containerd/log\"\n)\n\ntype KillOptions struct {\n\tSignal string\n}\n\nfunc (c *Composer) Kill(ctx context.Context, opts KillOptions, services []string) error {\n\tserviceNames, err := c.ServiceNames(services...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\teg, ctx := errgroup.WithContext(ctx)\n\tfor _, container := range containers {\n\t\tcontainer := container\n\t\teg.Go(func() error {\n\t\t\targs := []string{\"kill\", \"-s\", opts.Signal, container.ID()}\n\t\t\tif err := c.runNerdctlCmd(ctx, args...); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\treturn eg.Wait()\n}\n"
  },
  {
    "path": "pkg/composer/lock.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"os\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\n//nolint:unused\nvar locked *os.File\n\nfunc Lock(dataRoot string, address string) error {\n\t// Compose right now cannot be made safe to use concurrently, as we shell out to nerdctl for multiple operations,\n\t// preventing us from using the lock mechanisms from the API.\n\t// This here allows to impose a global lock, effectively preventing multiple compose commands from being run in parallel and\n\t// preventing some of the problems with concurrent execution.\n\t// This should be removed once we have better, in-depth solutions to make compose concurrency safe.\n\t// Note that in most cases we do not close the lock explicitly. Instead, the lock will get released when the `locked` global\n\t// variable will get collected and the file descriptor closed (eg: when the binary exits).\n\tvar err error\n\tdataStore, err := clientutil.DataStore(dataRoot, address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlocked, err = filesystem.Lock(dataStore)\n\treturn err\n}\n\nfunc Unlock() error {\n\treturn filesystem.Unlock(locked)\n}\n"
  },
  {
    "path": "pkg/composer/logs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"strings\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/pipetagger\"\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\ntype LogsOptions struct {\n\tAbortOnContainerExit bool\n\tFollow               bool\n\tTimestamps           bool\n\tTail                 string\n\tNoColor              bool\n\tNoLogPrefix          bool\n\tLatestRun            bool\n}\n\nfunc (c *Composer) Logs(ctx context.Context, lo LogsOptions, services []string) error {\n\t// Whether we called `compose logs`, or we are showing logs at the end of `up`, while in non detach mode, we need\n\t// to release the lock. At this point, no operation will be performed that needs exclusive locking anymore, and\n\t// not releasing the lock would otherwise unduly prevent further compose operations.\n\t// See https://github.com/containerd/nerdctl/issues/3678\n\tif err := Unlock(); err != nil {\n\t\treturn err\n\t}\n\n\tvar serviceNames []string\n\terr := c.project.ForEachService(services, func(name string, svc *types.ServiceConfig) error {\n\t\tserviceNames = append(serviceNames, svc.Name)\n\t\treturn nil\n\t}, types.IgnoreDependencies)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.logs(ctx, containers, lo)\n}\n\nfunc (c *Composer) logs(ctx context.Context, containers []containerd.Container, lo LogsOptions) error {\n\tvar logTagMaxLen int\n\ttype containerState struct {\n\t\tname      string\n\t\tlogTag    string\n\t\tlogCmd    *exec.Cmd\n\t\tstartedAt string\n\t}\n\n\tcontainerStates := make(map[string]containerState, len(containers)) // key: containerID\n\tfor _, container := range containers {\n\t\tinfo, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tname := info.Labels[labels.Name]\n\t\tlogTag := strings.TrimPrefix(name, c.project.Name+serviceparser.Separator)\n\t\tif l := len(logTag); l > logTagMaxLen {\n\t\t\tlogTagMaxLen = l\n\t\t}\n\t\tts, err := info.UpdatedAt.MarshalText()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcontainerStates[container.ID()] = containerState{\n\t\t\tname:      name,\n\t\t\tlogTag:    logTag,\n\t\t\tstartedAt: string(ts),\n\t\t}\n\t}\n\n\tlogsEOFChan := make(chan string) // value: container name\n\tfor id, state := range containerStates {\n\t\t// TODO: show logs without executing `nerdctl logs`\n\t\targs := []string{\"logs\"}\n\t\tif lo.Follow {\n\t\t\targs = append(args, \"-f\")\n\t\t}\n\t\tif lo.Timestamps {\n\t\t\targs = append(args, \"-t\")\n\t\t}\n\t\tif lo.Tail != \"\" {\n\t\t\targs = append(args, \"-n\")\n\t\t\tif lo.Tail == \"all\" {\n\t\t\t\targs = append(args, \"+0\")\n\t\t\t} else {\n\t\t\t\targs = append(args, lo.Tail)\n\t\t\t}\n\t\t}\n\t\tif lo.LatestRun {\n\t\t\targs = append(args, fmt.Sprintf(\"--since=%s\", state.startedAt))\n\t\t}\n\n\t\targs = append(args, id)\n\t\tstate.logCmd = c.createNerdctlCmd(ctx, args...)\n\t\tstdout, err := state.logCmd.StdoutPipe()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlogWidth := logTagMaxLen + 1\n\t\tif lo.NoLogPrefix {\n\t\t\tlogWidth = -1\n\t\t}\n\t\tstdoutTagger := pipetagger.New(os.Stdout, stdout, state.logTag, logWidth, lo.NoColor)\n\t\tstderr, err := state.logCmd.StderrPipe()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstderrTagger := pipetagger.New(os.Stderr, stderr, state.logTag, logWidth, lo.NoColor)\n\t\tif c.DebugPrintFull {\n\t\t\tlog.G(ctx).Debugf(\"Running %v\", state.logCmd.Args)\n\t\t}\n\t\tif err := state.logCmd.Start(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcontainerName := state.name\n\t\tgo func() {\n\t\t\tstdoutTagger.Run()\n\t\t\tlogsEOFChan <- containerName\n\t\t}()\n\t\tgo stderrTagger.Run()\n\t}\n\n\tinterruptChan := make(chan os.Signal, 1)\n\tsignal.Notify(interruptChan, os.Interrupt)\n\n\tlogsEOFMap := make(map[string]struct{}) // key: container name\n\tvar containerError error\nselectLoop:\n\tfor {\n\t\t// Wait for Ctrl-C, or `nerdctl compose down` in another terminal\n\t\tselect {\n\t\tcase sig := <-interruptChan:\n\t\t\tlog.G(ctx).Debugf(\"Received signal: %s\", sig)\n\t\t\tbreak selectLoop\n\t\tcase containerName := <-logsEOFChan:\n\t\t\tif lo.Follow {\n\t\t\t\t// When `nerdctl logs -f` has exited, we can assume that the container has exited\n\t\t\t\tlog.G(ctx).Infof(\"Container %q exited\", containerName)\n\t\t\t\t// In case a container has exited and the parameter --abort-on-container-exit,\n\t\t\t\t// we break the loop and set an error, so we can exit the program with 1\n\t\t\t\tif lo.AbortOnContainerExit {\n\t\t\t\t\tcontainerError = fmt.Errorf(\"container %q exited\", containerName)\n\t\t\t\t\tbreak selectLoop\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.G(ctx).Debugf(\"Logs for container %q reached EOF\", containerName)\n\t\t\t}\n\t\t\tlogsEOFMap[containerName] = struct{}{}\n\t\t\tif len(logsEOFMap) == len(containerStates) {\n\t\t\t\tif lo.Follow {\n\t\t\t\t\tlog.G(ctx).Info(\"All the containers have exited\")\n\t\t\t\t} else {\n\t\t\t\t\tlog.G(ctx).Debug(\"All the logs reached EOF\")\n\t\t\t\t}\n\t\t\t\tbreak selectLoop\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, state := range containerStates {\n\t\tif state.logCmd != nil && state.logCmd.Process != nil {\n\t\t\tif err := state.logCmd.Process.Kill(); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn containerError\n}\n"
  },
  {
    "path": "pkg/composer/orphans.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc (c *Composer) getOrphanContainers(ctx context.Context, parsedServices []*serviceparser.Service) ([]containerd.Container, error) {\n\t// get all running containers for project\n\tvar filters = []string{fmt.Sprintf(\"labels.%q==%s\", labels.ComposeProject, c.project.Name)}\n\tcontainers, err := c.client.Containers(ctx, filters...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tparsedSvcNames := make(map[string]bool)\n\tfor _, svc := range parsedServices {\n\t\tparsedSvcNames[svc.Unparsed.Name] = true\n\t}\n\n\tvar orphanContainers []containerd.Container\n\tfor _, container := range containers {\n\t\t// orphan containers doesn't have a `ComposeService` label corresponding\n\t\t// to any name of given services.\n\t\tcontainerLabels, err := container.Labels(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting container labels: %w\", err)\n\t\t}\n\t\tcontainerSvc := containerLabels[labels.ComposeService]\n\t\tif inServices := parsedSvcNames[containerSvc]; !inServices {\n\t\t\torphanContainers = append(orphanContainers, container)\n\t\t}\n\t}\n\n\treturn orphanContainers, nil\n}\n\nfunc containerShortIDs(containers []containerd.Container) []string {\n\tnames := make([]string, 0, len(containers))\n\tfor _, c := range containers {\n\t\tnames = append(names, c.ID()[:12])\n\t}\n\treturn names\n}\n"
  },
  {
    "path": "pkg/composer/pause.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\n// Pause pauses service containers belonging to `services`.\nfunc (c *Composer) Pause(ctx context.Context, services []string, writer io.Writer) error {\n\tserviceNames, err := c.ServiceNames(services...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar mu sync.Mutex\n\n\teg, ctx := errgroup.WithContext(ctx)\n\tfor _, container := range containers {\n\t\tcontainer := container\n\t\teg.Go(func() error {\n\t\t\tif err := containerutil.Pause(ctx, c.client, container.ID()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tinfo, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tmu.Lock()\n\t\t\tdefer mu.Unlock()\n\t\t\t_, err = fmt.Fprintln(writer, info.Labels[labels.Name])\n\n\t\t\treturn err\n\t\t})\n\t}\n\n\treturn eg.Wait()\n}\n\n// Unpause unpauses service containers belonging to `services`.\nfunc (c *Composer) Unpause(ctx context.Context, services []string, writer io.Writer) error {\n\tserviceNames, err := c.ServiceNames(services...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainers, err := c.Containers(ctx, serviceNames...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar mu sync.Mutex\n\n\teg, ctx := errgroup.WithContext(ctx)\n\tfor _, container := range containers {\n\t\tcontainer := container\n\t\teg.Go(func() error {\n\t\t\tif err := containerutil.Unpause(ctx, c.client, container.ID(), c.config, c.NerdctlCmd, c.NerdctlArgs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tinfo, err := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tmu.Lock()\n\t\t\tdefer mu.Unlock()\n\t\t\t_, err = fmt.Fprintln(writer, info.Labels[labels.Name])\n\n\t\t\treturn err\n\t\t})\n\t}\n\n\treturn eg.Wait()\n}\n"
  },
  {
    "path": "pkg/composer/pipetagger/pipetagger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage pipetagger\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n)\n\nfunc ChooseColorAttrs(tag string) []color.Attribute {\n\thasher := fnv.New32()\n\thasher.Write([]byte(tag))\n\ttagHash := int(hasher.Sum32())\n\n\tfgCandidates := []color.Attribute{\n\t\tcolor.FgBlack,\n\t\tcolor.FgRed,\n\t\tcolor.FgGreen,\n\t\tcolor.FgYellow,\n\t\tcolor.FgBlue,\n\t\tcolor.FgMagenta,\n\t\tcolor.FgCyan,\n\t\tcolor.FgWhite,\n\t\tcolor.FgHiBlack,\n\t\tcolor.FgHiRed,\n\t\tcolor.FgHiGreen,\n\t\tcolor.FgHiYellow,\n\t\tcolor.FgHiBlue,\n\t\tcolor.FgHiMagenta,\n\t\tcolor.FgHiCyan,\n\t\tcolor.FgHiWhite,\n\t}\n\tfgAttr := fgCandidates[tagHash%len(fgCandidates)]\n\n\tattrs := []color.Attribute{\n\t\tfgAttr,\n\t}\n\n\tswitch fgAttr {\n\tcase color.FgBlack:\n\t\tattrs = append(attrs, color.BgWhite)\n\tcase color.FgWhite:\n\t\tattrs = append(attrs, color.BgBlack)\n\tcase color.FgHiBlack:\n\t\tattrs = append(attrs, color.BgHiWhite)\n\tcase color.FgHiWhite:\n\t\tattrs = append(attrs, color.BgHiBlack)\n\t}\n\n\treturn attrs\n}\n\n// New create a PipeTagger.\n// Set width = -1 to disable tagging.\nfunc New(w io.Writer, r io.Reader, tag string, width int, noColor bool) *PipeTagger {\n\tvar attrs []color.Attribute\n\tif !noColor {\n\t\tattrs = ChooseColorAttrs(tag)\n\t}\n\treturn &PipeTagger{\n\t\tw:     w,\n\t\tr:     r,\n\t\ttag:   tag,\n\t\twidth: width,\n\t\tcolor: color.New(attrs...),\n\t}\n}\n\ntype PipeTagger struct {\n\tw     io.Writer\n\tr     io.Reader\n\ttag   string\n\twidth int\n\tcolor *color.Color\n}\n\nfunc (x *PipeTagger) Run() error {\n\tscanner := bufio.NewScanner(x.r)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif x.width < 0 {\n\t\t\tfmt.Fprintln(x.w, line)\n\t\t} else {\n\t\t\tfmt.Fprintf(x.w, \"%s%s|%s\\n\",\n\t\t\t\tx.color.Sprint(x.tag),\n\t\t\t\tstrings.Repeat(\" \", x.width-len(x.tag)),\n\t\t\t\tline,\n\t\t\t)\n\t\t}\n\t}\n\treturn scanner.Err()\n}\n"
  },
  {
    "path": "pkg/composer/port.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n)\n\n// PortOptions has args for getting the public port of a given private port/protocol\n// in a service container.\ntype PortOptions struct {\n\tServiceName string\n\tIndex       int\n\tPort        int\n\tProtocol    string\n\tDataStore   string\n\tNamespace   string\n}\n\n// Port gets the corresponding public port of a given private port/protocol\n// on a service container.\nfunc (c *Composer) Port(ctx context.Context, writer io.Writer, po PortOptions) error {\n\tcontainers, err := c.Containers(ctx, po.ServiceName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"fail to get containers for service %s: %w\", po.ServiceName, err)\n\t}\n\tif len(containers) == 0 {\n\t\treturn fmt.Errorf(\"no running containers from service %s\", po.ServiceName)\n\t}\n\tif po.Index > len(containers) {\n\t\treturn fmt.Errorf(\"index (%d) out of range: only %d running instances from service %s\",\n\t\t\tpo.Index, len(containers), po.ServiceName)\n\t}\n\tcontainer := containers[po.Index-1]\n\tcontainerLabels, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tports, err := portutil.LoadPortMappings(po.DataStore, po.Namespace, container.ID(), containerLabels)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn containerutil.PrintHostPort(ctx, writer, container, po.Port, po.Protocol, ports)\n}\n"
  },
  {
    "path": "pkg/composer/pull.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n)\n\ntype PullOptions struct {\n\tQuiet bool\n}\n\nfunc (c *Composer) Pull(ctx context.Context, po PullOptions, services []string) error {\n\treturn c.project.ForEachService(services, func(name string, svc *types.ServiceConfig) error {\n\t\tps, err := serviceparser.Parse(c.project, *svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.pullServiceImage(ctx, ps.Image, ps.Unparsed.Platform, ps, po)\n\t})\n}\n\nfunc (c *Composer) pullServiceImage(ctx context.Context, image string, platform string, ps *serviceparser.Service, po PullOptions) error {\n\tlog.G(ctx).Infof(\"Pulling image %s\", image)\n\n\tvar args []string // nolint: prealloc\n\tif platform != \"\" {\n\t\targs = append(args, \"--platform=\"+platform)\n\t}\n\tif po.Quiet {\n\t\targs = append(args, \"--quiet\")\n\t}\n\tif verifier, ok := ps.Unparsed.Extensions[serviceparser.ComposeVerify]; ok {\n\t\targs = append(args, \"--verify=\"+verifier.(string))\n\t}\n\tif publicKey, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPublicKey]; ok {\n\t\targs = append(args, \"--cosign-key=\"+publicKey.(string))\n\t}\n\tif certificateIdentity, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateIdentity]; ok {\n\t\targs = append(args, \"--cosign-certificate-identity=\"+certificateIdentity.(string))\n\t}\n\tif certificateIdentityRegexp, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateIdentityRegexp]; ok {\n\t\targs = append(args, \"--cosign-certificate-identity-regexp=\"+certificateIdentityRegexp.(string))\n\t}\n\tif certificateOidcIssuer, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateOidcIssuer]; ok {\n\t\targs = append(args, \"--cosign-certificate-oidc-issuer=\"+certificateOidcIssuer.(string))\n\t}\n\tif certificateOidcIssuerRegexp, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignCertificateOidcIssuerRegexp]; ok {\n\t\targs = append(args, \"--cosign-certificate-oidc-issuer-regexp=\"+certificateOidcIssuerRegexp.(string))\n\t}\n\n\tif c.Options.Experimental {\n\t\targs = append(args, \"--experimental\")\n\t}\n\n\targs = append(args, image)\n\n\tcmd := c.createNerdctlCmd(ctx, append([]string{\"pull\"}, args...)...)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"error while pulling image %s: %w\", image, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n)\n\ntype PushOptions struct {\n}\n\nfunc (c *Composer) Push(ctx context.Context, po PushOptions, services []string) error {\n\treturn c.project.ForEachService(services, func(name string, svc *types.ServiceConfig) error {\n\t\tps, err := serviceparser.Parse(c.project, *svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.pushServiceImage(ctx, ps.Image, ps.Unparsed.Platform, ps, po)\n\t})\n}\n\nfunc (c *Composer) pushServiceImage(ctx context.Context, image string, platform string, ps *serviceparser.Service, po PushOptions) error {\n\tlog.G(ctx).Infof(\"Pushing image %s\", image)\n\n\tvar args []string // nolint: prealloc\n\tif platform != \"\" {\n\t\targs = append(args, \"--platform=\"+platform)\n\t}\n\tif signer, ok := ps.Unparsed.Extensions[serviceparser.ComposeSign]; ok {\n\t\targs = append(args, \"--sign=\"+signer.(string))\n\t}\n\tif privateKey, ok := ps.Unparsed.Extensions[serviceparser.ComposeCosignPrivateKey]; ok {\n\t\targs = append(args, \"--cosign-key=\"+privateKey.(string))\n\t}\n\tif c.Options.Experimental {\n\t\targs = append(args, \"--experimental\")\n\t}\n\n\targs = append(args, image)\n\n\tcmd := c.createNerdctlCmd(ctx, append([]string{\"push\"}, args...)...)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"error while pushing image %s: %w\", image, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/restart.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\n// RestartOptions stores all option input from `nerdctl compose restart`\ntype RestartOptions struct {\n\tTimeout *uint\n}\n\n// Restart restarts running/stopped containers in `services`. It calls\n// `nerdctl restart CONTAINER_ID` to do the actual job.\nfunc (c *Composer) Restart(ctx context.Context, opt RestartOptions, services []string) error {\n\t// in dependency order\n\treturn c.project.ForEachService(services, func(name string, svc *types.ServiceConfig) error {\n\t\tcontainers, err := c.Containers(ctx, svc.Name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn c.restartContainers(ctx, containers, opt)\n\t})\n}\n\nfunc (c *Composer) restartContainers(ctx context.Context, containers []containerd.Container, opt RestartOptions) error {\n\tvar timeoutArg string\n\tif opt.Timeout != nil {\n\t\t// `nerdctl restart` uses `--time` instead of `--timeout`\n\t\ttimeoutArg = fmt.Sprintf(\"--time=%d\", *opt.Timeout)\n\t}\n\n\tvar rsWG sync.WaitGroup\n\tfor _, container := range containers {\n\t\tcontainer := container\n\t\trsWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer rsWG.Done()\n\t\t\tinfo, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\tlog.G(ctx).Infof(\"Restarting container %s\", info.Labels[labels.Name])\n\t\t\targs := []string{\"restart\"}\n\t\t\tif opt.Timeout != nil {\n\t\t\t\targs = append(args, timeoutArg)\n\t\t\t}\n\t\t\targs = append(args, container.ID())\n\t\t\tif err := c.runNerdctlCmd(ctx, args...); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t}\n\t\t}()\n\t}\n\trsWG.Wait()\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/rm.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"sync\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// RemoveOptions stores all options when removing compose containers:\n// Stop: if true, remove using `rm -f`; if false, check and skip running containers.\n// Volumes: if remove anonymous volumes associated with the container.\ntype RemoveOptions struct {\n\tStop    bool\n\tVolumes bool\n}\n\n// Remove removes stopped containers in `services`.\nfunc (c *Composer) Remove(ctx context.Context, opt RemoveOptions, services []string) error {\n\tserviceNames, err := c.ServiceNames(services...)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// reverse dependency order\n\tfor _, svc := range strutil.ReverseStrSlice(serviceNames) {\n\t\tcontainers, err := c.Containers(ctx, svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif opt.Stop {\n\t\t\t// use default Options to stop service containers.\n\t\t\tif err := c.stopContainers(ctx, containers, StopOptions{}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err := c.removeContainers(ctx, containers, opt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Composer) removeContainers(ctx context.Context, containers []containerd.Container, opt RemoveOptions) error {\n\targs := []string{\"rm\", \"-f\"}\n\tif opt.Volumes {\n\t\targs = append(args, \"-v\")\n\t}\n\n\tvar rmWG sync.WaitGroup\n\tfor _, container := range containers {\n\t\tcontainer := container\n\t\trmWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer rmWG.Done()\n\t\t\tinfo, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\t// if not `Stop`, check status and skip running container\n\t\t\tif !opt.Stop {\n\t\t\t\tcStatus := formatter.ContainerStatus(ctx, container)\n\t\t\t\tif strings.HasPrefix(cStatus, \"Up\") {\n\t\t\t\t\tlog.G(ctx).Warnf(\"Removing container %s failed: container still running.\", info.Labels[labels.Name])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlog.G(ctx).Infof(\"Removing container %s\", info.Labels[labels.Name])\n\t\t\tif err := c.runNerdctlCmd(ctx, append(args, container.ID())...); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t}\n\t\t}()\n\t}\n\trmWG.Wait()\n\n\treturn nil\n}\n\nfunc (c *Composer) removeContainersFromParsedServices(ctx context.Context, containers map[string]serviceparser.Container) {\n\tvar rmWG sync.WaitGroup\n\tfor id, container := range containers {\n\t\tid := id\n\t\tcontainer := container\n\t\trmWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer rmWG.Done()\n\t\t\tlog.G(ctx).Infof(\"Removing container %s\", container.Name)\n\t\t\tif err := c.runNerdctlCmd(ctx, \"rm\", \"-f\", id); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t}\n\t\t}()\n\t}\n\trmWG.Wait()\n}\n"
  },
  {
    "path": "pkg/composer/run.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/compose-spec/compose-go/v2/format\"\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n)\n\ntype RunOptions struct {\n\tServiceName string\n\tArgs        []string\n\n\tNoBuild       bool\n\tNoColor       bool\n\tNoLogPrefix   bool\n\tForceBuild    bool\n\tQuietPull     bool\n\tRemoveOrphans bool\n\n\tName         string\n\tDetach       bool\n\tNoDeps       bool\n\tTty          bool\n\tSigProxy     bool\n\tInteractive  bool\n\tRm           bool\n\tUser         string\n\tVolume       []string\n\tEntrypoint   []string\n\tEnv          []string\n\tLabel        []string\n\tWorkDir      string\n\tServicePorts bool\n\tPublish      []string\n}\n\nfunc (c *Composer) Run(ctx context.Context, ro RunOptions) error {\n\tfor shortName := range c.project.Networks {\n\t\tif err := c.upNetwork(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName := range c.project.Volumes {\n\t\tif err := c.upVolume(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName, secret := range c.project.Secrets {\n\t\tobj := types.FileObjectConfig(secret)\n\t\tif err := validateFileObjectConfig(obj, shortName, \"service\", c.project); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName, config := range c.project.Configs {\n\t\tobj := types.FileObjectConfig(config)\n\t\tif err := validateFileObjectConfig(obj, shortName, \"config\", c.project); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar svcs []types.ServiceConfig\n\n\tif ro.NoDeps {\n\t\tsvc, err := c.project.GetService(ro.ServiceName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsvcs = append(svcs, svc)\n\t} else {\n\t\tif err := c.project.ForEachService([]string{ro.ServiceName}, func(name string, svc *types.ServiceConfig) error {\n\t\t\tsvcs = append(svcs, *svc)\n\t\t\treturn nil\n\t\t}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar targetSvc *types.ServiceConfig\n\tfor i := range svcs {\n\t\tif svcs[i].Name == ro.ServiceName {\n\t\t\ttargetSvc = &svcs[i]\n\t\t\tbreak\n\t\t}\n\t}\n\tif targetSvc == nil {\n\t\treturn fmt.Errorf(\"error cannot find service name: %s\", ro.ServiceName)\n\t}\n\n\tfor i := range svcs {\n\t\t// FYI: https://github.com/docker/compose/blob/v2.18.1/pkg/compose/run.go#L65\n\t\tsvcs[i].ContainerName = fmt.Sprintf(\"%[1]s%[4]s%[2]s%[4]srun%[4]s%[3]s\", c.project.Name, svcs[i].Name, idgen.TruncateID(idgen.GenerateID()), serviceparser.Separator)\n\t}\n\n\ttargetSvc.Tty = ro.Tty\n\ttargetSvc.StdinOpen = ro.Interactive\n\n\tif ro.Name != \"\" {\n\t\ttargetSvc.ContainerName = ro.Name\n\t}\n\tif ro.User != \"\" {\n\t\ttargetSvc.User = ro.User\n\t}\n\tif len(ro.Volume) > 0 {\n\t\tfor _, v := range ro.Volume {\n\t\t\tvc, err := format.ParseVolume(v)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ttargetSvc.Volumes = append(targetSvc.Volumes, vc)\n\t\t}\n\t}\n\tif len(ro.Entrypoint) > 0 {\n\t\ttargetSvc.Entrypoint = make([]string, len(ro.Entrypoint))\n\t\tcopy(targetSvc.Entrypoint, ro.Entrypoint)\n\t}\n\tif len(ro.Env) > 0 {\n\t\tenvs := types.NewMappingWithEquals(ro.Env)\n\t\ttargetSvc.Environment.OverrideBy(envs)\n\t}\n\tif len(ro.Label) > 0 {\n\t\tlabel := types.NewMappingWithEquals(ro.Label)\n\t\tfor k, v := range label {\n\t\t\tif v != nil {\n\t\t\t\ttargetSvc.Labels.Add(k, *v)\n\t\t\t}\n\t\t}\n\t}\n\tif ro.WorkDir != \"\" {\n\t\tc.project.WorkingDir = ro.WorkDir\n\t}\n\n\t// `compose run` command does not create any of the ports specified in the service configuration.\n\tif !ro.ServicePorts {\n\t\tfor k := range svcs {\n\t\t\tsvcs[k].Ports = []types.ServicePortConfig{}\n\t\t}\n\t\tif len(ro.Publish) > 0 {\n\t\t\tfor _, p := range ro.Publish {\n\t\t\t\tpc, err := types.ParsePortConfig(p)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error parse --publish: %w\", err)\n\t\t\t\t}\n\t\t\t\ttargetSvc.Ports = append(targetSvc.Ports, pc...)\n\t\t\t}\n\t\t}\n\t}\n\n\t// `compose run` command overrides the command defined in the service configuration.\n\tif len(ro.Args) != 0 {\n\t\ttargetSvc.Command = make([]string, len(ro.Args))\n\t\tcopy(targetSvc.Command, ro.Args)\n\t}\n\n\tparsedServices := make([]*serviceparser.Service, 0)\n\tfor _, svc := range svcs {\n\t\tps, err := serviceparser.Parse(c.project, svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tparsedServices = append(parsedServices, ps)\n\t}\n\n\t// remove orphan containers before the service has be started\n\t// FYI: https://github.com/docker/compose/blob/v2.3.4/pkg/compose/create.go#L91-L112\n\torphans, err := c.getOrphanContainers(ctx, parsedServices)\n\tif err != nil && ro.RemoveOrphans {\n\t\treturn fmt.Errorf(\"error getting orphaned containers: %w\", err)\n\t}\n\tif len(orphans) > 0 {\n\t\tif ro.RemoveOrphans {\n\t\t\tif err := c.removeContainers(ctx, orphans, RemoveOptions{Stop: true, Volumes: true}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error removing orphaned containers: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.G(ctx).Warnf(\"found %d orphaned containers: %v, you can run this command with the --remove-orphans flag to clean it up\", len(orphans), containerShortIDs(orphans))\n\t\t}\n\t}\n\n\treturn c.runServices(ctx, parsedServices, ro)\n}\n\nfunc (c *Composer) runServices(ctx context.Context, parsedServices []*serviceparser.Service, ro RunOptions) error {\n\tif len(parsedServices) == 0 {\n\t\treturn errors.New(\"no service was provided\")\n\t}\n\n\t// TODO: parallelize loop for ensuring images (make sure not to mess up tty)\n\tfor _, ps := range parsedServices {\n\t\tif err := c.ensureServiceImage(ctx, ps, !ro.NoBuild, ro.ForceBuild, BuildOptions{}, ro.QuietPull, \"\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar (\n\t\tcontainers   = make(map[string]serviceparser.Container) // key: container ID\n\t\tservices     = []string{}\n\t\tcontainersMu sync.Mutex\n\t\trunEG        errgroup.Group\n\t\tcid          string // For printing cid when -d exists\n\t)\n\n\tfor _, ps := range parsedServices {\n\t\tps := ps\n\t\tservices = append(services, ps.Unparsed.Name)\n\n\t\tif len(ps.Containers) != 1 {\n\t\t\tlog.G(ctx).Warnf(\"compose run does not support scale but %s is currently %v, automatically it will configure 1\", ps.Unparsed.Name, len(ps.Containers))\n\t\t}\n\n\t\tif len(ps.Containers) == 0 {\n\t\t\treturn fmt.Errorf(\"error, a service should have at least one container but %s does not have any container\", ps.Unparsed.Name)\n\t\t}\n\t\tcontainer := ps.Containers[0]\n\n\t\trunEG.Go(func() error {\n\t\t\tid, err := c.upServiceContainer(ctx, ps, container, RecreateForce)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontainersMu.Lock()\n\t\t\tcontainers[id] = container\n\t\t\tcontainersMu.Unlock()\n\t\t\tif ps.Unparsed.Name == ro.ServiceName {\n\t\t\t\tcid = id\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\tif err := runEG.Wait(); err != nil {\n\t\treturn err\n\t}\n\n\tif ro.Detach {\n\t\tlog.G(ctx).Printf(\"%s\\n\", cid)\n\t\treturn nil\n\t}\n\n\t// TODO: fix it when `nerdctl logs` supports `nerdctl run` without detach\n\t// https://github.com/containerd/nerdctl/blob/v0.22.2/pkg/taskutil/taskutil.go#L55\n\tif !ro.Interactive && !ro.Tty {\n\t\tlog.G(ctx).Info(\"Attaching to logs\")\n\t\tlo := LogsOptions{\n\t\t\tFollow:      true,\n\t\t\tNoColor:     ro.NoColor,\n\t\t\tNoLogPrefix: ro.NoLogPrefix,\n\t\t}\n\t\t// it finally causes to show logs of some containers which are stopped but not deleted.\n\t\tif err := c.Logs(ctx, lo, services); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlog.G(ctx).Infof(\"Stopping containers (forcibly)\") // TODO: support gracefully stopping\n\tc.stopContainersFromParsedServices(ctx, containers)\n\n\tif ro.Rm {\n\t\tc.removeContainersFromParsedServices(ctx, containers)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/serviceparser/build.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage serviceparser\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\tsecurejoin \"github.com/cyphar/filepath-securejoin\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/reflectutil\"\n)\n\nfunc parseBuildConfig(c *types.BuildConfig, project *types.Project, imageName string) (*Build, error) {\n\tif unknown := reflectutil.UnknownNonEmptyFields(c,\n\t\t\"Context\", \"Dockerfile\", \"Args\", \"CacheFrom\", \"Target\", \"Labels\", \"Secrets\", \"DockerfileInline\", \"AdditionalContexts\",\n\t); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: build: %+v\", unknown)\n\t}\n\n\tif c.Context == \"\" {\n\t\treturn nil, errors.New(\"build: context must be specified\")\n\t}\n\tif strings.Contains(c.Context, \"://\") {\n\t\treturn nil, fmt.Errorf(\"build: URL-style context (%q) is not supported yet: %w\", c.Context, errdefs.ErrNotImplemented)\n\t}\n\tctxDir := project.RelativePath(c.Context)\n\n\tvar b Build\n\tb.BuildArgs = append(b.BuildArgs, \"-t=\"+imageName)\n\tif c.Dockerfile != \"\" {\n\t\tif filepath.IsAbs(c.Dockerfile) {\n\t\t\tlog.L.Warnf(\"build.dockerfile should be relative path, got %q\", c.Dockerfile)\n\t\t\tb.BuildArgs = append(b.BuildArgs, \"-f=\"+c.Dockerfile)\n\t\t} else {\n\t\t\t// no need to use securejoin\n\t\t\tdockerfile := filepath.Join(ctxDir, c.Dockerfile)\n\t\t\tb.BuildArgs = append(b.BuildArgs, \"-f=\"+dockerfile)\n\t\t}\n\t}\n\n\tif c.DockerfileInline != \"\" {\n\t\tb.DockerfileInline = c.DockerfileInline\n\t}\n\n\tfor k, v := range c.Args {\n\t\tif v == nil {\n\t\t\tb.BuildArgs = append(b.BuildArgs, \"--build-arg=\"+k)\n\t\t} else {\n\t\t\tb.BuildArgs = append(b.BuildArgs, \"--build-arg=\"+k+\"=\"+*v)\n\t\t}\n\t}\n\n\tfor _, s := range c.CacheFrom {\n\t\tb.BuildArgs = append(b.BuildArgs, \"--cache-from=\"+s)\n\t}\n\n\tfor k, v := range c.AdditionalContexts {\n\t\tb.BuildArgs = append(b.BuildArgs, \"--build-context=\"+k+\"=\"+v)\n\t}\n\n\tif c.Target != \"\" {\n\t\tb.BuildArgs = append(b.BuildArgs, \"--target=\"+c.Target)\n\t}\n\n\tif c.Labels != nil {\n\t\tfor k, v := range c.Labels {\n\t\t\tb.BuildArgs = append(b.BuildArgs, \"--label=\"+k+\"=\"+v)\n\t\t}\n\t}\n\n\tfor _, s := range c.Secrets {\n\t\tfileRef := types.FileReferenceConfig(s)\n\n\t\tif err := identifiers.ValidateDockerCompat(fileRef.Source); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid secret source name: %w\", err)\n\t\t}\n\n\t\tprojectSecret, ok := project.Secrets[fileRef.Source]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"build: secret %s is undefined\", fileRef.Source)\n\t\t}\n\t\tvar src string\n\t\tif filepath.IsAbs(projectSecret.File) {\n\t\t\tlog.L.Warnf(\"build.secrets should be relative path, got %q\", projectSecret.File)\n\t\t\tsrc = projectSecret.File\n\t\t} else {\n\t\t\tvar err error\n\t\t\tsrc, err = securejoin.SecureJoin(ctxDir, projectSecret.File)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tid := fileRef.Source\n\t\tif fileRef.Target != \"\" {\n\t\t\tid = fileRef.Target\n\t\t}\n\t\tb.BuildArgs = append(b.BuildArgs, \"--secret=id=\"+id+\",src=\"+src)\n\t}\n\n\tb.BuildArgs = append(b.BuildArgs, ctxDir)\n\treturn &b, nil\n}\n"
  },
  {
    "path": "pkg/composer/serviceparser/build_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage serviceparser\n\nimport (\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc lastOf(ss []string) string {\n\treturn ss[len(ss)-1]\n}\n\nfunc TestParseBuild(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    build: ./fooctx\n    pull_policy: always\n  bar:\n    image: barimg\n    pull_policy: build\n    build:\n      context: ./barctx\n      target: bartgt\n      labels:\n        bar: baz\n      secrets:\n        - source: src_secret\n          target: tgt_secret\n        - simple_secret\n        - absolute_secret\n  baz:\n    image: bazimg\n    build:\n      context: ./bazctx\n      dockerfile_inline: |\n       FROM random\nsecrets:\n  src_secret:\n    file: test_secret1\n  simple_secret:\n    file: test_secret2\n  absolute_secret:\n    file: /tmp/absolute_secret\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tassert.Equal(t, DefaultImageName(project.Name, \"foo\"), foo.Image)\n\tassert.Equal(t, false, foo.Build.Force)\n\tassert.Equal(t, project.RelativePath(\"fooctx\"), lastOf(foo.Build.BuildArgs))\n\n\tbarSvc, err := project.GetService(\"bar\")\n\tassert.NilError(t, err)\n\n\tbar, err := Parse(project, barSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"bar: %+v\", bar)\n\tassert.Equal(t, \"barimg\", bar.Image)\n\tassert.Equal(t, true, bar.Build.Force)\n\tassert.Equal(t, project.RelativePath(\"barctx\"), lastOf(bar.Build.BuildArgs))\n\tassert.Assert(t, in(bar.Build.BuildArgs, \"--target=bartgt\"))\n\tassert.Assert(t, in(bar.Build.BuildArgs, \"--label=bar=baz\"))\n\tsecretPath := project.WorkingDir\n\tassert.Assert(t, in(bar.Build.BuildArgs, \"--secret=id=tgt_secret,src=\"+secretPath+\"/test_secret1\"))\n\tassert.Assert(t, in(bar.Build.BuildArgs, \"--secret=id=simple_secret,src=\"+secretPath+\"/test_secret2\"))\n\tassert.Assert(t, in(bar.Build.BuildArgs, \"--secret=id=absolute_secret,src=/tmp/absolute_secret\"))\n\n\tbazSvc, err := project.GetService(\"baz\")\n\tassert.NilError(t, err)\n\n\tbaz, err := Parse(project, bazSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"baz: %+v\", baz)\n\tt.Logf(\"baz.Build.BuildArgs: %+v\", baz.Build.BuildArgs)\n\tt.Logf(\"baz.Build.DockerfileInline: %q\", baz.Build.DockerfileInline)\n\tassert.Assert(t, func() bool {\n\t\treturn strings.TrimSpace(baz.Build.DockerfileInline) == \"FROM random\"\n\t}())\n}\n"
  },
  {
    "path": "pkg/composer/serviceparser/serviceparser.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage serviceparser\n\nimport (\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/reflectutil\"\n)\n\n// ComposeExtensionKey defines fields used to implement extension features.\nconst (\n\tComposeVerify                            = \"x-nerdctl-verify\"\n\tComposeCosignPublicKey                   = \"x-nerdctl-cosign-public-key\"\n\tComposeSign                              = \"x-nerdctl-sign\"\n\tComposeCosignPrivateKey                  = \"x-nerdctl-cosign-private-key\"\n\tComposeCosignCertificateIdentity         = \"x-nerdctl-cosign-certificate-identity\"\n\tComposeCosignCertificateIdentityRegexp   = \"x-nerdctl-cosign-certificate-identity-regexp\"\n\tComposeCosignCertificateOidcIssuer       = \"x-nerdctl-cosign-certificate-oidc-issuer\"\n\tComposeCosignCertificateOidcIssuerRegexp = \"x-nerdctl-cosign-certificate-oidc-issuer-regexp\"\n)\n\n// Separator is used for naming components (e.g., service image or container)\n// https://github.com/docker/compose/blob/8c39b5b7fd4210a69d07885835f7ff826aaa1cd8/pkg/api/api.go#L483\nconst Separator = \"-\"\n\nfunc warnUnknownFields(svc types.ServiceConfig) {\n\tif unknown := reflectutil.UnknownNonEmptyFields(&svc,\n\t\t\"Name\",\n\t\t\"Annotations\",\n\t\t\"Build\",\n\t\t\"BlkioConfig\",\n\t\t\"CapAdd\",\n\t\t\"CapDrop\",\n\t\t\"CPUS\",\n\t\t\"CPUSet\",\n\t\t\"CPUShares\",\n\t\t\"Command\",\n\t\t\"Configs\",\n\t\t\"ContainerName\",\n\t\t\"DependsOn\",\n\t\t\"Deploy\",\n\t\t\"Devices\",\n\t\t\"Dockerfile\", // handled by the loader (normalizer)\n\t\t\"DNS\",\n\t\t\"DNSSearch\",\n\t\t\"DNSOpts\",\n\t\t\"Entrypoint\",\n\t\t\"Environment\",\n\t\t\"Extends\", // handled by the loader\n\t\t\"Extensions\",\n\t\t\"ExtraHosts\",\n\t\t\"Hostname\",\n\t\t\"Image\",\n\t\t\"Init\",\n\t\t\"Labels\",\n\t\t\"Logging\",\n\t\t\"MemLimit\",\n\t\t\"Networks\",\n\t\t\"NetworkMode\",\n\t\t\"Pid\",\n\t\t\"PidsLimit\",\n\t\t\"Platform\",\n\t\t\"Ports\",\n\t\t\"Privileged\",\n\t\t\"PullPolicy\",\n\t\t\"ReadOnly\",\n\t\t\"Restart\",\n\t\t\"Runtime\",\n\t\t\"Secrets\",\n\t\t\"Scale\",\n\t\t\"SecurityOpt\",\n\t\t\"ShmSize\",\n\t\t\"StopGracePeriod\",\n\t\t\"StopSignal\",\n\t\t\"Sysctls\",\n\t\t\"StdinOpen\",\n\t\t\"Tmpfs\",\n\t\t\"Tty\",\n\t\t\"User\",\n\t\t\"WorkingDir\",\n\t\t\"Volumes\",\n\t\t\"Ulimits\",\n\t); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: service %s: %+v\", svc.Name, unknown)\n\t}\n\n\tif svc.BlkioConfig != nil {\n\t\tif unknown := reflectutil.UnknownNonEmptyFields(svc.BlkioConfig,\n\t\t\t\"Weight\",\n\t\t); len(unknown) > 0 {\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: blkio_config: %+v\", svc.Name, unknown)\n\t\t}\n\t}\n\n\tfor depName, dep := range svc.DependsOn {\n\t\tif unknown := reflectutil.UnknownNonEmptyFields(&dep,\n\t\t\t\"Condition\",\n\t\t); len(unknown) > 0 {\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: depends_on: %s: %+v\", svc.Name, depName, unknown)\n\t\t}\n\t\tswitch dep.Condition {\n\t\tcase \"\", types.ServiceConditionStarted:\n\t\t\t// NOP\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: depends_on: %s: condition %s\", svc.Name, depName, dep.Condition)\n\t\t}\n\t}\n\n\tif svc.Deploy != nil {\n\t\tif unknown := reflectutil.UnknownNonEmptyFields(svc.Deploy,\n\t\t\t\"Replicas\",\n\t\t\t\"RestartPolicy\",\n\t\t\t\"Resources\",\n\t\t); len(unknown) > 0 {\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy: %+v\", svc.Name, unknown)\n\t\t}\n\t\tif svc.Deploy.RestartPolicy != nil {\n\t\t\tif unknown := reflectutil.UnknownNonEmptyFields(svc.Deploy.RestartPolicy,\n\t\t\t\t\"Condition\",\n\t\t\t); len(unknown) > 0 {\n\t\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.restart_policy: %+v\", svc.Name, unknown)\n\t\t\t}\n\t\t}\n\t\tif unknown := reflectutil.UnknownNonEmptyFields(svc.Deploy.Resources,\n\t\t\t\"Limits\",\n\t\t\t\"Reservations\",\n\t\t); len(unknown) > 0 {\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.resources: %+v\", svc.Name, unknown)\n\t\t}\n\t\tif svc.Deploy.Resources.Limits != nil {\n\t\t\tif unknown := reflectutil.UnknownNonEmptyFields(svc.Deploy.Resources.Limits,\n\t\t\t\t\"NanoCPUs\",\n\t\t\t\t\"MemoryBytes\",\n\t\t\t); len(unknown) > 0 {\n\t\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.resources.resources: %+v\", svc.Name, unknown)\n\t\t\t}\n\t\t}\n\t\tif svc.Deploy.Resources.Reservations != nil {\n\t\t\tif unknown := reflectutil.UnknownNonEmptyFields(svc.Deploy.Resources.Reservations,\n\t\t\t\t\"Devices\",\n\t\t\t); len(unknown) > 0 {\n\t\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.resources.resources.reservations: %+v\", svc.Name, unknown)\n\t\t\t}\n\t\t\tfor i, dev := range svc.Deploy.Resources.Reservations.Devices {\n\t\t\t\tif unknown := reflectutil.UnknownNonEmptyFields(dev,\n\t\t\t\t\t\"Capabilities\",\n\t\t\t\t\t\"Driver\",\n\t\t\t\t\t\"Count\",\n\t\t\t\t\t\"IDs\",\n\t\t\t\t); len(unknown) > 0 {\n\t\t\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.resources.resources.reservations.devices[%d]: %+v\",\n\t\t\t\t\t\tsvc.Name, i, unknown)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// unknown fields of Build is checked in parseBuild().\n}\n\ntype Container struct {\n\tName    string   // e.g., \"compose-wordpress_wordpress_1\"\n\tRunArgs []string // {\"--pull=never\", ...}\n\tMkdir   []string // For Bind.CreateHostPath\n}\n\ntype Build struct {\n\tForce            bool     // force build even if already present\n\tBuildArgs        []string // {\"-t\", \"example.com/foo\", \"--target\", \"foo\", \"/path/to/ctx\"}\n\tDockerfileInline string   // store contents of dockerfile_inline field is specified\n\t// TODO: call BuildKit API directly without executing `nerdctl build`\n}\n\ntype Service struct {\n\tImage      string\n\tPullMode   string\n\tContainers []Container // length = replicas\n\tBuild      *Build\n\tUnparsed   *types.ServiceConfig\n}\n\nfunc getReplicas(svc types.ServiceConfig) (int, error) {\n\treplicas := 1\n\n\t// No need to check svc.Scale, as it is automatically transformed to svc.Deploy.Replicas by compose-go\n\t// https://github.com/compose-spec/compose-go/commit/958cb4f953330a3d1303961796d826b7f79132d7\n\n\tif svc.Deploy != nil && svc.Deploy.Replicas != nil {\n\t\treplicas = int(*svc.Deploy.Replicas) // nolint:unconvert\n\t}\n\n\tif replicas < 0 {\n\t\treturn 0, fmt.Errorf(\"invalid replicas: %d\", replicas)\n\t}\n\treturn replicas, nil\n}\n\nfunc getCPULimit(svc types.ServiceConfig) (string, error) {\n\tvar limit string\n\tif svc.CPUS > 0 {\n\t\tlog.L.Warn(\"cpus is deprecated, use deploy.resources.limits.cpus\")\n\t\tlimit = fmt.Sprintf(\"%f\", svc.CPUS)\n\t}\n\tif svc.Deploy != nil && svc.Deploy.Resources.Limits != nil {\n\t\tif nanoCPUs := svc.Deploy.Resources.Limits.NanoCPUs; nanoCPUs != 0 {\n\t\t\tif svc.CPUS > 0 {\n\t\t\t\tlog.L.Warnf(\"deploy.resources.limits.cpus and cpus (deprecated) must not be set together, ignoring cpus=%f\", svc.CPUS)\n\t\t\t}\n\t\t\tlimit = strconv.FormatFloat(float64(nanoCPUs), 'f', 2, 32)\n\t\t}\n\t}\n\treturn limit, nil\n}\n\nfunc getMemLimit(svc types.ServiceConfig) (types.UnitBytes, error) {\n\tvar limit types.UnitBytes\n\tif svc.MemLimit > 0 {\n\t\tlog.L.Warn(\"mem_limit is deprecated, use deploy.resources.limits.memory\")\n\t\tlimit = svc.MemLimit\n\t}\n\tif svc.Deploy != nil && svc.Deploy.Resources.Limits != nil {\n\t\tif memoryBytes := svc.Deploy.Resources.Limits.MemoryBytes; memoryBytes > 0 {\n\t\t\tif svc.MemLimit > 0 && memoryBytes != svc.MemLimit {\n\t\t\t\tlog.L.Warnf(\"deploy.resources.limits.memory and mem_limit (deprecated) must not be set together, ignoring mem_limit=%d\", svc.MemLimit)\n\t\t\t}\n\t\t\tlimit = memoryBytes\n\t\t}\n\t}\n\treturn limit, nil\n}\n\nfunc getGPUs(svc types.ServiceConfig) (reqs []string, _ error) {\n\t// \"gpu\" and \"nvidia\" are also allowed capabilities (but not used as nvidia driver capabilities)\n\t// https://github.com/moby/moby/blob/v20.10.7/daemon/nvidia_linux.go#L37\n\tcapset := map[string]struct{}{\n\t\t\"gpu\": {}, \"nvidia\": {},\n\t\t// Allow the list of capabilities here (excluding \"all\" and \"none\")\n\t\t// https://github.com/NVIDIA/nvidia-container-toolkit/blob/ff7c2d4866a7d46d1bf2a83590b263e10ec99cb5/internal/config/image/capabilities.go#L28-L38\n\t\t\"compat32\": {},\n\t\t\"compute\":  {},\n\t\t\"display\":  {},\n\t\t\"graphics\": {},\n\t\t\"ngx\":      {},\n\t\t\"utility\":  {},\n\t\t\"video\":    {},\n\t}\n\tif svc.Deploy != nil && svc.Deploy.Resources.Reservations != nil {\n\t\tfor _, dev := range svc.Deploy.Resources.Reservations.Devices {\n\t\t\tif len(dev.Capabilities) == 0 {\n\t\t\t\t// \"capabilities\" is required.\n\t\t\t\t// https://github.com/compose-spec/compose-spec/blob/74b933db994109616580eab8f47bf2ba226e0faa/deploy.md#devices\n\t\t\t\treturn nil, fmt.Errorf(\"service %s: specifying \\\"capabilities\\\" is required for resource reservations\", svc.Name)\n\t\t\t}\n\n\t\t\tvar requiresGPU bool\n\t\t\tfor _, c := range dev.Capabilities {\n\t\t\t\tif _, ok := capset[c]; ok {\n\t\t\t\t\trequiresGPU = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !requiresGPU {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar e []string\n\t\t\tif len(dev.Capabilities) > 0 {\n\t\t\t\te = append(e, fmt.Sprintf(\"capabilities=%s\", strings.Join(dev.Capabilities, \",\")))\n\t\t\t}\n\t\t\tif dev.Driver != \"\" {\n\t\t\t\te = append(e, fmt.Sprintf(\"driver=%s\", dev.Driver))\n\t\t\t}\n\t\t\tif len(dev.IDs) > 0 {\n\t\t\t\te = append(e, fmt.Sprintf(\"device=%s\", strings.Join(dev.IDs, \",\")))\n\t\t\t}\n\t\t\tif dev.Count != 0 {\n\t\t\t\te = append(e, fmt.Sprintf(\"count=%d\", dev.Count))\n\t\t\t}\n\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\tw := csv.NewWriter(buf)\n\t\t\tif err := w.Write(e); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tw.Flush()\n\t\t\to := buf.Bytes()\n\t\t\tif len(o) > 0 {\n\t\t\t\treqs = append(reqs, string(o[:len(o)-1])) // remove carriage return\n\t\t\t}\n\t\t}\n\t}\n\treturn reqs, nil\n}\n\nvar restartFailurePat = regexp.MustCompile(`^on-failure:\\d+$`)\n\n// getRestart returns `nerdctl run --restart` flag string\n//\n// restart:                         {\"no\" (default), \"always\", \"on-failure\", \"unless-stopped\"} (https://github.com/compose-spec/compose-spec/blob/167f207d0a8967df87c5ed757dbb1a2bb6025a1e/spec.md#restart)\n// deploy.restart_policy.condition: {\"none\", \"on-failure\", \"any\" (default)}                    (https://github.com/compose-spec/compose-spec/blob/167f207d0a8967df87c5ed757dbb1a2bb6025a1e/deploy.md#restart_policy)\nfunc getRestart(svc types.ServiceConfig) (string, error) {\n\tvar restartFlag string\n\tswitch svc.Restart {\n\tcase \"\":\n\t\trestartFlag = \"no\"\n\tcase \"no\", \"always\", \"on-failure\", \"unless-stopped\":\n\t\trestartFlag = svc.Restart\n\tdefault:\n\t\tif restartFailurePat.MatchString(svc.Restart) {\n\t\t\trestartFlag = svc.Restart\n\t\t} else {\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: restart=%q (unknown)\", svc.Name, svc.Restart)\n\t\t}\n\t}\n\n\tif svc.Deploy != nil && svc.Deploy.RestartPolicy != nil {\n\t\tif svc.Restart != \"\" {\n\t\t\tlog.L.Warnf(\"deploy.restart_policy and restart must not be set together, ignoring restart=%s\", svc.Restart)\n\t\t}\n\t\tswitch cond := svc.Deploy.RestartPolicy.Condition; cond {\n\t\tcase \"\", \"any\":\n\t\t\trestartFlag = \"always\"\n\t\tcase \"always\":\n\t\t\treturn \"\", fmt.Errorf(\"deploy.restart_policy.condition: \\\"always\\\" is invalid, did you mean \\\"any\\\"?\")\n\t\tcase \"none\":\n\t\t\trestartFlag = \"no\"\n\t\tcase \"no\":\n\t\t\treturn \"\", fmt.Errorf(\"deploy.restart_policy.condition: \\\"no\\\" is invalid, did you mean \\\"none\\\"?\")\n\t\tcase \"on-failure\":\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.restart_policy.condition=%q (unimplemented)\", svc.Name, cond)\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"Ignoring: service %s: deploy.restart_policy.condition=%q (unknown)\", svc.Name, cond)\n\t\t}\n\t}\n\n\treturn restartFlag, nil\n}\n\ntype networkNamePair struct {\n\tshortNetworkName string\n\tfullName         string\n}\n\n// getNetworks returns full network names, e.g., {\"compose-wordpress_default\"}, or {\"host\"}\nfunc getNetworks(project *types.Project, svc types.ServiceConfig) ([]networkNamePair, error) {\n\tvar fullNames []networkNamePair // nolint: prealloc\n\n\tif svc.Net != \"\" {\n\t\tlog.L.Warn(\"net is deprecated, use network_mode or networks\")\n\t\tif len(svc.Networks) > 0 {\n\t\t\treturn nil, errors.New(\"networks and net must not be set together\")\n\t\t}\n\n\t\tfullNames = append(fullNames, networkNamePair{\n\t\t\tfullName:         svc.Net,\n\t\t\tshortNetworkName: \"\",\n\t\t})\n\t}\n\n\tif svc.NetworkMode != \"\" {\n\t\tif len(svc.Networks) > 0 {\n\t\t\treturn nil, errors.New(\"networks and network_mode must not be set together\")\n\t\t}\n\t\tif svc.Net != \"\" && svc.NetworkMode != svc.Net {\n\t\t\treturn nil, errors.New(\"net and network_mode must not be set together\")\n\t\t}\n\t\tif strings.Contains(svc.NetworkMode, \":\") {\n\t\t\tif !strings.HasPrefix(svc.NetworkMode, \"container:\") && !strings.HasPrefix(svc.NetworkMode, \"ns:\") {\n\t\t\t\treturn nil, fmt.Errorf(\"unsupported network_mode: %q\", svc.NetworkMode)\n\t\t\t}\n\t\t}\n\t\tfullNames = append(fullNames, networkNamePair{\n\t\t\tfullName:         svc.NetworkMode,\n\t\t\tshortNetworkName: \"\",\n\t\t})\n\t}\n\n\tfor shortName := range svc.Networks {\n\t\tnet, ok := project.Networks[shortName]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"invalid network %q\", shortName)\n\t\t}\n\t\tfullNames = append(fullNames, networkNamePair{\n\t\t\tfullName:         net.Name,\n\t\t\tshortNetworkName: shortName,\n\t\t})\n\t}\n\n\treturn fullNames, nil\n}\n\nfunc Parse(project *types.Project, svc types.ServiceConfig) (*Service, error) {\n\twarnUnknownFields(svc)\n\n\treplicas, err := getReplicas(svc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tparsed := &Service{\n\t\tImage:      svc.Image,\n\t\tPullMode:   \"missing\",\n\t\tContainers: make([]Container, replicas),\n\t\tUnparsed:   &svc,\n\t}\n\n\tif svc.Build == nil {\n\t\tif parsed.Image == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"service %s: missing image\", svc.Name)\n\t\t}\n\t} else {\n\t\tif parsed.Image == \"\" {\n\t\t\tparsed.Image = DefaultImageName(project.Name, svc.Name)\n\t\t}\n\t\tparsed.Build, err = parseBuildConfig(svc.Build, project, parsed.Image)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"service %s: failed to parse build: %w\", svc.Name, err)\n\t\t}\n\t}\n\n\tswitch svc.PullPolicy {\n\tcase \"\", types.PullPolicyMissing, types.PullPolicyIfNotPresent:\n\t\t// NOP\n\tcase types.PullPolicyAlways, types.PullPolicyNever:\n\t\tparsed.PullMode = svc.PullPolicy\n\tcase types.PullPolicyBuild:\n\t\tif parsed.Build == nil {\n\t\t\treturn nil, fmt.Errorf(\"service %s: pull_policy \\\"build\\\" requires build config\", svc.Name)\n\t\t}\n\t\tparsed.Build.Force = true\n\t\tparsed.PullMode = \"never\"\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid --pull option %q\", svc.PullPolicy)\n\t}\n\n\tfor i := 0; i < replicas; i++ {\n\t\tcontainer, err := newContainer(project, parsed, i)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparsed.Containers[i] = *container\n\t}\n\n\treturn parsed, nil\n}\n\nfunc newContainer(project *types.Project, parsed *Service, i int) (*Container, error) {\n\tsvc := *parsed.Unparsed\n\tvar c Container\n\tc.Name = DefaultContainerName(project.Name, svc.Name, strconv.Itoa(i+1))\n\tif svc.ContainerName != \"\" {\n\t\tif i != 0 {\n\t\t\treturn nil, errors.New(\"container_name must not be specified when replicas != 1\")\n\t\t}\n\t\tc.Name = svc.ContainerName\n\t}\n\n\tc.RunArgs = []string{\n\t\t\"--name=\" + c.Name,\n\t\t\"--pull=never\", // because image will be ensured before running replicas with `nerdctl run`.\n\t}\n\n\tfor k, v := range svc.Annotations {\n\t\tif v == \"\" {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--annotation=%s\", k))\n\t\t} else {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--annotation=%s=%s\", k, v))\n\t\t}\n\t}\n\n\tif svc.BlkioConfig != nil && svc.BlkioConfig.Weight != 0 {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--blkio-weight=%d\", svc.BlkioConfig.Weight))\n\t}\n\n\tfor _, v := range svc.CapAdd {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--cap-add=%s\", v))\n\t}\n\n\tfor _, v := range svc.CapDrop {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--cap-drop=%s\", v))\n\t}\n\n\tif cpuLimit, err := getCPULimit(svc); err != nil {\n\t\treturn nil, err\n\t} else if cpuLimit != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--cpus=%s\", cpuLimit))\n\t}\n\n\tif svc.CPUSet != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--cpuset-cpus=%s\", svc.CPUSet))\n\t}\n\n\tif svc.CPUShares != 0 {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--cpu-shares=%d\", svc.CPUShares))\n\t}\n\n\tfor _, v := range svc.Devices {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--device=%s:%s:%s\", v.Source, v.Target, v.Permissions))\n\t}\n\n\tfor _, v := range svc.DNS {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--dns=%s\", v))\n\t}\n\tfor _, v := range svc.DNSSearch {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--dns-search=%s\", v))\n\t}\n\tfor _, v := range svc.DNSOpts {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--dns-option=%s\", v))\n\t}\n\n\tfor _, v := range svc.Entrypoint {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--entrypoint=%s\", v))\n\t}\n\n\tfor k, v := range svc.Environment {\n\t\tif v == nil {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"-e=%s\", k))\n\t\t} else {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"-e=%s=%s\", k, *v))\n\t\t}\n\t}\n\tfor k, v := range svc.ExtraHosts {\n\t\tfor _, h := range v {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--add-host=%s:%s\", k, h))\n\t\t}\n\t}\n\n\tif svc.Init != nil && *svc.Init {\n\t\tc.RunArgs = append(c.RunArgs, \"--init\")\n\t}\n\n\tif memLimit, err := getMemLimit(svc); err != nil {\n\t\treturn nil, err\n\t} else if memLimit > 0 {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"-m=%d\", memLimit))\n\t}\n\n\tif gpuReqs, err := getGPUs(svc); err != nil {\n\t\treturn nil, err\n\t} else if len(gpuReqs) > 0 {\n\t\tfor _, gpus := range gpuReqs {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--gpus=%s\", gpus))\n\t\t}\n\t}\n\n\tfor k, v := range svc.Labels {\n\t\tif v == \"\" {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"-l=%s\", k))\n\t\t} else {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"-l=%s=%s\", k, v))\n\t\t}\n\t}\n\n\tif svc.Logging != nil {\n\t\tif svc.Logging.Driver != \"\" {\n\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--log-driver=%s\", svc.Logging.Driver))\n\t\t}\n\t\tif svc.Logging.Options != nil {\n\t\t\tfor k, v := range svc.Logging.Options {\n\t\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--log-opt=%s=%s\", k, v))\n\t\t\t}\n\t\t}\n\t}\n\n\tnetworks, err := getNetworks(project, svc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetTypeContainer := false\n\tfor _, net := range networks {\n\t\tif strings.HasPrefix(net.fullName, \"container:\") {\n\t\t\tnetTypeContainer = true\n\t\t}\n\t\tc.RunArgs = append(c.RunArgs, \"--net=\"+net.fullName)\n\t\tif value, ok := svc.Networks[net.shortNetworkName]; ok {\n\t\t\tif value != nil && value.Ipv4Address != \"\" {\n\t\t\t\tc.RunArgs = append(c.RunArgs, \"--ip=\"+value.Ipv4Address)\n\t\t\t}\n\t\t\tif value != nil && value.MacAddress != \"\" {\n\t\t\t\tc.RunArgs = append(c.RunArgs, \"--mac-address=\"+value.MacAddress)\n\t\t\t}\n\t\t}\n\t}\n\n\tif netTypeContainer && svc.Hostname != \"\" {\n\t\treturn nil, fmt.Errorf(\"conflicting options: hostname and container network mode\")\n\t}\n\tif !netTypeContainer {\n\t\thostname := svc.Hostname\n\t\tif hostname == \"\" {\n\t\t\thostname = svc.Name\n\t\t}\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--hostname=%s\", hostname))\n\t}\n\n\tif svc.Pid != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, \"--pid=\"+svc.Pid)\n\t}\n\n\tif svc.PidsLimit > 0 {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--pids-limit=%d\", svc.PidsLimit))\n\t}\n\n\tif svc.Ulimits != nil {\n\t\tfor utype, ulimit := range svc.Ulimits {\n\t\t\tif ulimit.Single != 0 {\n\t\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--ulimit=%s=%d\", utype, ulimit.Single))\n\t\t\t} else {\n\t\t\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--ulimit=%s=%d:%d\", utype, ulimit.Soft, ulimit.Hard))\n\t\t\t}\n\t\t}\n\t}\n\n\tif svc.Platform != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, \"--platform=\"+svc.Platform)\n\t}\n\n\tfor _, p := range svc.Ports {\n\t\tpStr, err := servicePortConfigToFlagP(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.RunArgs = append(c.RunArgs, \"-p=\"+pStr)\n\t}\n\n\tif svc.Privileged {\n\t\tc.RunArgs = append(c.RunArgs, \"--privileged\")\n\t}\n\n\tif svc.ReadOnly {\n\t\tc.RunArgs = append(c.RunArgs, \"--read-only\")\n\t}\n\n\tif svc.StopGracePeriod != nil {\n\t\ttimeout := time.Duration(*svc.StopGracePeriod)\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--stop-timeout=%d\", int(timeout.Seconds())))\n\t}\n\tif svc.StopSignal != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--stop-signal=%s\", svc.StopSignal))\n\t}\n\n\tif restart, err := getRestart(svc); err != nil {\n\t\treturn nil, err\n\t} else if restart != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--restart=%s\", restart))\n\t}\n\n\tif svc.Runtime != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, \"--runtime=\"+svc.Runtime)\n\t}\n\n\tif svc.ShmSize > 0 {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--shm-size=%d\", svc.ShmSize))\n\t}\n\n\tfor _, v := range svc.SecurityOpt {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--security-opt=%s\", v))\n\t}\n\n\tfor k, v := range svc.Sysctls {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--sysctl=%s=%s\", k, v))\n\t}\n\n\tif svc.StdinOpen {\n\t\tc.RunArgs = append(c.RunArgs, \"--interactive\")\n\t}\n\n\tif svc.User != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, \"--user=\"+svc.User)\n\t}\n\n\tfor _, v := range svc.GroupAdd {\n\t\tc.RunArgs = append(c.RunArgs, fmt.Sprintf(\"--group-add=%s\", v))\n\t}\n\n\tfor _, v := range svc.Volumes {\n\t\tvStr, mkdir, err := serviceVolumeConfigToFlagV(v, project)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch v.Type {\n\t\tcase \"tmpfs\":\n\t\t\tc.RunArgs = append(c.RunArgs, \"--tmpfs=\"+vStr)\n\t\tdefault:\n\t\t\tc.RunArgs = append(c.RunArgs, \"-v=\"+vStr)\n\t\t}\n\n\t\tc.Mkdir = mkdir\n\t}\n\n\tfor _, config := range svc.Configs {\n\t\tfileRef := types.FileReferenceConfig(config)\n\t\tvStr, err := fileReferenceConfigToFlagV(fileRef, project, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.RunArgs = append(c.RunArgs, \"-v=\"+vStr)\n\t}\n\n\tfor _, secret := range svc.Secrets {\n\t\tfileRef := types.FileReferenceConfig(secret)\n\t\tvStr, err := fileReferenceConfigToFlagV(fileRef, project, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.RunArgs = append(c.RunArgs, \"-v=\"+vStr)\n\t}\n\n\tfor _, tmpfs := range svc.Tmpfs {\n\t\tc.RunArgs = append(c.RunArgs, \"--tmpfs=\"+tmpfs)\n\t}\n\n\tif svc.Tty {\n\t\tc.RunArgs = append(c.RunArgs, \"--tty\")\n\t}\n\n\tif svc.WorkingDir != \"\" {\n\t\tc.RunArgs = append(c.RunArgs, \"-w=\"+svc.WorkingDir)\n\t}\n\n\tc.RunArgs = append(c.RunArgs, parsed.Image) // NOT svc.Image\n\tc.RunArgs = append(c.RunArgs, svc.Command...)\n\treturn &c, nil\n}\n\nfunc servicePortConfigToFlagP(c types.ServicePortConfig) (string, error) {\n\tif unknown := reflectutil.UnknownNonEmptyFields(&c,\n\t\t\"Mode\",\n\t\t\"HostIP\",\n\t\t\"Target\",\n\t\t\"Published\",\n\t\t\"Protocol\",\n\t); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: port: %+v\", unknown)\n\t}\n\tswitch c.Mode {\n\tcase \"\", \"ingress\":\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unsupported port mode: %s\", c.Mode)\n\t}\n\tif c.Target <= 0 {\n\t\treturn \"\", fmt.Errorf(\"unsupported port number: %d\", c.Target)\n\t}\n\ts := fmt.Sprintf(\"%s:%d\", c.Published, c.Target)\n\tif c.HostIP != \"\" {\n\t\tif strings.Contains(c.HostIP, \":\") {\n\t\t\ts = fmt.Sprintf(\"[%s]:%s\", c.HostIP, s)\n\t\t} else {\n\t\t\ts = fmt.Sprintf(\"%s:%s\", c.HostIP, s)\n\t\t}\n\t}\n\tif c.Protocol != \"\" {\n\t\ts = fmt.Sprintf(\"%s/%s\", s, c.Protocol)\n\t}\n\treturn s, nil\n}\n\nfunc serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Project) (flagV string, mkdir []string, err error) {\n\tif unknown := reflectutil.UnknownNonEmptyFields(&c,\n\t\t\"Type\",\n\t\t\"Source\",\n\t\t\"Target\",\n\t\t\"ReadOnly\",\n\t\t\"Bind\",\n\t\t\"Volume\",\n\t\t\"Tmpfs\",\n\t); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: volume: %+v\", unknown)\n\t}\n\tif c.Bind != nil {\n\t\tif unknown := reflectutil.UnknownNonEmptyFields(c.Bind, \"CreateHostPath\", \"Propagation\"); len(unknown) > 0 {\n\t\t\tlog.L.Warnf(\"Ignoring: volume: Bind: %+v\", unknown)\n\t\t}\n\t}\n\tif c.Volume != nil {\n\t\t// c.Volume is expected to be a non-nil reference to an empty Volume struct\n\t\tif unknown := reflectutil.UnknownNonEmptyFields(c.Volume); len(unknown) > 0 {\n\t\t\tlog.L.Warnf(\"Ignoring: volume: Volume: %+v\", unknown)\n\t\t}\n\t}\n\n\tif c.Target == \"\" {\n\t\treturn \"\", nil, errors.New(\"volume target is missing\")\n\t}\n\tif !filepath.IsAbs(c.Target) {\n\t\treturn \"\", nil, fmt.Errorf(\"volume target must be an absolute path, got %q\", c.Target)\n\t}\n\n\tif c.Type == \"tmpfs\" {\n\t\tvar opts []string\n\n\t\tif c.ReadOnly {\n\t\t\topts = append(opts, \"ro\")\n\t\t}\n\t\tif c.Tmpfs != nil {\n\t\t\tif c.Tmpfs.Size != 0 {\n\t\t\t\topts = append(opts, fmt.Sprintf(\"size=%d\", c.Tmpfs.Size))\n\t\t\t}\n\t\t\tif c.Tmpfs.Mode != 0 {\n\t\t\t\topts = append(opts, fmt.Sprintf(\"mode=%o\", c.Tmpfs.Mode))\n\t\t\t}\n\t\t}\n\n\t\ts := c.Target\n\t\tif len(opts) > 0 {\n\t\t\ts = fmt.Sprintf(\"%s:%s\", s, strings.Join(opts, \",\"))\n\t\t}\n\n\t\treturn s, mkdir, nil\n\t}\n\n\tif c.Source == \"\" {\n\t\t// anonymous volume\n\t\ts := c.Target\n\t\tif c.ReadOnly {\n\t\t\ts += \":ro\"\n\t\t}\n\t\treturn s, mkdir, nil\n\t}\n\n\tvar src string\n\tvar opts []string\n\tswitch c.Type {\n\tcase \"volume\":\n\t\tvol, ok := project.Volumes[c.Source]\n\t\tif !ok {\n\t\t\treturn \"\", nil, fmt.Errorf(\"invalid volume %q\", c.Source)\n\t\t}\n\t\t// c.Source is like \"db_data\", vol.Name is like \"compose-wordpress_db_data\"\n\t\tsrc = vol.Name\n\tcase \"bind\":\n\t\tsrc = project.RelativePath(c.Source)\n\t\tvar err error\n\t\tsrc, err = filepath.Abs(src)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"invalid relative path %q: %w\", c.Source, err)\n\t\t}\n\t\tif c.Bind != nil && c.Bind.CreateHostPath {\n\t\t\tif _, stErr := os.Stat(src); errors.Is(stErr, os.ErrNotExist) {\n\t\t\t\tmkdir = append(mkdir, src)\n\t\t\t}\n\t\t}\n\t\tif c.Bind != nil && c.Bind.Propagation != \"\" {\n\t\t\topts = append(opts, c.Bind.Propagation)\n\t\t}\n\tdefault:\n\t\treturn \"\", nil, fmt.Errorf(\"unsupported volume type: %q\", c.Type)\n\t}\n\ts := fmt.Sprintf(\"%s:%s\", src, c.Target)\n\tif c.ReadOnly {\n\t\topts = append(opts, \"ro\")\n\t}\n\tif len(opts) > 0 {\n\t\ts = fmt.Sprintf(\"%s:%s\", s, strings.Join(opts, \",\"))\n\t}\n\treturn s, mkdir, nil\n}\n\nfunc fileReferenceConfigToFlagV(c types.FileReferenceConfig, project *types.Project, secret bool) (string, error) {\n\tobjType := \"config\"\n\tif secret {\n\t\tobjType = \"secret\"\n\t}\n\tif unknown := reflectutil.UnknownNonEmptyFields(&c,\n\t\t\"Source\", \"Target\", \"UID\", \"GID\", \"Mode\",\n\t); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: %s: %+v\", objType, unknown)\n\t}\n\n\tif err := identifiers.ValidateDockerCompat(c.Source); err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid source name for %s: %w\", objType, err)\n\t}\n\n\tvar obj types.FileObjectConfig\n\tif secret {\n\t\tsecret, ok := project.Secrets[c.Source]\n\t\tif !ok {\n\t\t\treturn \"\", fmt.Errorf(\"secret %s is undefined\", c.Source)\n\t\t}\n\t\tobj = types.FileObjectConfig(secret)\n\t} else {\n\t\tconfig, ok := project.Configs[c.Source]\n\t\tif !ok {\n\t\t\treturn \"\", fmt.Errorf(\"config %s is undefined\", c.Source)\n\t\t}\n\t\tobj = types.FileObjectConfig(config)\n\t}\n\tsrc := project.RelativePath(obj.File)\n\tvar err error\n\tsrc, err = filepath.Abs(src)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%s %s: invalid relative path %q: %w\", objType, c.Source, src, err)\n\t}\n\n\ttarget := c.Target\n\tif target == \"\" {\n\t\tif secret {\n\t\t\ttarget = filepath.Join(\"/run/secrets\", c.Source)\n\t\t} else {\n\t\t\ttarget = filepath.Join(\"/\", c.Source)\n\t\t}\n\t} else {\n\t\ttarget = filepath.Clean(target)\n\t\tif !filepath.IsAbs(target) {\n\t\t\tif secret {\n\t\t\t\ttarget = filepath.Join(\"/run/secrets\", target)\n\t\t\t} else {\n\t\t\t\treturn \"\", fmt.Errorf(\"config %s: target %q must be an absolute path\", c.Source, c.Target)\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.UID != \"\" {\n\t\t// Raise an error rather than ignoring the value, for avoiding any security issue\n\t\treturn \"\", fmt.Errorf(\"%s %s: unsupported field: UID\", objType, c.Source)\n\t}\n\tif c.GID != \"\" {\n\t\treturn \"\", fmt.Errorf(\"%s %s: unsupported field: GID\", objType, c.Source)\n\t}\n\tif c.Mode != nil {\n\t\treturn \"\", fmt.Errorf(\"%s %s: unsupported field: Mode\", objType, c.Source)\n\t}\n\n\ts := fmt.Sprintf(\"%s:%s:ro\", src, target)\n\treturn s, nil\n}\n\n// DefaultImageName returns the image name following compose naming logic.\nfunc DefaultImageName(projectName string, serviceName string) string {\n\treturn projectName + Separator + serviceName\n}\n\n// DefaultContainerName returns the service container name following compose naming logic.\nfunc DefaultContainerName(projectName, serviceName, suffix string) string {\n\treturn DefaultImageName(projectName, serviceName) + Separator + suffix\n}\n"
  },
  {
    "path": "pkg/composer/serviceparser/serviceparser_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage serviceparser\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc TestServicePortConfigToFlagP(t *testing.T) {\n\tt.Parallel()\n\ttype testCase struct {\n\t\ttypes.ServicePortConfig\n\t\texpected string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tServicePortConfig: types.ServicePortConfig{\n\t\t\t\tMode:      \"ingress\",\n\t\t\t\tTarget:    80,\n\t\t\t\tPublished: \"8080\",\n\t\t\t\tProtocol:  \"tcp\",\n\t\t\t},\n\t\t\texpected: \"8080:80/tcp\",\n\t\t},\n\t\t{\n\t\t\tServicePortConfig: types.ServicePortConfig{\n\t\t\t\tHostIP:    \"127.0.0.1\",\n\t\t\t\tTarget:    80,\n\t\t\t\tPublished: \"8080\",\n\t\t\t},\n\t\t\texpected: \"127.0.0.1:8080:80\",\n\t\t},\n\t\t{\n\t\t\tServicePortConfig: types.ServicePortConfig{\n\t\t\t\tHostIP: \"127.0.0.1\",\n\t\t\t\tTarget: 80,\n\t\t\t},\n\t\t\texpected: \"127.0.0.1::80\",\n\t\t},\n\t}\n\tfor i, tc := range testCases {\n\t\tgot, err := servicePortConfigToFlagP(tc.ServicePortConfig)\n\t\tif tc.expected == \"\" {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"#%d: error is expected\", i)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tassert.NilError(t, err)\n\t\tassert.Equal(t, tc.expected, got)\n\t}\n}\n\nvar in = strutil.InStringSlice\n\nfunc TestParse(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\n\tconst dockerComposeYAML = `\nversion: '3.1'\n\nservices:\n\n  wordpress:\n    ulimits:\n      nproc: 500\n      nofile:\n        soft: 20000\n        hard: 20000\n    image: wordpress:5.7\n    restart: always\n    ports:\n      - 8080:80\n    extra_hosts:\n      test.com: 172.19.1.1\n      test2.com: 172.19.1.2\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - wordpress:/var/www/html\n    pids_limit: 100\n    shm_size: 1G\n    dns:\n      - 8.8.8.8\n      - 8.8.4.4\n    dns_search: example.com\n    dns_opt:\n      - no-tld-query\n    logging:\n      driver: json-file\n      options:\n        max-size: \"5K\"\n        max-file: \"2\"\n    user: 1001:1001\n    group_add:\n      - \"1001\"\n\n  db:\n    image: mariadb:10.5\n    restart: always\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db:/var/lib/mysql\n    stop_grace_period: 1m30s\n    stop_signal: SIGUSR1\n\nvolumes:\n  wordpress:\n  db:\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\twpSvc, err := project.GetService(\"wordpress\")\n\tassert.NilError(t, err)\n\n\twp, err := Parse(project, wpSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"wordpress: %+v\", wp)\n\tassert.Assert(t, wp.PullMode == \"missing\")\n\tassert.Assert(t, wp.Image == \"wordpress:5.7\")\n\tassert.Assert(t, len(wp.Containers) == 1)\n\twp1 := wp.Containers[0]\n\tassert.Assert(t, wp1.Name == DefaultContainerName(project.Name, \"wordpress\", \"1\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--name=\"+wp1.Name))\n\tassert.Assert(t, in(wp1.RunArgs, \"--hostname=wordpress\"))\n\tassert.Assert(t, in(wp1.RunArgs, fmt.Sprintf(\"--net=%s_default\", project.Name)))\n\tassert.Assert(t, in(wp1.RunArgs, \"--restart=always\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"-e=WORDPRESS_DB_HOST=db\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"-e=WORDPRESS_DB_USER=exampleuser\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"-p=8080:80/tcp\"))\n\tassert.Assert(t, in(wp1.RunArgs, fmt.Sprintf(\"-v=%s_wordpress:/var/www/html\", project.Name)))\n\tassert.Assert(t, in(wp1.RunArgs, \"--pids-limit=100\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--ulimit=nproc=500\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--ulimit=nofile=20000:20000\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--dns=8.8.8.8\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--dns=8.8.4.4\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--dns-search=example.com\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--dns-option=no-tld-query\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--log-driver=json-file\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--log-opt=max-size=5K\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--log-opt=max-file=2\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--add-host=test.com:172.19.1.1\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--add-host=test2.com:172.19.1.2\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--shm-size=1073741824\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--user=1001:1001\"))\n\tassert.Assert(t, in(wp1.RunArgs, \"--group-add=1001\"))\n\n\tdbSvc, err := project.GetService(\"db\")\n\tassert.NilError(t, err)\n\n\tdb, err := Parse(project, dbSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"db: %+v\", db)\n\tassert.Assert(t, len(db.Containers) == 1)\n\tdb1 := db.Containers[0]\n\tassert.Assert(t, db1.Name == DefaultContainerName(project.Name, \"db\", \"1\"))\n\tassert.Assert(t, in(db1.RunArgs, \"--hostname=db\"))\n\tassert.Assert(t, in(db1.RunArgs, fmt.Sprintf(\"-v=%s_db:/var/lib/mysql\", project.Name)))\n\tassert.Assert(t, in(db1.RunArgs, \"--stop-signal=SIGUSR1\"))\n\tassert.Assert(t, in(db1.RunArgs, \"--stop-timeout=90\"))\n}\n\nfunc TestParseDeprecated(t *testing.T) {\n\tt.Parallel()\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    # scale was deprecated in favor of deploy.replicas, and is now ignored\n    scale: 2\n    # cpus is deprecated in favor of deploy.resources.limits.cpu, but still valid\n    cpus: 0.42\n    # mem_limit is deprecated in favor of deploy.resources.limits.memory, but still valid\n    mem_limit: 42m\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tassert.Assert(t, len(foo.Containers) == 1)\n\tfor i, c := range foo.Containers {\n\t\tassert.Assert(t, c.Name == DefaultContainerName(project.Name, \"foo\", strconv.Itoa(i+1)))\n\t\tassert.Assert(t, in(c.RunArgs, \"--name=\"+c.Name))\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"--cpus=%f\", 0.42)))\n\t\tassert.Assert(t, in(c.RunArgs, \"-m=44040192\"))\n\t}\n}\n\nfunc TestParseDeploy(t *testing.T) {\n\tt.Parallel()\n\tconst dockerComposeYAML = `\nservices:\n  foo: # restart=no\n    image: nginx:alpine\n    deploy:\n      replicas: 3\n      resources:\n        limits:\n          cpus: \"0.42\"\n          memory: \"42m\"\n  bar: # restart=always\n    image: nginx:alpine\n    deploy:\n      restart_policy: {}\n      resources:\n        reservations:\n          devices:\n          - capabilities: [\"gpu\", \"utility\", \"compute\"]\n            driver: nvidia\n            count: 2\n          - capabilities: [\"nvidia\"]\n            device_ids: [\"dummy\", \"dummy2\"]\n  baz: # restart=no\n    image: nginx:alpine\n    deploy:\n      restart_policy:\n        condition: none\n      resources:\n        reservations:\n          devices:\n          - capabilities: [\"utility\"]\n            count: all\n  qux: # replicas=0\n    image: nginx:alpine\n    deploy:\n      replicas: 0\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tassert.Assert(t, len(foo.Containers) == 3)\n\tfor i, c := range foo.Containers {\n\t\tassert.Assert(t, c.Name == DefaultContainerName(project.Name, \"foo\", strconv.Itoa(i+1)))\n\t\tassert.Assert(t, in(c.RunArgs, \"--name=\"+c.Name))\n\n\t\tassert.Assert(t, in(c.RunArgs, \"--restart=no\"))\n\t\tassert.Assert(t, in(c.RunArgs, \"--cpus=0.42\"))\n\t\tassert.Assert(t, in(c.RunArgs, \"-m=44040192\"))\n\t}\n\n\tbarSvc, err := project.GetService(\"bar\")\n\tassert.NilError(t, err)\n\n\tbar, err := Parse(project, barSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"bar: %+v\", bar)\n\tassert.Assert(t, len(bar.Containers) == 1)\n\tfor _, c := range bar.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"--restart=always\"))\n\t\tassert.Assert(t, in(c.RunArgs, `--gpus=\"capabilities=gpu,utility,compute\",driver=nvidia,count=2`))\n\t\tassert.Assert(t, in(c.RunArgs, `--gpus=capabilities=nvidia,\"device=dummy,dummy2\"`))\n\t}\n\n\tbazSvc, err := project.GetService(\"baz\")\n\tassert.NilError(t, err)\n\n\tbaz, err := Parse(project, bazSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"baz: %+v\", baz)\n\tassert.Assert(t, len(baz.Containers) == 1)\n\tfor _, c := range baz.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"--restart=no\"))\n\t\tassert.Assert(t, in(c.RunArgs, `--gpus=capabilities=utility,count=-1`))\n\t}\n\n\tquxSvc, err := project.GetService(\"qux\")\n\tassert.NilError(t, err)\n\n\tqux, err := Parse(project, quxSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"qux: %+v\", qux)\n\tassert.Assert(t, len(qux.Containers) == 0)\n\n}\n\nfunc TestParseDevices(t *testing.T) {\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    devices:\n      - /dev/a\n      - /dev/b:/dev/b\n      - /dev/c:/dev/c:rw\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tfor _, c := range foo.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"--device=/dev/a:/dev/a:rwm\"))\n\t\tassert.Assert(t, in(c.RunArgs, \"--device=/dev/b:/dev/b:rwm\"))\n\t\tassert.Assert(t, in(c.RunArgs, \"--device=/dev/c:/dev/c:rw\"))\n\t}\n}\n\nfunc TestParseRelative(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    volumes:\n    - \"/file1:/file1\"\n    - \"./file2:/file2\"\n    # break out the project dir, but this is fine\n    - \"../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../file3:/file3\"\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tfor _, c := range foo.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"-v=/file1:/file1\"))\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"-v=%s:/file2\", filepath.Join(project.WorkingDir, \"file2\"))))\n\t\tassert.Assert(t, in(c.RunArgs, \"-v=/file3:/file3\"))\n\t}\n}\n\nfunc TestParseVolumeLongSyntax(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    volumes:\n    - type: bind\n      source: /src/dir1\n      target: /tgt/dir1\n      read_only: true\n      bind:\n        propagation: rshared\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tfor _, c := range foo.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"-v=/src/dir1:/tgt/dir1:rshared,ro\"))\n\t}\n}\n\nfunc TestTmpfsVolumeLongSyntax(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    volumes:\n      - type: tmpfs\n        target: /target\n        read_only: true\n        tmpfs:\n          size: 2G\n          mode: 0o1770\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tfor _, c := range foo.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"--tmpfs=/target:ro,size=2147483648,mode=1770\"))\n\t}\n}\n\nfunc TestParseNetworkMode(t *testing.T) {\n\tt.Parallel()\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    network_mode: host\n    container_name: nginx\n  bar:\n    image: alpine:3.14\n    network_mode: container:nginx\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tfor _, c := range foo.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"--net=host\"))\n\t}\n\n\tbarSvc, err := project.GetService(\"bar\")\n\tassert.NilError(t, err)\n\n\tbar, err := Parse(project, barSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"bar: %+v\", bar)\n\tfor _, c := range bar.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, \"--net=container:nginx\"))\n\t\tassert.Assert(t, !in(c.RunArgs, \"--hostname=bar\"))\n\t}\n\n}\n\nfunc TestParseConfigs(t *testing.T) {\n\tt.Parallel()\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\tconst dockerComposeYAML = `\nservices:\n  foo:\n    image: nginx:alpine\n    secrets:\n    - secret1\n    - source: secret2\n      target: secret2-foo\n    - source: secret3\n      target: /mnt/secret3-foo\n    configs:\n    - config1\n    - source: config2\n      target: /mnt/config2-foo\nsecrets:\n  secret1:\n    file: ./secret1\n  secret2:\n    file: ./secret2\n  secret3:\n    file: ./secret3\nconfigs:\n  config1:\n    file: ./config1\n  config2:\n    file: ./config2\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tfor _, f := range []string{\"secret1\", \"secret2\", \"secret3\", \"config1\", \"config2\"} {\n\t\terr = filesystem.WriteFile(filepath.Join(project.WorkingDir, f), []byte(\"content-\"+f), 0444)\n\t\tassert.NilError(t, err)\n\t}\n\n\tfooSvc, err := project.GetService(\"foo\")\n\tassert.NilError(t, err)\n\n\tfoo, err := Parse(project, fooSvc)\n\tassert.NilError(t, err)\n\n\tt.Logf(\"foo: %+v\", foo)\n\tfor _, c := range foo.Containers {\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"-v=%s:/run/secrets/secret1:ro\", filepath.Join(project.WorkingDir, \"secret1\"))))\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"-v=%s:/run/secrets/secret2-foo:ro\", filepath.Join(project.WorkingDir, \"secret2\"))))\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"-v=%s:/mnt/secret3-foo:ro\", filepath.Join(project.WorkingDir, \"secret3\"))))\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"-v=%s:/config1:ro\", filepath.Join(project.WorkingDir, \"config1\"))))\n\t\tassert.Assert(t, in(c.RunArgs, fmt.Sprintf(\"-v=%s:/mnt/config2-foo:ro\", filepath.Join(project.WorkingDir, \"config2\"))))\n\t}\n}\n\nfunc TestParseRestartPolicy(t *testing.T) {\n\tt.Parallel()\n\tconst dockerComposeYAML = `\nservices:\n  onfailure_no_count:\n    image: alpine:3.14\n    restart: on-failure\n  onfailure_with_count:\n    image: alpine:3.14\n    restart: on-failure:10\n  onfailure_ignore:\n    image: alpine:3.14\n    restart: on-failure:3.14\n  unless_stopped:\n    image: alpine:3.14\n    restart: unless-stopped\n`\n\tcomp := testutil.NewComposeDir(t, dockerComposeYAML)\n\tdefer comp.CleanUp()\n\n\tproject, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)\n\tassert.NilError(t, err)\n\n\tgetContainersFromService := func(svcName string) []Container {\n\t\tsvcConfig, err := project.GetService(svcName)\n\t\tassert.NilError(t, err)\n\t\tsvc, err := Parse(project, svcConfig)\n\t\tassert.NilError(t, err)\n\n\t\treturn svc.Containers\n\t}\n\n\tvar c Container\n\tc = getContainersFromService(\"onfailure_no_count\")[0]\n\tassert.Assert(t, in(c.RunArgs, \"--restart=on-failure\"))\n\n\tc = getContainersFromService(\"onfailure_with_count\")[0]\n\tassert.Assert(t, in(c.RunArgs, \"--restart=on-failure:10\"))\n\n\tc = getContainersFromService(\"onfailure_ignore\")[0]\n\tassert.Assert(t, !in(c.RunArgs, \"--restart=on-failure:3.14\"))\n\n\tc = getContainersFromService(\"unless_stopped\")[0]\n\tassert.Assert(t, in(c.RunArgs, \"--restart=unless-stopped\"))\n}\n"
  },
  {
    "path": "pkg/composer/stop.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// StopOptions stores all option input from `nerdctl compose stop`\ntype StopOptions struct {\n\tTimeout *uint\n}\n\n// Stop stops containers in `services` without removing them. It calls\n// `nerdctl stop CONTAINER_ID` to do the actual job.\nfunc (c *Composer) Stop(ctx context.Context, opt StopOptions, services []string) error {\n\tserviceNames, err := c.ServiceNames(services...)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// reverse dependency order\n\tfor _, svc := range strutil.ReverseStrSlice(serviceNames) {\n\t\tcontainers, err := c.Containers(ctx, svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := c.stopContainers(ctx, containers, opt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *Composer) stopContainers(ctx context.Context, containers []containerd.Container, opt StopOptions) error {\n\tvar timeoutArg string\n\tif opt.Timeout != nil {\n\t\t// `nerdctl stop` uses `--time` instead of `--timeout`\n\t\ttimeoutArg = fmt.Sprintf(\"--time=%d\", *opt.Timeout)\n\t}\n\n\tvar rmWG sync.WaitGroup\n\tfor _, container := range containers {\n\t\tcontainer := container\n\t\trmWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer rmWG.Done()\n\t\t\tinfo, _ := container.Info(ctx, containerd.WithoutRefreshedMetadata)\n\t\t\tlog.G(ctx).Infof(\"Stopping container %s\", info.Labels[labels.Name])\n\t\t\targs := []string{\"stop\"}\n\t\t\tif opt.Timeout != nil {\n\t\t\t\targs = append(args, timeoutArg)\n\t\t\t}\n\t\t\targs = append(args, container.ID())\n\t\t\tif err := c.runNerdctlCmd(ctx, args...); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t}\n\t\t}()\n\t}\n\trmWG.Wait()\n\n\treturn nil\n}\n\nfunc (c *Composer) stopContainersFromParsedServices(ctx context.Context, containers map[string]serviceparser.Container) {\n\tvar rmWG sync.WaitGroup\n\tfor id, container := range containers {\n\t\tid := id\n\t\tcontainer := container\n\t\trmWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer rmWG.Done()\n\t\t\tlog.G(ctx).Infof(\"Stopping container %s\", container.Name)\n\t\t\tif err := c.runNerdctlCmd(ctx, \"stop\", id); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t}\n\t\t}()\n\t}\n\trmWG.Wait()\n}\n"
  },
  {
    "path": "pkg/composer/up.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/reflectutil\"\n)\n\ntype UpOptions struct {\n\tAbortOnContainerExit bool\n\tDetach               bool\n\tNoBuild              bool\n\tNoColor              bool\n\tNoLogPrefix          bool\n\tForceBuild           bool\n\tIPFS                 bool\n\tQuietPull            bool\n\tRemoveOrphans        bool\n\tForceRecreate        bool\n\tNoRecreate           bool\n\tScale                map[string]int // map of service name to replicas\n\tPull                 string\n}\n\nfunc (opts UpOptions) recreateStrategy() string {\n\tswitch {\n\tcase opts.ForceRecreate:\n\t\treturn RecreateForce\n\tcase opts.NoRecreate:\n\t\treturn RecreateNever\n\tdefault:\n\t\treturn RecreateDiverged\n\t}\n}\n\nfunc (c *Composer) Up(ctx context.Context, uo UpOptions, services []string) error {\n\tfor shortName := range c.project.Networks {\n\t\tif err := c.upNetwork(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName := range c.project.Volumes {\n\t\tif err := c.upVolume(ctx, shortName); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName, secret := range c.project.Secrets {\n\t\tobj := types.FileObjectConfig(secret)\n\t\tif err := validateFileObjectConfig(obj, shortName, \"service\", c.project); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor shortName, config := range c.project.Configs {\n\t\tobj := types.FileObjectConfig(config)\n\t\tif err := validateFileObjectConfig(obj, shortName, \"config\", c.project); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar parsedServices []*serviceparser.Service\n\t// use WithServices to sort the services in dependency order\n\tforEachFn := func(name string, svc *types.ServiceConfig) error {\n\t\tif replicas, ok := uo.Scale[svc.Name]; ok {\n\t\t\tif svc.Deploy == nil {\n\t\t\t\tsvc.Deploy = &types.DeployConfig{}\n\t\t\t}\n\t\t\tsvc.Deploy.Replicas = &replicas\n\t\t}\n\t\tps, err := serviceparser.Parse(c.project, *svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tparsedServices = append(parsedServices, ps)\n\t\treturn nil\n\t}\n\terr := c.project.ForEachService(services, forEachFn)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// remove orphan containers before the service has be started\n\t// FYI: https://github.com/docker/compose/blob/v2.3.4/pkg/compose/create.go#L91-L112\n\torphans, err := c.getOrphanContainers(ctx, parsedServices)\n\tif err != nil && uo.RemoveOrphans {\n\t\treturn fmt.Errorf(\"error getting orphaned containers: %w\", err)\n\t}\n\tif len(orphans) > 0 {\n\t\tif uo.RemoveOrphans {\n\t\t\tif err := c.removeContainers(ctx, orphans, RemoveOptions{Stop: true, Volumes: true}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error removing orphaned containers: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.G(ctx).Warnf(\"found %d orphaned containers: %v, you can run this command with the --remove-orphans flag to clean it up\", len(orphans), containerShortIDs(orphans))\n\t\t}\n\t}\n\n\treturn c.upServices(ctx, parsedServices, uo)\n}\n\nfunc validateFileObjectConfig(obj types.FileObjectConfig, shortName, objType string, project *types.Project) error {\n\tif unknown := reflectutil.UnknownNonEmptyFields(&obj, \"Name\", \"External\", \"File\"); len(unknown) > 0 {\n\t\tlog.L.Warnf(\"Ignoring: %s %s: %+v\", objType, shortName, unknown)\n\t}\n\n\tif obj.File == \"\" {\n\t\treturn fmt.Errorf(\"%s %q: lacks file path\", objType, shortName)\n\t}\n\tfullPath := project.RelativePath(obj.File)\n\tif _, err := os.Stat(fullPath); err != nil {\n\t\treturn fmt.Errorf(\"%s %q: failed to open file %q: %w\", objType, shortName, fullPath, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/up_network.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/reflectutil\"\n)\n\nfunc (c *Composer) upNetwork(ctx context.Context, shortName string) error {\n\tnet, ok := c.project.Networks[shortName]\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid network name %q\", shortName)\n\t}\n\tif net.External {\n\t\t// NOP\n\t\treturn nil\n\t}\n\n\tif unknown := reflectutil.UnknownNonEmptyFields(&net, \"Name\", \"Ipam\", \"Driver\", \"DriverOpts\"); len(unknown) > 0 {\n\t\tlog.G(ctx).Warnf(\"Ignoring: network %s: %+v\", shortName, unknown)\n\t}\n\n\t// shortName is like \"default\", fullName is like \"compose-wordpress_default\"\n\tfullName := net.Name\n\tnetExists, err := c.NetworkExists(fullName)\n\tif err != nil {\n\t\treturn err\n\t} else if !netExists {\n\t\tlog.G(ctx).Infof(\"Creating network %s\", fullName)\n\t\t//add metadata labels to network https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels-1\n\t\tcreateArgs := []string{\n\t\t\tfmt.Sprintf(\"--label=%s=%s\", labels.ComposeProject, c.project.Name),\n\t\t\tfmt.Sprintf(\"--label=%s=%s\", labels.ComposeNetwork, shortName),\n\t\t}\n\n\t\tif net.Driver != \"\" {\n\t\t\tcreateArgs = append(createArgs, fmt.Sprintf(\"--driver=%s\", net.Driver))\n\t\t}\n\n\t\tif net.DriverOpts != nil {\n\t\t\tfor k, v := range net.DriverOpts {\n\t\t\t\tcreateArgs = append(createArgs, fmt.Sprintf(\"--opt=%s=%s\", k, v))\n\t\t\t}\n\t\t}\n\n\t\tif net.Ipam.Config != nil {\n\t\t\tif len(net.Ipam.Config) > 1 {\n\t\t\t\tlog.G(ctx).Warnf(\"Ignoring: network %s: imam.config %+v\", shortName, net.Ipam.Config[1:])\n\t\t\t}\n\n\t\t\tipamConfig := net.Ipam.Config[0]\n\t\t\tif unknown := reflectutil.UnknownNonEmptyFields(ipamConfig, \"Subnet\", \"Gateway\", \"IPRange\"); len(unknown) > 0 {\n\t\t\t\tlog.G(ctx).Warnf(\"Ignoring: network %s: ipam.config[0]: %+v\", shortName, unknown)\n\t\t\t}\n\t\t\tif ipamConfig.Subnet != \"\" {\n\t\t\t\tcreateArgs = append(createArgs, fmt.Sprintf(\"--subnet=%s\", ipamConfig.Subnet))\n\t\t\t}\n\t\t\tif ipamConfig.Gateway != \"\" {\n\t\t\t\tcreateArgs = append(createArgs, fmt.Sprintf(\"--gateway=%s\", ipamConfig.Gateway))\n\t\t\t}\n\t\t\tif ipamConfig.IPRange != \"\" {\n\t\t\t\tcreateArgs = append(createArgs, fmt.Sprintf(\"--ip-range=%s\", ipamConfig.IPRange))\n\t\t\t}\n\t\t}\n\n\t\tcreateArgs = append(createArgs, fullName)\n\n\t\tif c.DebugPrintFull {\n\t\t\tlog.G(ctx).Debugf(\"Creating network args: %s\", createArgs)\n\t\t}\n\n\t\tif err := c.runNerdctlCmd(ctx, append([]string{\"network\", \"create\"}, createArgs...)...); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/up_service.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/composer/serviceparser\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc (c *Composer) upServices(ctx context.Context, parsedServices []*serviceparser.Service, uo UpOptions) error {\n\tif len(parsedServices) == 0 {\n\t\treturn errors.New(\"no service was provided\")\n\t}\n\n\t// TODO: parallelize loop for ensuring images (make sure not to mess up tty)\n\tfor _, ps := range parsedServices {\n\t\tif err := c.ensureServiceImage(ctx, ps, !uo.NoBuild, uo.ForceBuild, BuildOptions{}, uo.QuietPull, uo.Pull); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\trecreate := uo.recreateStrategy()\n\n\tvar (\n\t\tcontainers   = make(map[string]serviceparser.Container) // key: container ID\n\t\tservices     = []string{}\n\t\tcontainersMu sync.Mutex\n\t)\n\tfor _, ps := range parsedServices {\n\t\tps := ps\n\t\tvar runEG errgroup.Group\n\t\tservices = append(services, ps.Unparsed.Name)\n\t\tfor _, container := range ps.Containers {\n\t\t\tcontainer := container\n\t\t\trunEG.Go(func() error {\n\t\t\t\tid, err := c.upServiceContainer(ctx, ps, container, recreate)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcontainersMu.Lock()\n\t\t\t\tcontainers[id] = container\n\t\t\t\tcontainersMu.Unlock()\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\t\tif err := runEG.Wait(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif uo.Detach {\n\t\treturn nil\n\t}\n\n\t// this is used to stop containers in case --abort-on-container-exit flag is set.\n\t// c.Logs returns an error, so we don't need Ctrl-c to reach the \"Stopping containers (forcibly)\"\n\tif uo.AbortOnContainerExit {\n\t\tdefer c.stopContainersFromParsedServices(ctx, containers)\n\t}\n\tlog.G(ctx).Info(\"Attaching to logs\")\n\tlo := LogsOptions{\n\t\tAbortOnContainerExit: uo.AbortOnContainerExit,\n\t\tFollow:               true,\n\t\tNoColor:              uo.NoColor,\n\t\tNoLogPrefix:          uo.NoLogPrefix,\n\t\tLatestRun:            recreate == RecreateNever,\n\t}\n\tif err := c.Logs(ctx, lo, services); err != nil {\n\t\treturn err\n\t}\n\n\tlog.G(ctx).Infof(\"Stopping containers (forcibly)\") // TODO: support gracefully stopping\n\tc.stopContainersFromParsedServices(ctx, containers)\n\treturn nil\n}\n\nfunc (c *Composer) ensureServiceImage(ctx context.Context, ps *serviceparser.Service, allowBuild, forceBuild bool, bo BuildOptions, quiet bool, pullModeArg string) error {\n\tif ps.Build != nil && allowBuild {\n\t\tif ps.Build.Force || forceBuild {\n\t\t\treturn c.buildServiceImage(ctx, ps.Image, ps.Build, ps.Unparsed.Platform, bo)\n\t\t}\n\t\tif ok, err := c.ImageExists(ctx, ps.Image); err != nil {\n\t\t\treturn err\n\t\t} else if !ok {\n\t\t\treturn c.buildServiceImage(ctx, ps.Image, ps.Build, ps.Unparsed.Platform, bo)\n\t\t}\n\t\t// even when c.ImageExists returns true, we need to call c.EnsureImage\n\t\t// because ps.PullMode can be \"always\". So no return here.\n\t\tlog.G(ctx).Debugf(\"Image %s already exists, not building\", ps.Image)\n\t}\n\n\tlog.G(ctx).Infof(\"Ensuring image %s\", ps.Image)\n\tif pullModeArg != \"\" {\n\t\treturn c.EnsureImage(ctx, ps.Image, pullModeArg, ps.Unparsed.Platform, ps, quiet)\n\t}\n\treturn c.EnsureImage(ctx, ps.Image, ps.PullMode, ps.Unparsed.Platform, ps, quiet)\n}\n\n// upServiceContainer must be called after ensureServiceImage\n// upServiceContainer returns container ID\nfunc (c *Composer) upServiceContainer(ctx context.Context, service *serviceparser.Service, container serviceparser.Container, recreate string) (string, error) {\n\t// check if container already exists\n\texistingCid, err := c.containerID(ctx, container.Name, service.Unparsed.Name)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while checking for containers with name %q: %w\", container.Name, err)\n\t}\n\n\t// FIXME\n\tif service.Unparsed.StdinOpen != service.Unparsed.Tty {\n\t\treturn \"\", fmt.Errorf(\"currently StdinOpen(-i) and Tty(-t) should be same\")\n\t}\n\n\tvar runFlagD bool\n\tif !service.Unparsed.StdinOpen && !service.Unparsed.Tty {\n\t\tcontainer.RunArgs = append([]string{\"-d\"}, container.RunArgs...)\n\t\trunFlagD = true\n\t}\n\n\t// start the existing container and exit early\n\tif existingCid != \"\" && recreate == RecreateNever {\n\t\tcmd := c.createNerdctlCmd(ctx, append([]string{\"start\"}, existingCid)...)\n\t\tif err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error while starting existing container %s: %w\", container.Name, err)\n\t\t}\n\t\treturn existingCid, nil\n\t}\n\n\t// delete container if it already exists\n\tif existingCid != \"\" {\n\t\t// Default behavior for RecreateDiverged: compare stored hash with current service hash\n\t\tif recreate == RecreateDiverged {\n\t\t\tcurrentHash, err := ServiceHash(*service.Unparsed)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed computing service hash for %s: %w\", container.Name, err)\n\t\t\t}\n\t\t\tcon, err := c.client.LoadContainer(ctx, existingCid)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to load container %s: %w\", existingCid, err)\n\t\t\t}\n\t\t\tlbls, err := con.Labels(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to read labels for %s: %w\", existingCid, err)\n\t\t\t}\n\t\t\tif lbls[labels.ComposeConfigHash] == currentHash {\n\t\t\t\tcmd := c.createNerdctlCmd(ctx, append([]string{\"start\"}, existingCid)...)\n\t\t\t\tif err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"error while starting existing container %s: %w\", container.Name, err)\n\t\t\t\t}\n\t\t\t\treturn existingCid, nil\n\t\t\t}\n\t\t}\n\t\tlog.G(ctx).Debugf(\"Container %q already exists, deleting\", container.Name)\n\t\tdelCmd := c.createNerdctlCmd(ctx, \"rm\", \"-f\", container.Name)\n\t\tif err = delCmd.Run(); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"could not delete container %q: %w\", container.Name, err)\n\t\t}\n\t\tlog.G(ctx).Infof(\"Re-creating container %s\", container.Name)\n\t} else {\n\t\tlog.G(ctx).Infof(\"Creating container %s\", container.Name)\n\t}\n\n\tfor _, f := range container.Mkdir {\n\t\tlog.G(ctx).Debugf(\"Creating a directory %q\", f)\n\t\tif err = os.MkdirAll(f, 0o755); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to create a directory %q: %w\", f, err)\n\t\t}\n\t}\n\n\ttempDir, err := os.MkdirTemp(os.TempDir(), \"compose-\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while creating/re-creating container %s: %w\", container.Name, err)\n\t}\n\tdefer os.RemoveAll(tempDir)\n\tcidFilename := filepath.Join(tempDir, \"cid\")\n\n\tif c.EnvFile != \"\" {\n\t\tcontainer.RunArgs = append([]string{\"--env-file=\" + c.EnvFile}, container.RunArgs...)\n\t}\n\n\t//add metadata labels to container https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels\n\tcurrentHash, err := ServiceHash(*service.Unparsed)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed computing service hash for %s: %w\", container.Name, err)\n\t}\n\tcontainer.RunArgs = append([]string{\n\t\t\"--cidfile=\" + cidFilename,\n\t\tfmt.Sprintf(\"-l=%s=%s\", labels.ComposeProject, c.project.Name),\n\t\tfmt.Sprintf(\"-l=%s=%s\", labels.ComposeService, service.Unparsed.Name),\n\t\tfmt.Sprintf(\"-l=%s=%s\", labels.ComposeConfigHash, currentHash),\n\t}, container.RunArgs...)\n\n\tcmd := c.createNerdctlCmd(ctx, append([]string{\"run\"}, container.RunArgs...)...)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\n\tif err := c.executeUpCmd(ctx, cmd, container.Name, runFlagD, service.Unparsed.StdinOpen); err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while creating container %s: %w\", container.Name, err)\n\t}\n\n\tcid, err := filesystem.ReadFile(cidFilename)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error while creating container %s: %w\", container.Name, err)\n\t}\n\treturn strings.TrimSpace(string(cid)), nil\n}\n\nfunc (c *Composer) executeUpCmd(ctx context.Context, cmd *exec.Cmd, containerName string, runFlagD, stdinOpen bool) error {\n\tlog.G(ctx).Infof(\"Running %v\", cmd.Args)\n\tif c.DebugPrintFull {\n\t\tlog.G(ctx).Debugf(\"Running %v\", cmd.Args)\n\t}\n\n\tif stdinOpen {\n\t\tcmd.Stdin = os.Stdin\n\t}\n\tif !runFlagD {\n\t\tcmd.Stdout = os.Stdout\n\t}\n\t// Always propagate stderr to print detailed error messages (https://github.com/containerd/nerdctl/issues/1942)\n\tcmd.Stderr = os.Stderr\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"error while creating container %s: %w\", containerName, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/composer/up_volume.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage composer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/reflectutil\"\n)\n\nfunc (c *Composer) upVolume(ctx context.Context, shortName string) error {\n\tvol, ok := c.project.Volumes[shortName]\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid volume name %q\", shortName)\n\t}\n\tif vol.External {\n\t\t// NOP\n\t\treturn nil\n\t}\n\n\tif unknown := reflectutil.UnknownNonEmptyFields(&vol, \"Name\"); len(unknown) > 0 {\n\t\tlog.G(ctx).Warnf(\"Ignoring: volume %s: %+v\", shortName, unknown)\n\t}\n\n\t// shortName is like \"db_data\", fullName is like \"compose-wordpress_db_data\"\n\tfullName := vol.Name\n\t// FIXME: this is racy. By the time we get below to creating the volume, there is no guarantee that things are still fine.\n\t// Furthermore, by the time we are done creating all the volumes, they may very well have been destroyed.\n\t// This cannot be fixed without getting rid of the whole \"shell-out\" approach entirely.\n\tvolExists, err := c.VolumeExists(fullName)\n\tif err != nil {\n\t\treturn err\n\t} else if !volExists {\n\t\tlog.G(ctx).Infof(\"Creating volume %s\", fullName)\n\t\t//add metadata labels to volume https://github.com/compose-spec/compose-spec/blob/master/spec.md#labels-2\n\t\tcreateArgs := []string{\n\t\t\tfmt.Sprintf(\"--label=%s=%s\", labels.ComposeProject, c.project.Name),\n\t\t\tfmt.Sprintf(\"--label=%s=%s\", labels.ComposeVolume, shortName),\n\t\t\tfullName,\n\t\t}\n\t\tif err := c.runNerdctlCmd(ctx, append([]string{\"volume\", \"create\"}, createArgs...)...); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/config/config.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n\n\tncdefaults \"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\n// Config corresponds to nerdctl.toml .\n// See docs/config.md .\ntype Config struct {\n\tDebug            bool     `toml:\"debug\"`\n\tDebugFull        bool     `toml:\"debug_full\"`\n\tAddress          string   `toml:\"address\"`\n\tNamespace        string   `toml:\"namespace\"`\n\tSnapshotter      string   `toml:\"snapshotter\"`\n\tCNIPath          string   `toml:\"cni_path\"`\n\tCNINetConfPath   string   `toml:\"cni_netconfpath\"`\n\tDataRoot         string   `toml:\"data_root\"`\n\tCgroupManager    string   `toml:\"cgroup_manager\"`\n\tInsecureRegistry bool     `toml:\"insecure_registry\"`\n\tHostsDir         []string `toml:\"hosts_dir\"`\n\tExperimental     bool     `toml:\"experimental\"`\n\tHostGatewayIP    string   `toml:\"host_gateway_ip\"`\n\tBridgeIP         string   `toml:\"bridge_ip, omitempty\"`\n\tKubeHideDupe     bool     `toml:\"kube_hide_dupe\"`\n\tCDISpecDirs      []string `toml:\"cdi_spec_dirs,omitempty\"` // CDISpecDirs is a list of directories in which CDI specifications can be found.\n\tUsernsRemap      string   `toml:\"userns_remap, omitempty\"`\n\tDNS              []string `toml:\"dns,omitempty\"`\n\tDNSOpts          []string `toml:\"dns_opts,omitempty\"`\n\tDNSSearch        []string `toml:\"dns_search,omitempty\"`\n\tDisableHCSystemd bool     `toml:\"disable_hc_systemd\"`\n}\n\n// New creates a default Config object statically,\n// without interpolating CLI flags, env vars, and toml.\nfunc New() *Config {\n\treturn &Config{\n\t\tDebug:            false,\n\t\tDebugFull:        false,\n\t\tAddress:          defaults.DefaultAddress,\n\t\tNamespace:        namespaces.Default,\n\t\tSnapshotter:      defaults.DefaultSnapshotter,\n\t\tCNIPath:          ncdefaults.CNIPath(),\n\t\tCNINetConfPath:   ncdefaults.CNINetConfPath(),\n\t\tDataRoot:         ncdefaults.DataRoot(),\n\t\tCgroupManager:    ncdefaults.CgroupManager(),\n\t\tInsecureRegistry: false,\n\t\tHostsDir:         ncdefaults.HostsDirs(),\n\t\tExperimental:     true,\n\t\tHostGatewayIP:    ncdefaults.HostGatewayIP(),\n\t\tKubeHideDupe:     false,\n\t\tCDISpecDirs:      ncdefaults.CDISpecDirs(),\n\t\tUsernsRemap:      \"\",\n\t\tDNS:              []string{},\n\t\tDNSOpts:          []string{},\n\t\tDNSSearch:        []string{},\n\t\tDisableHCSystemd: false,\n\t}\n}\n"
  },
  {
    "path": "pkg/consoleutil/consoleutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage consoleutil\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/containerd/console\"\n)\n\n// Current is from https://github.com/containerd/console/blob/v1.0.4/console.go#L68-L81\n// adapted so that it does not panic\nfunc Current() (c console.Console, err error) {\n\tfor _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {\n\t\tif c, err = console.ConsoleFromFile(s); err == nil {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\treturn nil, console.ErrNotAConsole\n}\n\n// resizer is from https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/tasks/tasks.go#L25-L27\ntype resizer interface {\n\tResize(ctx context.Context, w, h uint32) error\n}\n"
  },
  {
    "path": "pkg/consoleutil/consoleutil_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage consoleutil\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/containerd/console\"\n\t\"github.com/containerd/log\"\n)\n\n// HandleConsoleResize resizes the console.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/tasks/tasks_unix.go#L43-L68\nfunc HandleConsoleResize(ctx context.Context, task resizer, con console.Console) error {\n\t// do an initial resize of the console\n\tsize, err := con.Size()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {\n\t\tlog.G(ctx).WithError(err).Error(\"resize pty\")\n\t}\n\ts := make(chan os.Signal, 16)\n\tsignal.Notify(s, unix.SIGWINCH)\n\tgo func() {\n\t\tfor range s {\n\t\t\tsize, err := con.Size()\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Error(\"get pty size\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Error(\"resize pty\")\n\t\t\t}\n\t\t}\n\t}()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/consoleutil/consoleutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage consoleutil\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/containerd/console\"\n\t\"github.com/containerd/log\"\n)\n\n// HandleConsoleResize resizes the console.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/tasks/tasks_windows.go#L34-L61\nfunc HandleConsoleResize(ctx context.Context, task resizer, con console.Console) error {\n\t// do an initial resize of the console\n\tsize, err := con.Size()\n\tif err != nil {\n\t\treturn err\n\t}\n\tgo func() {\n\t\tprevSize := size\n\t\tfor {\n\t\t\ttime.Sleep(time.Millisecond * 250)\n\n\t\t\tsize, err := con.Size()\n\t\t\tif err != nil {\n\t\t\t\tlog.G(ctx).WithError(err).Error(\"get pty size\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif size.Width != prevSize.Width || size.Height != prevSize.Height {\n\t\t\t\tif err := task.Resize(ctx, uint32(size.Width), uint32(size.Height)); err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).Error(\"resize pty\")\n\t\t\t\t}\n\t\t\t\tprevSize = size\n\t\t\t}\n\t\t}\n\t}()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/consoleutil/detach.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage consoleutil\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/moby/term\"\n\n\t\"github.com/containerd/log\"\n)\n\nconst DefaultDetachKeys = \"ctrl-p,ctrl-q\"\n\ntype detachableStdin struct {\n\tstdin  io.Reader\n\tcloser func()\n}\n\n// NewDetachableStdin returns an io.Reader that\n// uses a TTY proxy reader to read from stdin and detect when the specified detach keys are read,\n// in which case closer will be called.\nfunc NewDetachableStdin(stdin io.Reader, keys string, closer func()) (io.Reader, error) {\n\tif len(keys) == 0 {\n\t\tkeys = DefaultDetachKeys\n\t}\n\tb, err := term.ToBytes(keys)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert the detach keys to bytes: %w\", err)\n\t}\n\treturn &detachableStdin{\n\t\tstdin:  term.NewEscapeProxy(stdin, b),\n\t\tcloser: closer,\n\t}, nil\n}\n\nfunc (ds *detachableStdin) Read(p []byte) (int, error) {\n\tn, err := ds.stdin.Read(p)\n\tvar eerr term.EscapeError\n\tif errors.As(err, &eerr) {\n\t\tlog.L.Info(\"read detach keys\")\n\t\tif ds.closer != nil {\n\t\t\tds.closer()\n\t\t}\n\t\treturn n, io.EOF\n\t}\n\treturn n, err\n}\n"
  },
  {
    "path": "pkg/containerdutil/content.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package containerdutil provides \"caching\" versions of containerd native snapshotter and content store.\n// NOTE: caching should only be used for single, atomic operations, like `nerdctl images`, and NOT kept\n// across successive, distincts operations. As such, caching is not persistent across invocations of nerdctl,\n// and only lasts as long as the lifetime of the Snapshotter or ContentStore.\npackage containerdutil\n\nimport (\n\t\"context\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n)\n\n// ContentStore should be called to get a Provider with caching\nfunc NewProvider(client *containerd.Client) content.Provider {\n\treturn &providerWithCache{\n\t\tclient.ContentStore(),\n\t\tmake(map[string]*readerAtWithCache),\n\t}\n}\n\ntype providerWithCache struct {\n\tnative content.Provider\n\tcache  map[string]*readerAtWithCache\n}\n\nfunc (provider *providerWithCache) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {\n\tkey := desc.Digest.String()\n\t// If we had en entry already, get the size over\n\tvalue, ok := provider.cache[key]\n\tif !ok {\n\t\tnewReaderAt, err := provider.native.ReaderAt(ctx, desc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// Build the final object\n\t\tvalue = &readerAtWithCache{\n\t\t\tnewReaderAt,\n\t\t\t-1,\n\t\t\tfunc() {\n\t\t\t\tdelete(provider.cache, key)\n\t\t\t},\n\t\t}\n\t\t// Cache it\n\t\tprovider.cache[key] = value\n\t}\n\n\treturn value, nil\n}\n\n// ReaderAtWithCache implements the content.ReaderAt interface\ntype readerAtWithCache struct {\n\tcontent.ReaderAt\n\tsize  int64\n\tprune func()\n}\n\nfunc (rac *readerAtWithCache) Size() int64 {\n\t// local implementation in containerd technically provides a similar mechanism, so, this method not really useful\n\t// by default - but obviously, this is implementation dependent\n\tif rac.size == -1 {\n\t\trac.size = rac.ReaderAt.Size()\n\t}\n\treturn rac.size\n}\n\nfunc (rac *readerAtWithCache) Close() error {\n\terr := rac.ReaderAt.Close()\n\t// Remove ourselves from the cache\n\trac.prune()\n\treturn err\n}\n"
  },
  {
    "path": "pkg/containerdutil/helpers.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerdutil\n\nimport (\n\t\"context\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/content\"\n)\n\nvar ReadBlob = readBlobWithCache()\n\ntype readBlob func(ctx context.Context, provider content.Provider, desc ocispec.Descriptor) ([]byte, error)\n\nfunc readBlobWithCache() readBlob {\n\tvar cache = make(map[string]([]byte))\n\n\treturn func(ctx context.Context, provider content.Provider, desc ocispec.Descriptor) ([]byte, error) {\n\t\tvar err error\n\t\tv, ok := cache[desc.Digest.String()]\n\t\tif !ok {\n\t\t\tv, err = content.ReadBlob(ctx, provider, desc)\n\t\t\tif err == nil {\n\t\t\t\tcache[desc.Digest.String()] = v\n\t\t\t}\n\t\t}\n\n\t\treturn v, err\n\t}\n}\n"
  },
  {
    "path": "pkg/containerdutil/image_store.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerdutil\n\ntype ImageStore struct {\n}\n"
  },
  {
    "path": "pkg/containerdutil/snapshotter.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerdutil\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n)\n\n// SnapshotService should be called to get a new caching snapshotter\nfunc SnapshotService(client *containerd.Client, snapshotterName string) snapshots.Snapshotter {\n\treturn &snapshotterWithCache{\n\t\tclient.SnapshotService(snapshotterName),\n\t\tmap[string]snapshots.Info{},\n\t\tmap[string]snapshots.Usage{},\n\t}\n}\n\ntype snapshotterWithCache struct {\n\tsnapshots.Snapshotter\n\tstatCache  map[string]snapshots.Info\n\tusageCache map[string]snapshots.Usage\n}\n\nfunc (snap *snapshotterWithCache) Stat(ctx context.Context, key string) (snapshots.Info, error) {\n\tif stat, ok := snap.statCache[key]; ok {\n\t\treturn stat, nil\n\t}\n\tstat, err := snap.Snapshotter.Stat(ctx, key)\n\tif err == nil {\n\t\tsnap.statCache[key] = stat\n\t}\n\treturn stat, err\n}\n\nfunc (snap *snapshotterWithCache) Usage(ctx context.Context, key string) (snapshots.Usage, error) {\n\tif usage, ok := snap.usageCache[key]; ok {\n\t\treturn usage, nil\n\t}\n\tusage, err := snap.Snapshotter.Usage(ctx, key)\n\tif err == nil {\n\t\tsnap.usageCache[key] = usage\n\t}\n\treturn usage, err\n}\n"
  },
  {
    "path": "pkg/containerdutil/version.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerdutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n)\n\nfunc ServerSemVer(ctx context.Context, client *containerd.Client) (*semver.Version, error) {\n\tv, err := client.Version(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsv, err := semver.NewVersion(v.Version)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse the containerd version %q: %w\", v.Version, err)\n\t}\n\treturn sv, nil\n}\n\n// SupportsFullTransferService checks if the containerd version fully supports the Transfer service.\n// While containerd 1.7 has Transfer service, full support is only available in 2.0+.\n// The following features are missing in containerd 1.7:\n//   - Non-distributable artifacts support\n//   - Registry configuration options: WithHostDir(), WithDefaultScheme() etc.\nfunc SupportsFullTransferService(ctx context.Context, client *containerd.Client) bool {\n\tsv, err := ServerSemVer(ctx, client)\n\tif err != nil {\n\t\t// If we can't determine version, assume it's an older version for safety\n\t\treturn false\n\t}\n\tv20, _ := semver.NewVersion(\"2.0.0\")\n\treturn !sv.LessThan(v20)\n}\n"
  },
  {
    "path": "pkg/containerinspector/containerinspector.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerinspector\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/typeurl/v2\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\nfunc Inspect(ctx context.Context, container containerd.Container) (*native.Container, error) {\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tn := &native.Container{\n\t\tContainer: info,\n\t}\n\tid := container.ID()\n\n\tn.Spec, err = typeurl.UnmarshalAny(info.Spec)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", id).Warnf(\"failed to inspect Spec\")\n\t\treturn n, nil\n\t}\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\tif !errdefs.IsNotFound(err) {\n\t\t\tlog.G(ctx).WithError(err).WithField(\"id\", id).Warnf(\"failed to inspect Task\")\n\t\t}\n\t\treturn n, nil\n\t}\n\tn.Process = &native.Process{\n\t\tPid: int(task.Pid()),\n\t}\n\tst, err := task.Status(ctx)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", id).Warnf(\"failed to inspect Status\")\n\t\treturn n, nil\n\t}\n\tn.Process.Status = st\n\tnetNS, err := InspectNetNS(ctx, n.Process.Pid)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", id).Warnf(\"failed to inspect NetNS\")\n\t\treturn n, nil\n\t}\n\tn.Process.NetNS = netNS\n\treturn n, nil\n}\n"
  },
  {
    "path": "pkg/containerinspector/containerinspector_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerinspector\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/containernetworking/plugins/pkg/ns\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\nfunc InspectNetNS(ctx context.Context, pid int) (*native.NetNS, error) {\n\tnsPath := fmt.Sprintf(\"/proc/%d/ns/net\", pid)\n\tres := &native.NetNS{}\n\tfn := func(_ ns.NetNS) error {\n\t\tintf, err := net.Interfaces()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tres.Interfaces = make([]native.NetInterface, len(intf))\n\t\tfor i, f := range intf {\n\t\t\tx := native.NetInterface{\n\t\t\t\tInterface: f,\n\t\t\t}\n\t\t\tif f.HardwareAddr != nil {\n\t\t\t\tx.HardwareAddr = f.HardwareAddr.String()\n\t\t\t}\n\t\t\tif x.Interface.Flags.String() != \"0\" {\n\t\t\t\tx.Flags = strings.Split(x.Interface.Flags.String(), \"|\")\n\t\t\t}\n\t\t\tif addrs, err := x.Interface.Addrs(); err == nil {\n\t\t\t\tx.Addrs = make([]string, len(addrs))\n\t\t\t\tfor j, a := range addrs {\n\t\t\t\t\tx.Addrs[j] = a.String()\n\t\t\t\t}\n\t\t\t}\n\t\t\tres.Interfaces[i] = x\n\t\t}\n\t\tres.PrimaryInterface = determinePrimaryInterface(res.Interfaces)\n\t\treturn nil\n\t}\n\tif err := ns.WithNetNSPath(nsPath, fn); err != nil {\n\t\treturn nil, err\n\t}\n\treturn res, nil\n}\n\n// determinePrimaryInterface returns a net.Interface.Index value, not a slice index.\n// Zero means no primary interface was detected.\nfunc determinePrimaryInterface(interfaces []native.NetInterface) int {\n\tfor _, f := range interfaces {\n\t\tif f.Interface.Flags&net.FlagLoopback == 0 && f.Interface.Flags&net.FlagUp != 0 && !strings.HasPrefix(f.Name, \"lo\") {\n\t\t\treturn f.Index\n\t\t}\n\t}\n\treturn 0\n}\n"
  },
  {
    "path": "pkg/containerinspector/containerinspector_unix_nolinux.go",
    "content": "//go:build unix && !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerinspector\n\nimport (\n\t\"context\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\nfunc InspectNetNS(ctx context.Context, pid int) (*native.NetNS, error) {\n\tr := &native.NetNS{}\n\n\treturn r, nil\n}\n"
  },
  {
    "path": "pkg/containerinspector/containerinspector_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerinspector\n\nimport (\n\t\"context\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\nfunc InspectNetNS(ctx context.Context, pid int) (*native.NetNS, error) {\n\tr := &native.NetNS{}\n\n\treturn r, nil\n}\n"
  },
  {
    "path": "pkg/containerutil/config.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/ipcutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/nettype\"\n)\n\n// ReconfigNetContainer reconfigures the container's network namespace path.\nfunc ReconfigNetContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error {\n\tnetworksJSON, ok := lab[labels.Networks]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvar networks []string\n\tif err := json.Unmarshal([]byte(networksJSON), &networks); err != nil {\n\t\treturn err\n\t}\n\tnetType, err := nettype.Detect(networks)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif netType == nettype.Container {\n\t\tnetwork := strings.Split(networks[0], \":\")\n\t\tif len(network) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid network: %s, should be \\\"container:<id|name>\\\"\", networks[0])\n\t\t}\n\t\ttargetCon, err := client.LoadContainer(ctx, network[1])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnetNSPath, err := ContainerNetNSPath(ctx, targetCon)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tspec, err := c.Spec(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = c.Update(ctx, containerd.UpdateContainerOpts(\n\t\t\tcontainerd.WithSpec(spec, oci.WithLinuxNamespace(\n\t\t\t\tspecs.LinuxNamespace{\n\t\t\t\t\tType: specs.NetworkNamespace,\n\t\t\t\t\tPath: netNSPath,\n\t\t\t\t}))))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ReconfigPIDContainer reconfigures the container's spec options for sharing PID namespace.\nfunc ReconfigPIDContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error {\n\ttargetContainerID, ok := lab[labels.PIDContainer]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif runtime.GOOS != \"linux\" {\n\t\treturn errors.New(\"--pid only supported on linux\")\n\t}\n\ttargetCon, err := client.LoadContainer(ctx, targetContainerID)\n\tif err != nil {\n\t\treturn err\n\t}\n\topts, err := GenerateSharingPIDOpts(ctx, targetCon)\n\tif err != nil {\n\t\treturn err\n\t}\n\tspec, err := c.Spec(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = c.Update(ctx, containerd.UpdateContainerOpts(\n\t\tcontainerd.WithSpec(spec, oci.Compose(opts...)),\n\t))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ReconfigIPCContainer reconfigures the container's spec options for sharing IPC namespace and volumns.\nfunc ReconfigIPCContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error {\n\tipc, err := ipcutil.DecodeIPCLabel(lab[labels.IPC])\n\tif err != nil {\n\t\treturn err\n\t}\n\topts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client)\n\tif err != nil {\n\t\treturn err\n\t}\n\tspec, err := c.Spec(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = c.Update(ctx, containerd.UpdateContainerOpts(\n\t\tcontainerd.WithSpec(spec, oci.Compose(opts...)),\n\t))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/containerutil/container_network_manager.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/nettype\"\n\t\"github.com/containerd/nerdctl/v2/pkg/resolvconf\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nconst (\n\tUtsNamespaceHost = \"host\"\n)\n\nfunc withCustomResolvConf(src string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Mounts = append(s.Mounts, specs.Mount{\n\t\t\tDestination: \"/etc/resolv.conf\",\n\t\t\tType:        \"bind\",\n\t\t\tSource:      src,\n\t\t\tOptions:     []string{\"bind\", mountutil.DefaultPropagationMode}, // writable\n\t\t})\n\t\treturn nil\n\t}\n}\n\nfunc withCustomEtcHostname(src string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Mounts = append(s.Mounts, specs.Mount{\n\t\t\tDestination: \"/etc/hostname\",\n\t\t\tType:        \"bind\",\n\t\t\tSource:      src,\n\t\t\tOptions:     []string{\"bind\", mountutil.DefaultPropagationMode}, // writable\n\t\t})\n\t\treturn nil\n\t}\n}\n\nfunc withCustomHosts(src string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\ts.Mounts = append(s.Mounts, specs.Mount{\n\t\t\tDestination: \"/etc/hosts\",\n\t\t\tType:        \"bind\",\n\t\t\tSource:      src,\n\t\t\tOptions:     []string{\"bind\", mountutil.DefaultPropagationMode}, // writable\n\t\t})\n\t\treturn nil\n\t}\n}\n\nfunc fetchDNSResolverConfig(netOpts types.NetworkOptions, allowLocalhostDNS bool) ([]string, []string, []string, error) {\n\tdns := netOpts.DNSServers\n\tdnsSearch := netOpts.DNSSearchDomains\n\tdnsOptions := netOpts.DNSResolvConfOptions\n\n\tconf, err := resolvconf.Get()\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\t// if resolvConf file does't exist, using default resolvers\n\t\tconf = &resolvconf.File{}\n\t\tlog.L.WithError(err).Debugf(\"resolvConf file doesn't exist on host\")\n\t}\n\tconf, err = resolvconf.FilterResolvDNSWithLocalhostOption(conf.Content, true, allowLocalhostDNS)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\tif len(netOpts.DNSServers) == 0 {\n\t\tdns = resolvconf.GetNameservers(conf.Content, resolvconf.IP)\n\t}\n\tif len(netOpts.DNSSearchDomains) == 0 {\n\t\tdnsSearch = resolvconf.GetSearchDomains(conf.Content)\n\t}\n\tif len(netOpts.DNSResolvConfOptions) == 0 {\n\t\tdnsOptions = resolvconf.GetOptions(conf.Content)\n\t}\n\treturn dns, dnsSearch, dnsOptions, err\n}\n\n// NetworkOptionsManager types.NetworkOptionsManager is an interface for reading/setting networking\n// options for containers based on the provided command flags.\ntype NetworkOptionsManager interface {\n\t// NetworkOptions Returns a copy of the internal types.NetworkOptions.\n\tNetworkOptions() types.NetworkOptions\n\n\t// VerifyNetworkOptions Verifies that the internal network settings are correct.\n\tVerifyNetworkOptions(context.Context) error\n\n\t// SetupNetworking Performs setup actions required for the container with the given ID.\n\tSetupNetworking(context.Context, string) error\n\n\t// CleanupNetworking Performs any required cleanup actions for the given container.\n\t// Should only be called to revert any setup steps performed in SetupNetworking.\n\tCleanupNetworking(context.Context, containerd.Container) error\n\n\t// InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.\n\t//\n\t// These options can potentially differ from the actual networking options\n\t// that the NetworkOptionsManager was initially instantiated with.\n\t// E.g: in container networking mode, the label will be normalized to an ID:\n\t// `--net=container:myContainer` => `--net=container:<ID of myContainer>`.\n\tInternalNetworkingOptionLabels(context.Context) (types.NetworkOptions, error)\n\n\t// ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n\t// the network specs which need to be applied to the container with the given ID.\n\tContainerNetworkingOpts(context.Context, string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error)\n}\n\n// NewNetworkingOptionsManager Returns a types.NetworkOptionsManager based on the provided command's flags.\nfunc NewNetworkingOptionsManager(globalOptions types.GlobalCommandOptions, netOpts types.NetworkOptions, client *containerd.Client) (NetworkOptionsManager, error) {\n\tnetType, err := nettype.Detect(netOpts.NetworkSlice)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar manager NetworkOptionsManager\n\tswitch netType {\n\tcase nettype.None:\n\t\tmanager = &noneNetworkManager{globalOptions, netOpts, client}\n\tcase nettype.Host:\n\t\tmanager = &hostNetworkManager{globalOptions, netOpts, client}\n\tcase nettype.Container:\n\t\tmanager = &containerNetworkManager{globalOptions, netOpts, client}\n\tcase nettype.CNI:\n\t\tmanager = &cniNetworkManager{globalOptions, netOpts, client, cniNetworkManagerPlatform{}}\n\tcase nettype.Namespace:\n\t\t// We'll handle Namespace networking identically to Host-mode networking, but\n\t\t// put the container in the specified network namespace instead of the root.\n\t\tmanager = &hostNetworkManager{globalOptions, netOpts, client}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected container networking type: %q\", netType)\n\t}\n\n\treturn manager, nil\n}\n\n// No-op types.NetworkOptionsManager for network-less containers.\ntype noneNetworkManager struct {\n\tglobalOptions types.GlobalCommandOptions\n\tnetOpts       types.NetworkOptions\n\tclient        *containerd.Client\n}\n\n// NetworkOptions Returns a copy of the internal types.NetworkOptions.\nfunc (m *noneNetworkManager) NetworkOptions() types.NetworkOptions {\n\treturn m.netOpts\n}\n\n// VerifyNetworkOptions Verifies that the internal network settings are correct.\nfunc (m *noneNetworkManager) VerifyNetworkOptions(_ context.Context) error {\n\terr := validateUtsSettings(m.netOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// SetupNetworking Performs setup actions required for the container with the given ID.\nfunc (m *noneNetworkManager) SetupNetworking(ctx context.Context, containerID string) error {\n\t// Retrieve the container\n\tcontainer, err := m.client.ContainerService().Get(ctx, containerID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the dataStore\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the hostsStore\n\ths, err := hostsstore.New(dataStore, container.Labels[labels.Namespace])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get extra-hosts\n\textraHostsJSON := container.Labels[labels.ExtraHosts]\n\tvar extraHosts []string\n\tif err = json.Unmarshal([]byte(extraHostsJSON), &extraHosts); err != nil {\n\t\treturn err\n\t}\n\n\thosts := make(map[string]string)\n\tfor _, host := range extraHosts {\n\t\tif v := strings.SplitN(host, \":\", 2); len(v) == 2 {\n\t\t\thosts[v[0]] = v[1]\n\t\t}\n\t}\n\n\t// Prep the meta\n\thsMeta := hostsstore.Meta{\n\t\tID:         container.ID,\n\t\tHostname:   container.Labels[labels.Hostname],\n\t\tExtraHosts: hosts,\n\t\tName:       container.Labels[labels.Name],\n\t}\n\n\t// Save the meta information\n\treturn hs.Acquire(hsMeta)\n}\n\n// CleanupNetworking Performs any required cleanup actions for the given container.\n// Should only be called to revert any setup steps performed in SetupNetworking.\nfunc (m *noneNetworkManager) CleanupNetworking(ctx context.Context, container containerd.Container) error {\n\t// Get the dataStore\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get labels\n\tlbls, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the hostsStore\n\ths, err := hostsstore.New(dataStore, lbls[labels.Namespace])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Release\n\treturn hs.Release(container.ID())\n}\n\n// InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.\nfunc (m *noneNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {\n\topts := m.netOpts\n\t// Cannot have a MAC address in host networking mode.\n\topts.MACAddress = \"\"\n\treturn opts, nil\n}\n\n// ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n// the network specs which need to be applied to the container with the given ID.\nfunc (m *noneNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tstateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tresolvConfPath := filepath.Join(stateDir, \"resolv.conf\")\n\tdns, dnsSearch, dnsOptions, err := fetchDNSResolverConfig(m.netOpts, false)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\t_, err = resolvconf.Build(resolvConfPath, dns, dnsSearch, dnsOptions)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ths, err := hostsstore.New(dataStore, m.globalOptions.Namespace)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tetcHostsPath, err := hs.AllocHostsFile(containerID, []byte{})\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// `/etc/host` does not exist in FreeBSD minimal rootfs image\n\t// `/etc/resolv.conf` does not exist in FreeBSD minimal rootfs image\n\tspecs := []oci.SpecOpts{}\n\tif runtime.GOOS == \"linux\" {\n\t\tspecs = []oci.SpecOpts{\n\t\t\twithDedupMounts(\"/etc/resolv.conf\", withCustomResolvConf(resolvConfPath)),\n\t\t\twithDedupMounts(\"/etc/hosts\", withCustomHosts(etcHostsPath)),\n\t\t}\n\t}\n\n\t// `/etc/hostname` does not exist on FreeBSD\n\tif runtime.GOOS == \"linux\" {\n\t\t// If no hostname is set, default to first 12 characters of the container ID.\n\t\thostname := m.netOpts.Hostname\n\t\tif hostname == \"\" {\n\t\t\thostname = containerID\n\t\t\tif len(hostname) > 12 {\n\t\t\t\thostname = hostname[0:12]\n\t\t\t}\n\t\t}\n\t\tm.netOpts.Hostname = hostname\n\n\t\thostnameOpts, err := writeEtcHostnameForContainer(m.globalOptions, m.netOpts.Hostname, containerID)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif hostnameOpts != nil {\n\t\t\tspecs = append(specs, hostnameOpts...)\n\t\t}\n\t}\n\treturn specs, []containerd.NewContainerOpts{}, nil\n}\n\n// types.NetworkOptionsManager implementation for container networking settings.\ntype containerNetworkManager struct {\n\tglobalOptions types.GlobalCommandOptions\n\tnetOpts       types.NetworkOptions\n\tclient        *containerd.Client\n}\n\n// NetworkOptions Returns a copy of the internal types.NetworkOptions.\nfunc (m *containerNetworkManager) NetworkOptions() types.NetworkOptions {\n\treturn m.netOpts\n}\n\n// VerifyNetworkOptions Verifies that the internal network settings are correct.\nfunc (m *containerNetworkManager) VerifyNetworkOptions(_ context.Context) error {\n\t// TODO: check host OS, not client-side OS.\n\tif runtime.GOOS != \"linux\" {\n\t\treturn errors.New(\"container networking mode is currently only supported on Linux\")\n\t}\n\n\terr := validateUtsSettings(m.netOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Note that mac-address is accepted, though it is a no-op\n\tnonZeroParams := nonZeroMapValues(map[string]interface{}{\n\t\t\"--hostname\":   m.netOpts.Hostname,\n\t\t\"--domainname\": m.netOpts.Domainname,\n\t\t// NOTE: an empty slice still counts as a non-zero value so we check its length:\n\t\t\"-p/--publish\": len(m.netOpts.PortMappings) != 0,\n\t\t\"--dns\":        len(m.netOpts.DNSServers) != 0,\n\t\t\"--add-host\":   len(m.netOpts.AddHost) != 0,\n\t})\n\n\tif len(nonZeroParams) != 0 {\n\t\treturn fmt.Errorf(\"conflicting options: the following arguments are not supported when using `--network=container:<container>`: %s\", nonZeroParams)\n\t}\n\n\treturn nil\n}\n\n// Returns the relevant paths of the `hostname`, `resolv.conf`, and `hosts` files\n// in the datastore of the container with the given ID.\nfunc (m *containerNetworkManager) getContainerNetworkFilePaths(containerID string) (string, string, string, error) {\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\tconStateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\thostsStore, err := hostsstore.New(dataStore, m.globalOptions.Namespace)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\n\thostnamePath := filepath.Join(conStateDir, \"hostname\")\n\tresolvConfPath := filepath.Join(conStateDir, \"resolv.conf\")\n\n\tetcHostsPath, err := hostsStore.HostsPath(containerID)\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", err\n\t}\n\n\treturn hostnamePath, resolvConfPath, etcHostsPath, nil\n}\n\n// SetupNetworking Performs setup actions required for the container with the given ID.\nfunc (m *containerNetworkManager) SetupNetworking(_ context.Context, _ string) error {\n\t// NOTE: container networking simply reuses network config files from the\n\t// bridged container so there are no setup/teardown steps required.\n\treturn nil\n}\n\n// CleanupNetworking Performs any required cleanup actions for the given container.\n// Should only be called to revert any setup steps performed in SetupNetworking.\nfunc (m *containerNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error {\n\t// NOTE: container networking simply reuses network config files from the\n\t// bridged container so there are no setup/teardown steps required.\n\treturn nil\n}\n\n// Searches for and returns the networking container for the given network argument.\nfunc (m *containerNetworkManager) getNetworkingContainerForArgument(ctx context.Context, containerNetArg string, client *containerd.Client) (containerd.Container, error) {\n\tnetItems := strings.Split(containerNetArg, \":\")\n\tif len(netItems) < 2 {\n\t\treturn nil, fmt.Errorf(\"container networking argument format must be 'container:<id|name>', got: %q\", containerNetArg)\n\t}\n\tcontainerName := netItems[1]\n\n\tvar foundContainer containerd.Container\n\twalker := &containerwalker.ContainerWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\tif found.MatchCount > 1 {\n\t\t\t\treturn fmt.Errorf(\"container networking: multiple containers found with prefix: %s\", containerName)\n\t\t\t}\n\t\t\tfoundContainer = found.Container\n\t\t\treturn nil\n\t\t},\n\t}\n\tn, err := walker.Walk(ctx, containerName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif n == 0 {\n\t\treturn nil, fmt.Errorf(\"container networking: could not find container: %s\", containerName)\n\t}\n\n\treturn foundContainer, nil\n}\n\n// InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.\nfunc (m *containerNetworkManager) InternalNetworkingOptionLabels(ctx context.Context) (types.NetworkOptions, error) {\n\topts := m.netOpts\n\tif m.netOpts.NetworkSlice == nil || len(m.netOpts.NetworkSlice) != 1 {\n\t\treturn opts, fmt.Errorf(\"conflicting options: exactly one network specification is allowed when using '--network=container:<container>'\")\n\t}\n\t// MacAddress is not allowed with container networking\n\topts.MACAddress = \"\"\n\n\tcontainer, err := m.getNetworkingContainerForArgument(ctx, m.netOpts.NetworkSlice[0], m.client)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\tcontainerID := container.ID()\n\topts.NetworkSlice = []string{fmt.Sprintf(\"container:%s\", containerID)}\n\treturn opts, nil\n}\n\n// ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n// the network specs which need to be applied to the container with the given ID.\nfunc (m *containerNetworkManager) ContainerNetworkingOpts(ctx context.Context, _ string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\topts := []oci.SpecOpts{}\n\tcOpts := []containerd.NewContainerOpts{}\n\n\tcontainer, err := m.getNetworkingContainerForArgument(ctx, m.netOpts.NetworkSlice[0], m.client)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcontainerID := container.ID()\n\n\ts, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\thostname := s.Hostname\n\t// if Utsnamespace is set we should not set the hostname\n\tif m.netOpts.UTSNamespace == UtsNamespaceHost {\n\t\thostname = \"\"\n\t}\n\n\tnetNSPath, err := ContainerNetNSPath(ctx, container)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\thostnamePath, resolvConfPath, etcHostsPath, err := m.getContainerNetworkFilePaths(containerID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\topts = append(opts,\n\t\toci.WithLinuxNamespace(specs.LinuxNamespace{\n\t\t\tType: specs.NetworkNamespace,\n\t\t\tPath: netNSPath,\n\t\t}),\n\t\twithCustomResolvConf(resolvConfPath),\n\t\twithCustomHosts(etcHostsPath),\n\t\toci.WithHostname(hostname),\n\t\twithCustomEtcHostname(hostnamePath),\n\t)\n\n\treturn opts, cOpts, nil\n}\n\n// types.NetworkOptionsManager implementation for host networking settings.\ntype hostNetworkManager struct {\n\tglobalOptions types.GlobalCommandOptions\n\tnetOpts       types.NetworkOptions\n\tclient        *containerd.Client\n}\n\n// NetworkOptions Returns a copy of the internal types.NetworkOptions.\nfunc (m *hostNetworkManager) NetworkOptions() types.NetworkOptions {\n\treturn m.netOpts\n}\n\n// VerifyNetworkOptions Verifies that the internal network settings are correct.\nfunc (m *hostNetworkManager) VerifyNetworkOptions(_ context.Context) error {\n\t// TODO: check host OS, not client-side OS.\n\tif runtime.GOOS == \"windows\" {\n\t\treturn errors.New(\"cannot use host networking on Windows\")\n\t}\n\n\treturn validateUtsSettings(m.netOpts)\n}\n\n// SetupNetworking Performs setup actions required for the container with the given ID.\nfunc (m *hostNetworkManager) SetupNetworking(ctx context.Context, containerID string) error {\n\t// Retrieve the container\n\tcontainer, err := m.client.ContainerService().Get(ctx, containerID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the dataStore\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the hostsStore\n\ths, err := hostsstore.New(dataStore, container.Labels[labels.Namespace])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get extra-hosts\n\textraHostsJSON := container.Labels[labels.ExtraHosts]\n\tvar extraHosts []string\n\tif err = json.Unmarshal([]byte(extraHostsJSON), &extraHosts); err != nil {\n\t\treturn err\n\t}\n\n\thosts := make(map[string]string)\n\tfor _, host := range extraHosts {\n\t\tif v := strings.SplitN(host, \":\", 2); len(v) == 2 {\n\t\t\thosts[v[0]] = v[1]\n\t\t}\n\t}\n\n\t// Prep the meta\n\thsMeta := hostsstore.Meta{\n\t\tID:         container.ID,\n\t\tHostname:   container.Labels[labels.Hostname],\n\t\tExtraHosts: hosts,\n\t\tName:       container.Labels[labels.Name],\n\t}\n\n\t// Save the meta information\n\treturn hs.Acquire(hsMeta)\n}\n\n// CleanupNetworking Performs any required cleanup actions for the given container.\n// Should only be called to revert any setup steps performed in SetupNetworking.\nfunc (m *hostNetworkManager) CleanupNetworking(ctx context.Context, container containerd.Container) error {\n\t// Get the dataStore\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get labels\n\tlbls, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the hostsStore\n\ths, err := hostsstore.New(dataStore, lbls[labels.Namespace])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Release\n\treturn hs.Release(container.ID())\n}\n\n// InternalNetworkingOptionLabels Returns the set of NetworkingOptions which should be set as labels on the container.\nfunc (m *hostNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {\n\topts := m.netOpts\n\t// Cannot have a MAC address in host networking mode.\n\topts.MACAddress = \"\"\n\treturn opts, nil\n}\n\n// withDedupMounts Returns the specOpts if the mountPath is not in existing mounts.\n// for https://github.com/containerd/nerdctl/issues/2685\nfunc withDedupMounts(mountPath string, defaultSpec oci.SpecOpts) oci.SpecOpts {\n\treturn func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {\n\t\tfor _, m := range s.Mounts {\n\t\t\tif m.Destination == mountPath {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn defaultSpec(ctx, client, c, s)\n\t}\n}\n\n// getHostNetworkingNamespace Returns an oci.SpecOpts representing the network namespace to\n// be used by the hostNetworkManager. When running with `--network=host` this would be the host's\n// root namespace, but `--network=ns:<path>` can be used to run a container in an existing netns.\nfunc getHostNetworkingNamespace(netModeArg string) (oci.SpecOpts, error) {\n\tif !strings.Contains(netModeArg, \":\") {\n\t\t// Use the host root namespace by default\n\t\treturn oci.WithHostNamespace(specs.NetworkNamespace), nil\n\t}\n\n\tnetItems := strings.Split(netModeArg, \":\")\n\tif len(netItems) < 2 {\n\t\treturn nil, fmt.Errorf(\"namespace networking argument format must be 'ns:<path>', got: %q\", netModeArg)\n\t}\n\tnetnsPath := netItems[1]\n\treturn oci.WithLinuxNamespace(specs.LinuxNamespace{\n\t\tType: specs.NetworkNamespace,\n\t\tPath: netnsPath,\n\t}), nil\n}\n\n// ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n// the network specs which need to be applied to the container with the given ID.\nfunc (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\n\tcOpts := []containerd.NewContainerOpts{}\n\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tstateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tresolvConfPath := filepath.Join(stateDir, \"resolv.conf\")\n\tdns, dnsSearch, dnsOptions, err := fetchDNSResolverConfig(m.netOpts, true)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = resolvconf.Build(resolvConfPath, dns, dnsSearch, dnsOptions)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ths, err := hostsstore.New(dataStore, m.globalOptions.Namespace)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcontent, err := filesystem.ReadFile(\"/etc/hosts\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tetcHostsPath, err := hs.AllocHostsFile(containerID, content)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tnetModeArg := m.netOpts.NetworkSlice[0]\n\tnetNamespace, err := getHostNetworkingNamespace(netModeArg)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tspecs := []oci.SpecOpts{\n\t\tnetNamespace,\n\t\twithDedupMounts(\"/etc/resolv.conf\", withCustomResolvConf(resolvConfPath)),\n\t\twithDedupMounts(\"/etc/hosts\", withCustomHosts(etcHostsPath)),\n\t}\n\n\t// `/etc/hostname` does not exist on FreeBSD\n\tif runtime.GOOS == \"linux\" {\n\t\thostname := m.netOpts.Hostname\n\t\tif hostname == \"\" {\n\t\t\t// Hostname by default should be the host hostname\n\t\t\thostname, err = os.Hostname()\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Warn(\"could not get hostname\")\n\t\t\t\t// If no hostname is set, default to first 12 characters of the container ID.\n\t\t\t\thostname = containerID\n\t\t\t\tif len(hostname) > 12 {\n\t\t\t\t\thostname = hostname[0:12]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tm.netOpts.Hostname = hostname\n\n\t\thostnameOpts, err := writeEtcHostnameForContainer(m.globalOptions, m.netOpts.Hostname, containerID)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif hostnameOpts != nil {\n\t\t\tspecs = append(specs, hostnameOpts...)\n\t\t}\n\t\tif m.netOpts.Domainname != \"\" {\n\t\t\tspecs = append(specs, oci.WithDomainname(m.netOpts.Domainname))\n\t\t}\n\t}\n\n\tif rootlessutil.IsRootless() {\n\t\tdetachedNetNS, err := rootlessutil.DetachedNetNS()\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to check whether RootlessKit is running with --detach-netns: %w\", err)\n\t\t}\n\t\tif detachedNetNS != \"\" {\n\t\t\t// For rootless + host netns, we can't mount sysfs.\n\t\t\t// We can't (non-recursively) bind mount /sys, either.\n\t\t\t//\n\t\t\t// TODO: consider to just rbind /sys from the host with rro,\n\t\t\t// when rro is available (kernel >= 5.12, runc >= 1.1).\n\t\t\t//\n\t\t\t// Relevant: https://github.com/moby/buildkit/blob/v0.12.4/util/rootless/specconv/specconv_linux.go#L15-L34\n\t\t\tspecs = append(specs, withRemoveSysfs)\n\t\t}\n\t}\n\n\treturn specs, cOpts, nil\n}\n\nfunc withRemoveSysfs(_ context.Context, _ oci.Client, c *containers.Container, s *oci.Spec) error {\n\tvar hasSysfs bool\n\tfor _, mount := range s.Mounts {\n\t\tif mount.Type == \"sysfs\" {\n\t\t\thasSysfs = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasSysfs {\n\t\t// NOP, as the user has specified a custom /sys mount\n\t\treturn nil\n\t}\n\tvar mounts []specs.Mount // nolint: prealloc\n\tfor _, mount := range s.Mounts {\n\t\tif strings.HasPrefix(mount.Destination, \"/sys\") {\n\t\t\tcontinue\n\t\t}\n\t\tmounts = append(mounts, mount)\n\t}\n\ts.Mounts = mounts\n\treturn nil\n}\n\n// types.NetworkOptionsManager implementation for CNI networking settings.\n// This is a more specialized and OS-dependendant networking model so this\n// struct provides different implementations on different platforms.\ntype cniNetworkManager struct {\n\tglobalOptions types.GlobalCommandOptions\n\tnetOpts       types.NetworkOptions\n\tclient        *containerd.Client\n\tcniNetworkManagerPlatform\n}\n\n// NetworkOptions Returns a copy of the internal types.NetworkOptions.\nfunc (m *cniNetworkManager) NetworkOptions() types.NetworkOptions {\n\treturn m.netOpts\n}\n\nfunc validateUtsSettings(netOpts types.NetworkOptions) error {\n\tutsNamespace := netOpts.UTSNamespace\n\tif utsNamespace == \"\" {\n\t\treturn nil\n\t}\n\n\t// Docker considers this a validation error so keep compat.\n\t// https://docs.docker.com/reference/cli/docker/container/run/#uts\n\tif utsNamespace == UtsNamespaceHost && (netOpts.Hostname != \"\" || netOpts.Domainname != \"\") {\n\t\treturn fmt.Errorf(\"conflicting options: cannot set --hostname and/or --domainname with --uts=host\")\n\t}\n\n\treturn nil\n}\n\n// Writes the provided hostname string in a \"hostname\" file in the Container's\n// Nerdctl-managed datastore and returns the oci.SpecOpts required in the container\n// spec for the file to be mounted under /etc/hostname in the new container.\n// If the hostname is empty, the leading 12 characters of the containerID\n// This sets world readable permissions on /etc/hostname, ignoring umask\nfunc writeEtcHostnameForContainer(globalOptions types.GlobalCommandOptions, hostname string, containerID string) ([]oci.SpecOpts, error) {\n\tif containerID == \"\" {\n\t\treturn nil, fmt.Errorf(\"container ID is required for setting up hostname file\")\n\t}\n\n\tdataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstateDir, err := ContainerStateDirPath(globalOptions.Namespace, dataStore, containerID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thostnamePath := filepath.Join(stateDir, \"hostname\")\n\tif err := filesystem.WriteFile(hostnamePath, []byte(hostname+\"\\n\"), 0644); err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = os.Chmod(hostnamePath, 0644)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn []oci.SpecOpts{oci.WithHostname(hostname), withCustomEtcHostname(hostnamePath)}, nil\n}\n\n// Loads all available networks and verifies that every selected network\n// from the networkSlice is of a type within supportedTypes.\n// nolint:unused\nfunc verifyNetworkTypes(env *netutil.CNIEnv, networkSlice []string, supportedTypes []string) (map[string]*netutil.NetworkConfig, error) {\n\tres := make(map[string]*netutil.NetworkConfig, len(networkSlice))\n\tvar netConfig *netutil.NetworkConfig\n\tvar err error\n\tfor _, netstr := range networkSlice {\n\t\tif netConfig, err = env.NetworkByNameOrID(netstr); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnetType := netConfig.Plugins[0].Network.Type\n\t\tif supportedTypes != nil && !strutil.InStringSlice(supportedTypes, netType) {\n\t\t\treturn nil, fmt.Errorf(\"network type %q is not supported for network mapping %q, must be one of: %v\", netType, netstr, supportedTypes)\n\t\t}\n\n\t\tres[netstr] = netConfig\n\t}\n\n\treturn res, nil\n}\n\n// NetworkOptionsFromSpec Returns the NetworkOptions used in a container's creation from its spec.Annotations.\nfunc NetworkOptionsFromSpec(spec *specs.Spec) (types.NetworkOptions, error) {\n\topts := types.NetworkOptions{}\n\n\tif spec == nil {\n\t\treturn opts, fmt.Errorf(\"cannot determine networking options from nil spec\")\n\t}\n\tif spec.Annotations == nil {\n\t\treturn opts, fmt.Errorf(\"cannot determine networking options from nil spec.Annotations\")\n\t}\n\n\topts.Hostname = spec.Hostname\n\n\tif macAddress, ok := spec.Annotations[labels.MACAddress]; ok {\n\t\topts.MACAddress = macAddress\n\t}\n\n\tif ipAddress, ok := spec.Annotations[labels.IPAddress]; ok {\n\t\topts.IPAddress = ipAddress\n\t}\n\n\tvar networks []string\n\tnetworksJSON := spec.Annotations[labels.Networks]\n\tif err := json.Unmarshal([]byte(networksJSON), &networks); err != nil {\n\t\treturn opts, err\n\t}\n\topts.NetworkSlice = networks\n\n\treturn opts, nil\n}\n\n// Returns a lslice of keys of the values in the map that are invalid or are a non-zero-value\n// for their respective type. (e.g. anything other than a `\"\"` for string type)\n// Note that the zero-values for innately pointer-types slices/maps/chans are `nil`,\n// and NOT a zero-length container value like `[]Any{}`.\nfunc nonZeroMapValues(values map[string]interface{}) []string {\n\tnonZero := []string{}\n\n\tfor k, v := range values {\n\t\tif !reflect.ValueOf(v).IsZero() {\n\t\t\tnonZero = append(nonZero, k)\n\t\t}\n\t}\n\n\treturn nonZero\n}\n"
  },
  {
    "path": "pkg/containerutil/container_network_manager_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"path/filepath\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/resolvconf\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\ntype cniNetworkManagerPlatform struct {\n}\n\n// Verifies that the internal network settings are correct.\nfunc (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {\n\te, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork(m.globalOptions.BridgeIP))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif m.netOpts.MACAddress != \"\" {\n\t\tmacValidNetworks := []string{\"bridge\", \"macvlan\"}\n\t\tif _, err := verifyNetworkTypes(e, m.netOpts.NetworkSlice, macValidNetworks); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn validateUtsSettings(m.netOpts)\n}\n\n// Performs setup actions required for the container with the given ID.\nfunc (m *cniNetworkManager) SetupNetworking(_ context.Context, _ string) error {\n\t// NOTE: on non-Windows systems which support OCI hooks, CNI networking setup\n\t// is performed via createRuntime and postCreate hooks whose logic can\n\t// be found in the pkg/ocihook package.\n\treturn nil\n}\n\n// Performs any required cleanup actions for the given container.\n// Should only be called to revert any setup steps performed in setupNetworking.\nfunc (m *cniNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error {\n\t// NOTE: on non-Windows systems which support OCI hooks, CNI networking setup\n\t// is performed via createRuntime and postCreate hooks whose logic can\n\t// be found in the pkg/ocihook package.\n\treturn nil\n}\n\n// Returns the set of NetworkingOptions which should be set as labels on the container.\nfunc (m *cniNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {\n\treturn m.netOpts, nil\n}\n\n// Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n// the network specs which need to be applied to the container with the given ID.\nfunc (m *cniNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\topts := []oci.SpecOpts{}\n\tcOpts := []containerd.NewContainerOpts{}\n\n\tdataStore, err := clientutil.DataStore(m.globalOptions.DataRoot, m.globalOptions.Address)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tstateDir, err := ContainerStateDirPath(m.globalOptions.Namespace, dataStore, containerID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tresolvConfPath := filepath.Join(stateDir, \"resolv.conf\")\n\tif err := m.buildResolvConf(resolvConfPath); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// the content of /etc/hosts is created in OCI Hook\n\ths, err := hostsstore.New(dataStore, m.globalOptions.Namespace)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tetcHostsPath, err := hs.AllocHostsFile(containerID, []byte(\"\"))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\topts = append(opts, withCustomResolvConf(resolvConfPath), withCustomHosts(etcHostsPath))\n\n\tif m.netOpts.UTSNamespace != UtsNamespaceHost {\n\t\t// If no hostname is set, default to first 12 characters of the container ID.\n\t\thostname := m.netOpts.Hostname\n\t\tif hostname == \"\" {\n\t\t\thostname = containerID\n\t\t\tif len(hostname) > 12 {\n\t\t\t\thostname = hostname[0:12]\n\t\t\t}\n\t\t}\n\t\tm.netOpts.Hostname = hostname\n\n\t\thostnameOpts, err := writeEtcHostnameForContainer(m.globalOptions, m.netOpts.Hostname, containerID)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif hostnameOpts != nil {\n\t\t\topts = append(opts, hostnameOpts...)\n\t\t}\n\t\tif m.netOpts.Domainname != \"\" {\n\t\t\topts = append(opts, oci.WithDomainname(m.netOpts.Domainname))\n\t\t}\n\t}\n\n\treturn opts, cOpts, nil\n}\n\nfunc (m *cniNetworkManager) buildResolvConf(resolvConfPath string) error {\n\tvar err error\n\tslirp4Dns := []string{}\n\tif rootlessutil.IsRootlessChild() {\n\t\tslirp4Dns, err = dnsutil.GetSlirp4netnsDNS()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar (\n\t\tnameServers   = m.netOpts.DNSServers\n\t\tsearchDomains = m.netOpts.DNSSearchDomains\n\t\tdnsOptions    = m.netOpts.DNSResolvConfOptions\n\t)\n\n\t// Use host defaults if any DNS settings are missing:\n\tif len(nameServers) == 0 || len(searchDomains) == 0 || len(dnsOptions) == 0 {\n\t\tconf, err := resolvconf.Get()\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// if resolvConf file does't exist, using default resolvers\n\t\t\tconf = &resolvconf.File{}\n\t\t\tlog.L.WithError(err).Debugf(\"resolvConf file doesn't exist on host\")\n\t\t}\n\t\tconf, err = resolvconf.FilterResolvDNS(conf.Content, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(nameServers) == 0 {\n\t\t\tnameServers = resolvconf.GetNameservers(conf.Content, resolvconf.IPv4)\n\t\t}\n\t\tif len(searchDomains) == 0 {\n\t\t\tsearchDomains = resolvconf.GetSearchDomains(conf.Content)\n\t\t}\n\t\tif len(dnsOptions) == 0 {\n\t\t\tdnsOptions = resolvconf.GetOptions(conf.Content)\n\t\t}\n\t}\n\n\t_, err = resolvconf.Build(resolvConfPath, append(slirp4Dns, nameServers...), searchDomains, dnsOptions)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/containerutil/container_network_manager_other.go",
    "content": "//go:build !(linux || windows)\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"runtime\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\ntype cniNetworkManagerPlatform struct {\n}\n\n// Verifies that the internal network settings are correct.\nfunc (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {\n\treturn fmt.Errorf(\"CNI networking currently unsupported on %s\", runtime.GOOS)\n}\n\n// Performs setup actions required for the container with the given ID.\nfunc (m *cniNetworkManager) SetupNetworking(_ context.Context, _ string) error {\n\treturn fmt.Errorf(\"CNI networking currently unsupported on %s\", runtime.GOOS)\n}\n\n// Performs any required cleanup actions for the given container.\n// Should only be called to revert any setup steps performed in setupNetworking.\nfunc (m *cniNetworkManager) CleanupNetworking(_ context.Context, _ containerd.Container) error {\n\treturn fmt.Errorf(\"CNI networking currently unsupported on %s\", runtime.GOOS)\n}\n\n// Returns the set of NetworkingOptions which should be set as labels on the container.\nfunc (m *cniNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {\n\treturn m.netOpts, fmt.Errorf(\"CNI networking currently unsupported on %s\", runtime.GOOS)\n}\n\n// Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n// the network specs which need to be applied to the container with the given ID.\nfunc (m *cniNetworkManager) ContainerNetworkingOpts(_ context.Context, _ string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\treturn []oci.SpecOpts{}, []containerd.NewContainerOpts{}, fmt.Errorf(\"CNI networking currently unsupported on %s\", runtime.GOOS)\n}\n"
  },
  {
    "path": "pkg/containerutil/container_network_manager_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestZeroMapValues(t *testing.T) {\n\temptyString := \"\"\n\ttestCases := []struct {\n\t\tkey          string\n\t\tvalue        interface{}\n\t\tshouldBeZero bool\n\t}{\n\t\t{\n\t\t\tkey:          \"false\",\n\t\t\tvalue:        false,\n\t\t\tshouldBeZero: true,\n\t\t},\n\t\t{\n\t\t\tkey:          \"true\",\n\t\t\tvalue:        true,\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"zeroInt\",\n\t\t\tvalue:        int(0),\n\t\t\tshouldBeZero: true,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nonZeroInt\",\n\t\t\tvalue:        int(1),\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"zeroString\",\n\t\t\tvalue:        \"\",\n\t\t\tshouldBeZero: true,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nonZeroString\",\n\t\t\tvalue:        \"non-zero\",\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nilPointer\",\n\t\t\tvalue:        (*string)(nil),\n\t\t\tshouldBeZero: true,\n\t\t},\n\t\t{\n\t\t\tkey:   \"pointerToEmpty\",\n\t\t\tvalue: &emptyString,\n\t\t\t// technically just a nil pointer check, so any value should be non-Zero:\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nilSlice\",\n\t\t\tvalue:        []string(nil),\n\t\t\tshouldBeZero: true,\n\t\t},\n\t\t{\n\t\t\tkey:          \"emptySlice\",\n\t\t\tvalue:        []string{},\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nonEmptySlice\",\n\t\t\tvalue:        []string{\"non-empty\"},\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nilMap\",\n\t\t\tvalue:        map[string]int(nil),\n\t\t\tshouldBeZero: true,\n\t\t},\n\t\t{\n\t\t\tkey:          \"emptyMap\",\n\t\t\tvalue:        map[string]int{},\n\t\t\tshouldBeZero: false,\n\t\t},\n\t\t{\n\t\t\tkey:          \"nonEmptyMap\",\n\t\t\tvalue:        map[string]int{\"non-empty\": 42},\n\t\t\tshouldBeZero: false,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttestName := fmt.Sprintf(\"%s=%t\", tc.key, tc.shouldBeZero)\n\t\tt.Run(testName, func(tt *testing.T) {\n\t\t\tresult := nonZeroMapValues(map[string]interface{}{tc.key: tc.value})\n\t\t\tif tc.shouldBeZero {\n\t\t\t\tassert.Equal(tt, len(result), 0)\n\t\t\t} else {\n\t\t\t\tassert.Equal(tt, len(result), 1)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/containerutil/container_network_manager_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/netns\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ocihook\"\n)\n\ntype cniNetworkManagerPlatform struct {\n\tnetNs *netns.NetNS\n}\n\n// Verifies that the internal network settings are correct.\nfunc (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {\n\te, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork(m.globalOptions.BridgeIP))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// NOTE: only currently supported network type on Windows is nat:\n\tvalidNetworkTypes := []string{\"nat\"}\n\tif _, err := verifyNetworkTypes(e, m.netOpts.NetworkSlice, validNetworkTypes); err != nil {\n\t\treturn err\n\t}\n\n\tnonZeroArgs := nonZeroMapValues(map[string]interface{}{\n\t\t\"--hostname\":   m.netOpts.Hostname,\n\t\t\"--domainname\": m.netOpts.Domainname,\n\t\t\"--uts\":        m.netOpts.UTSNamespace,\n\t\t// NOTE: IP and MAC settings are currently ignored on Windows.\n\t\t\"--ip-address\":  m.netOpts.IPAddress,\n\t\t\"--mac-address\": m.netOpts.MACAddress,\n\t\t// NOTE: zero-length slices count as a non-zero-value so we explicitly check length:\n\t\t\"--dns-opt/--dns-option\": len(m.netOpts.DNSResolvConfOptions) != 0,\n\t\t\"--dns-servers\":          len(m.netOpts.DNSServers) != 0,\n\t\t\"--dns-search\":           len(m.netOpts.DNSSearchDomains) != 0,\n\t\t\"--add-host\":             len(m.netOpts.AddHost) != 0,\n\t})\n\tif len(nonZeroArgs) != 0 {\n\t\treturn fmt.Errorf(\"the following networking arguments are not supported on Windows: %+v\", nonZeroArgs)\n\t}\n\n\treturn nil\n}\n\nfunc (m *cniNetworkManager) getCNI() (cni.CNI, error) {\n\te, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork(m.globalOptions.BridgeIP))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to instantiate CNI env: %w\", err)\n\t}\n\n\tcniOpts := []cni.Opt{\n\t\tcni.WithPluginDir([]string{m.globalOptions.CNIPath}),\n\t\tcni.WithPluginConfDir(m.globalOptions.CNINetConfPath),\n\t}\n\n\tif netMap, err := verifyNetworkTypes(e, m.netOpts.NetworkSlice, nil); err == nil {\n\t\tfor _, netConf := range netMap {\n\t\t\tcniOpts = append(cniOpts, cni.WithConfListBytes(netConf.Bytes))\n\t\t}\n\t} else {\n\t\treturn nil, err\n\t}\n\n\treturn cni.New(cniOpts...)\n}\n\n// Performs setup actions required for the container with the given ID.\nfunc (m *cniNetworkManager) SetupNetworking(ctx context.Context, containerID string) error {\n\tcni, err := m.getCNI()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container networking for setup: %w\", err)\n\t}\n\n\tnetNs, err := m.setupNetNs()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = cni.Setup(ctx, containerID, netNs.GetPath(), m.getCNINamespaceOpts()...)\n\treturn err\n}\n\n// Performs any required cleanup actions for the given container.\n// Should only be called to revert any setup steps performed in setupNetworking.\nfunc (m *cniNetworkManager) CleanupNetworking(ctx context.Context, container containerd.Container) error {\n\tcontainerID := container.ID()\n\tcni, err := m.getCNI()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container networking for cleanup: %w\", err)\n\t}\n\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container specs for networking cleanup: %w\", err)\n\t}\n\n\tnetNsID, found := spec.Annotations[ocihook.NetworkNamespace]\n\tif !found {\n\t\treturn fmt.Errorf(\"no %q annotation present on container with ID %s\", ocihook.NetworkNamespace, containerID)\n\t}\n\n\treturn cni.Remove(ctx, containerID, netNsID, m.getCNINamespaceOpts()...)\n}\n\n// Returns the set of NetworkingOptions which should be set as labels on the container.\nfunc (m *cniNetworkManager) InternalNetworkingOptionLabels(_ context.Context) (types.NetworkOptions, error) {\n\treturn m.netOpts, nil\n}\n\n// Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent\n// the network specs which need to be applied to the container with the given ID.\nfunc (m *cniNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) {\n\tns, err := m.setupNetNs()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\topts := []oci.SpecOpts{\n\t\toci.WithWindowNetworksAllowUnqualifiedDNSQuery(),\n\t\toci.WithWindowsNetworkNamespace(ns.GetPath()),\n\t}\n\n\tcOpts := []containerd.NewContainerOpts{\n\t\tcontainerd.WithAdditionalContainerLabels(\n\t\t\tmap[string]string{\n\t\t\t\tocihook.NetworkNamespace: ns.GetPath(),\n\t\t\t},\n\t\t),\n\t}\n\n\treturn opts, cOpts, nil\n}\n\n// Returns the string path to a network namespace.\nfunc (m *cniNetworkManager) setupNetNs() (*netns.NetNS, error) {\n\tif m.netNs != nil {\n\t\treturn m.netNs, nil\n\t}\n\n\t// NOTE: the baseDir argument to NewNetNS is ignored on Windows.\n\tns, err := netns.NewNetNS(\"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tm.netNs = ns\n\treturn ns, err\n}\n\n// Returns the []cni.NamespaceOpts to be used for CNI setup/teardown.\nfunc (m *cniNetworkManager) getCNINamespaceOpts() []cni.NamespaceOpts {\n\topts := []cni.NamespaceOpts{\n\t\tcni.WithLabels(map[string]string{\n\t\t\t// allow loose CNI argument verification\n\t\t\t// FYI: https://github.com/containernetworking/cni/issues/560\n\t\t\t\"IgnoreUnknown\": \"1\",\n\t\t}),\n\t}\n\n\tif m.netOpts.MACAddress != \"\" {\n\t\topts = append(opts, cni.WithArgs(\"MAC\", m.netOpts.MACAddress))\n\t}\n\n\tif m.netOpts.IPAddress != \"\" {\n\t\topts = append(opts, cni.WithArgs(\"IP\", m.netOpts.IPAddress))\n\t}\n\n\tif m.netOpts.PortMappings != nil {\n\t\topts = append(opts, cni.WithCapabilityPortMap(m.netOpts.PortMappings))\n\t}\n\n\treturn opts\n}\n"
  },
  {
    "path": "pkg/containerutil/containerutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\tdockercliopts \"github.com/docker/cli/opts\"\n\tdockeropts \"github.com/docker/docker/opts\"\n\t\"github.com/moby/sys/signal\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/console\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/core/runtime/restart\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/go-cni\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/formatter\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipcutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels/k8slabels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/signalutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/taskutil\"\n)\n\n// PrintHostPort writes to `writer` the public (HostIP:HostPort) of a given `containerPort/protocol` in a container.\n// if `containerPort < 0`, it writes all public ports of the container.\nfunc PrintHostPort(ctx context.Context, writer io.Writer, container containerd.Container, containerPort int, proto string, ports []cni.PortMapping) error {\n\tif containerPort < 0 {\n\t\tfor _, p := range ports {\n\t\t\tfmt.Fprintf(writer, \"%d/%s -> %s:%d\\n\", p.ContainerPort, p.Protocol, p.HostIP, p.HostPort)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor _, p := range ports {\n\t\tif p.ContainerPort == int32(containerPort) && strings.ToLower(p.Protocol) == proto {\n\t\t\tfmt.Fprintf(writer, \"%s:%d\\n\", p.HostIP, p.HostPort)\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"no public port %d/%s published for %q\", containerPort, proto, container.ID())\n}\n\n// ContainerStatus returns the container's status from its task.\nfunc ContainerStatus(ctx context.Context, c containerd.Container) (containerd.Status, error) {\n\ttask, err := c.Task(ctx, nil)\n\tif err != nil {\n\t\treturn containerd.Status{}, err\n\t}\n\n\treturn task.Status(ctx)\n}\n\n// ContainerNetNSPath returns the netns path of a container.\nfunc ContainerNetNSPath(ctx context.Context, c containerd.Container) (string, error) {\n\ttask, err := c.Task(ctx, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif status.Status != containerd.Running {\n\t\treturn \"\", fmt.Errorf(\"invalid target container: %s, should be running\", c.ID())\n\t}\n\treturn fmt.Sprintf(\"/proc/%d/ns/net\", task.Pid()), nil\n}\n\n// UpdateStatusLabel updates the \"containerd.io/restart.status\"\n// label of the container according to the value of restart desired status.\nfunc UpdateStatusLabel(ctx context.Context, container containerd.Container, status containerd.ProcessStatus) error {\n\topt := containerd.WithAdditionalContainerLabels(map[string]string{\n\t\trestart.StatusLabel: string(status),\n\t})\n\treturn container.Update(ctx, containerd.UpdateContainerOpts(opt))\n}\n\n// UpdateExplicitlyStoppedLabel updates the \"containerd.io/restart.explicitly-stopped\"\n// label of the container according to the value of explicitlyStopped.\nfunc UpdateExplicitlyStoppedLabel(ctx context.Context, container containerd.Container, explicitlyStopped bool) error {\n\topt := containerd.WithAdditionalContainerLabels(map[string]string{\n\t\trestart.ExplicitlyStoppedLabel: strconv.FormatBool(explicitlyStopped),\n\t})\n\treturn container.Update(ctx, containerd.UpdateContainerOpts(opt))\n}\n\n// UpdateErrorLabel updates the \"nerdctl/error\"\n// label of the container according to the container error.\nfunc UpdateErrorLabel(ctx context.Context, container containerd.Container, err error) error {\n\topt := containerd.WithAdditionalContainerLabels(map[string]string{\n\t\tlabels.Error: err.Error(),\n\t})\n\treturn container.Update(ctx, containerd.UpdateContainerOpts(opt))\n}\n\n// WithBindMountHostProcfs replaces procfs mount with rbind.\n// Required for --pid=host on rootless.\n//\n// https://github.com/moby/moby/pull/41893/files\n// https://github.com/containers/podman/blob/v3.0.0-rc1/pkg/specgen/generate/oci.go#L248-L257\nfunc WithBindMountHostProcfs(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\tfor i, m := range s.Mounts {\n\t\tif path.Clean(m.Destination) == \"/proc\" {\n\t\t\tnewM := specs.Mount{\n\t\t\t\tDestination: \"/proc\",\n\t\t\t\tType:        \"bind\",\n\t\t\t\tSource:      \"/proc\",\n\t\t\t\tOptions:     []string{\"rbind\", \"nosuid\", \"noexec\", \"nodev\"},\n\t\t\t}\n\t\t\ts.Mounts[i] = newM\n\t\t}\n\t}\n\n\t// Remove ReadonlyPaths for /proc/*\n\tnewROP := s.Linux.ReadonlyPaths[:0]\n\tfor _, x := range s.Linux.ReadonlyPaths {\n\t\tx = path.Clean(x)\n\t\tif !strings.HasPrefix(x, \"/proc/\") {\n\t\t\tnewROP = append(newROP, x)\n\t\t}\n\t}\n\ts.Linux.ReadonlyPaths = newROP\n\treturn nil\n}\n\n// GenerateSharingPIDOpts returns the oci.SpecOpts that shares the host linux namespace from `targetCon`\n// If `targetCon` doesn't have a `PIDNamespace`, a new one is generated from its `Pid`.\nfunc GenerateSharingPIDOpts(ctx context.Context, targetCon containerd.Container) ([]oci.SpecOpts, error) {\n\topts := make([]oci.SpecOpts, 0)\n\n\ttask, err := targetCon.Task(ctx, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif status.Status != containerd.Running {\n\t\treturn nil, fmt.Errorf(\"shared container is not running\")\n\t}\n\n\tspec, err := targetCon.Spec(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tisHost := true\n\tfor _, n := range spec.Linux.Namespaces {\n\t\tif n.Type == specs.PIDNamespace {\n\t\t\tisHost = false\n\t\t}\n\t}\n\tif isHost {\n\t\topts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))\n\t\tif rootlessutil.IsRootless() {\n\t\t\topts = append(opts, WithBindMountHostProcfs)\n\t\t}\n\t} else {\n\t\tns := specs.LinuxNamespace{\n\t\t\tType: specs.PIDNamespace,\n\t\t\tPath: fmt.Sprintf(\"/proc/%d/ns/pid\", task.Pid()),\n\t\t}\n\t\topts = append(opts, oci.WithLinuxNamespace(ns))\n\t}\n\treturn opts, nil\n}\n\n// Start starts `container` with `attach` flag. If `attach` is true, it will attach to the container's stdio.\nfunc Start(ctx context.Context, container containerd.Container, isAttach bool, isInteractive bool, client *containerd.Client, detachKeys string, checkpointDir string, cfg *config.Config, nerdctlCmd string, nerdctlArgs []string) (err error) {\n\t// defer the storage of start error in the dedicated label\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tUpdateErrorLabel(ctx, container, err)\n\t\t}\n\t}()\n\tlab, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, ok := lab[k8slabels.ContainerType]; ok {\n\t\tlog.L.Warnf(\"nerdctl does not support starting container %s created by Kubernetes\", container.ID())\n\t}\n\tif err := ReconfigNetContainer(ctx, container, client, lab); err != nil {\n\t\treturn err\n\t}\n\n\tif err := ReconfigPIDContainer(ctx, container, client, lab); err != nil {\n\t\treturn err\n\t}\n\n\tif err := ReconfigIPCContainer(ctx, container, client, lab); err != nil {\n\t\treturn err\n\t}\n\n\tprocess, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tisTerminal := process.Process.Terminal\n\tvar con console.Console\n\tif (isInteractive || isAttach) && isTerminal {\n\t\tcon, err = consoleutil.Current()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer con.Reset()\n\t\tif _, err := term.MakeRaw(int(con.Fd())); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlogURI := lab[labels.LogURI]\n\tnamespace := lab[labels.Namespace]\n\tcStatus := formatter.ContainerStatus(ctx, container)\n\tif cStatus == \"Up\" {\n\t\tlog.G(ctx).Warnf(\"container %s is already running\", container.ID())\n\t\treturn nil\n\t}\n\n\t_, restartPolicyExist := lab[restart.PolicyLabel]\n\tif restartPolicyExist {\n\t\tif err := UpdateStatusLabel(ctx, container, containerd.Running); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := UpdateExplicitlyStoppedLabel(ctx, container, false); err != nil {\n\t\treturn err\n\t}\n\tif oldTask, err := container.Task(ctx, nil); err == nil {\n\t\tif _, err := oldTask.Delete(ctx); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Debug(\"failed to delete old task\")\n\t\t}\n\t}\n\tdetachC := make(chan struct{})\n\tattachStreamOpt := []string{}\n\tif isAttach {\n\t\t// In start, isAttach attaches only STDOUT/STDERR\n\t\t// source: https://github.com/containerd/nerdctl/blob/main/docs/command-reference.md#whale-nerdctl-start\n\t\tattachStreamOpt = []string{\"STDOUT\", \"STDERR\"}\n\t}\n\ttask, err := taskutil.NewTask(ctx, client, container, taskutil.TaskOptions{\n\t\tAttachStreamOpt: attachStreamOpt,\n\t\tIsInteractive:   isInteractive,\n\t\tIsTerminal:      isTerminal,\n\t\tIsDetach:        true,\n\t\tCon:             con,\n\t\tLogURI:          logURI,\n\t\tDetachKeys:      detachKeys,\n\t\tNamespace:       namespace,\n\t\tDetachC:         detachC,\n\t\tCheckpointDir:   checkpointDir,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tstatusC, err := task.Wait(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := task.Start(ctx); err != nil {\n\t\treturn err\n\t}\n\n\t// If container has health checks configured, create and start systemd timer/service files.\n\tif err := healthcheck.CreateTimer(ctx, container, cfg, nerdctlCmd, nerdctlArgs); err != nil {\n\t\treturn fmt.Errorf(\"failed to create healthcheck timer: %w\", err)\n\t}\n\tif err := healthcheck.StartTimer(ctx, container, cfg); err != nil {\n\t\treturn fmt.Errorf(\"failed to start healthcheck timer: %w\", err)\n\t}\n\n\tif !isAttach {\n\t\treturn nil\n\t}\n\tif isAttach && isTerminal {\n\t\tif err := consoleutil.HandleConsoleResize(ctx, task, con); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Error(\"console resize\")\n\t\t}\n\t}\n\tsigc := signalutil.ForwardAllSignals(ctx, task)\n\tdefer signalutil.StopCatch(sigc)\n\tselect {\n\t// io.Wait() would return when either 1) the user detaches from the container OR 2) the container is about to exit.\n\t//\n\t// If we replace the `select` block with io.Wait() and\n\t// directly use task.Status() to check the status of the container after io.Wait() returns,\n\t// it can still be running even though the container is about to exit (somehow especially for Windows).\n\t//\n\t// As a result, we need a separate detachC to distinguish from the 2 cases mentioned above.\n\tcase <-detachC:\n\t\tio := task.IO()\n\t\tif io == nil {\n\t\t\treturn errors.New(\"got a nil IO from the task\")\n\t\t}\n\t\tio.Wait()\n\tcase status := <-statusC:\n\t\tcode, _, err := status.Result()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif code != 0 {\n\t\t\treturn errutil.NewExitCoderErr(int(code))\n\t\t}\n\t}\n\treturn nil\n}\n\n// Stop stops `container` by sending SIGTERM. If the container is not stopped after `timeout`, it sends a SIGKILL.\nfunc Stop(ctx context.Context, container containerd.Container, timeout *time.Duration, signalValue string) (err error) {\n\t// defer the storage of stop error in the dedicated label\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tUpdateErrorLabel(ctx, container, err)\n\t\t}\n\t}()\n\tif err := UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil {\n\t\treturn err\n\t}\n\n\tl, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC])\n\tif err != nil {\n\t\treturn err\n\t}\n\t// defer umount\n\tdefer func() {\n\t\tif err := ipcutil.CleanUp(ipc); err != nil {\n\t\t\tlog.G(ctx).Warnf(\"failed to clean up IPC container %s: %s\", container.ID(), err)\n\t\t}\n\t}()\n\n\tif timeout == nil {\n\t\tt, ok := l[labels.StopTimeout]\n\t\tif !ok {\n\t\t\t// Default is 10 seconds.\n\t\t\tt = \"10\"\n\t\t}\n\t\ttd, err := time.ParseDuration(t + \"s\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttimeout = &td\n\t}\n\n\ttask, err := container.Task(ctx, cio.Load)\n\tif err != nil {\n\t\t// NOTE: NotFound doesn't mean that container hasn't started.\n\t\t// In docker/CRI-containerd plugin, the task will be deleted\n\t\t// when it exits. So, the status will be \"created\" for this\n\t\t// case.\n\t\tif errdefs.IsNotFound(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpaused := false\n\n\tswitch status.Status {\n\tcase containerd.Created, containerd.Stopped:\n\t\t// Cleanup the IO after a successful Stop\n\t\tif io := task.IO(); io != nil {\n\t\t\tif cerr := io.Close(); cerr != nil {\n\t\t\t\tlog.G(ctx).Warnf(\"failed to close IO for container %s: %v\", container.ID(), cerr)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\tcase containerd.Paused, containerd.Pausing:\n\t\tpaused = true\n\tdefault:\n\t}\n\n\t// NOTE: ctx is main context so that it's ok to use for task.Wait().\n\texitCh, err := task.Wait(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// signal will be sent once resume is finished\n\tif paused {\n\t\tif err := task.Resume(ctx); err != nil {\n\t\t\tlog.G(ctx).Errorf(\"cannot unpause container %s: %s\", container.ID(), err)\n\t\t\treturn err\n\t\t}\n\t}\n\tif *timeout > 0 {\n\t\tsig, err := getSignal(signalValue, l)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := task.Kill(ctx, sig); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsigtermCtx, sigtermCtxCancel := context.WithTimeout(ctx, *timeout)\n\t\tdefer sigtermCtxCancel()\n\n\t\terr = waitContainerStop(sigtermCtx, task, exitCh, container.ID())\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tif ctx.Err() != nil {\n\t\t\treturn ctx.Err()\n\t\t}\n\t}\n\n\tsig, err := signal.ParseSignal(\"SIGKILL\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := task.Kill(ctx, sig); err != nil {\n\t\treturn err\n\t}\n\n\treturn waitContainerStop(ctx, task, exitCh, container.ID())\n}\n\nfunc getSignal(signalValue string, containerLabels map[string]string) (syscall.Signal, error) {\n\tif signalValue != \"\" {\n\t\treturn signal.ParseSignal(signalValue)\n\t}\n\n\tif stopSignal, ok := containerLabels[containerd.StopSignalLabel]; ok {\n\t\treturn signal.ParseSignal(stopSignal)\n\t}\n\n\treturn signal.ParseSignal(\"SIGTERM\")\n}\n\nfunc waitContainerStop(ctx context.Context, task containerd.Task, exitCh <-chan containerd.ExitStatus, id string) error {\n\tselect {\n\tcase <-ctx.Done():\n\t\tif err := ctx.Err(); err != nil {\n\t\t\treturn fmt.Errorf(\"wait container %v: %w\", id, err)\n\t\t}\n\t\treturn nil\n\tcase status := <-exitCh:\n\t\t// Cleanup the IO after a successful Stop\n\t\tif io := task.IO(); io != nil {\n\t\t\tif cerr := io.Close(); cerr != nil {\n\t\t\t\tlog.G(ctx).Warnf(\"failed to close IO for container %s: %v\", id, cerr)\n\t\t\t}\n\t\t}\n\t\treturn status.Error()\n\t}\n}\n\n// Pause pauses a container by its id.\nfunc Pause(ctx context.Context, client *containerd.Client, id string) error {\n\tcontainer, err := client.LoadContainer(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttask, err := container.Task(ctx, cio.Load)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch status.Status {\n\tcase containerd.Paused:\n\t\treturn fmt.Errorf(\"container %s is already paused\", id)\n\tcase containerd.Created, containerd.Stopped:\n\t\treturn fmt.Errorf(\"container %s is not running\", id)\n\tdefault:\n\t\treturn task.Pause(ctx)\n\t}\n}\n\n// Unpause unpauses a container by its id.\nfunc Unpause(ctx context.Context, client *containerd.Client, id string, cfg *config.Config, nerdctlCmd string, nerdctlArgs []string) error {\n\tcontainer, err := client.LoadContainer(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttask, err := container.Task(ctx, cio.Load)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Recreate healthcheck related systemd timer/service files.\n\tif err := healthcheck.CreateTimer(ctx, container, cfg, nerdctlCmd, nerdctlArgs); err != nil {\n\t\treturn fmt.Errorf(\"failed to create healthcheck timer: %w\", err)\n\t}\n\tif err := healthcheck.StartTimer(ctx, container, cfg); err != nil {\n\t\treturn fmt.Errorf(\"failed to start healthcheck timer: %w\", err)\n\t}\n\n\tswitch status.Status {\n\tcase containerd.Paused:\n\t\treturn task.Resume(ctx)\n\tdefault:\n\t\treturn fmt.Errorf(\"container %s is not paused\", id)\n\t}\n}\n\n// ContainerStateDirPath returns the path to the Nerdctl-managed state directory for the container with the given ID.\nfunc ContainerStateDirPath(ns, dataStore, id string) (string, error) {\n\treturn filepath.Join(dataStore, \"containers\", ns, id), nil\n}\n\n// ContainerVolume is a struct representing a volume in a container.\ntype ContainerVolume struct {\n\tType        string\n\tName        string\n\tSource      string\n\tDestination string\n\tMode        string\n\tRW          bool\n\tPropagation string\n}\n\n// GetContainerVolumes is a function that returns a slice of containerVolume pointers.\n// It accepts a map of container labels as input, where key is the label name and value is its associated value.\n// The function iterates over the predefined volume labels (AnonymousVolumes and Mounts)\n// and for each, it checks if the labels exists in the provided container labels.\n// If yes, it decodes the label value from JSON format and appends the volumes to the result.\n// In case of error during decoding, it logs the error and continues to the next label.\nfunc GetContainerVolumes(containerLabels map[string]string) []*ContainerVolume {\n\tvar vols []*ContainerVolume\n\tvolLabels := []string{labels.AnonymousVolumes, labels.Mounts}\n\tfor _, volLabel := range volLabels {\n\t\tnames, ok := containerLabels[volLabel]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tvar (\n\t\t\tvolumes []*ContainerVolume\n\t\t\terr     error\n\t\t)\n\t\tif volLabel == labels.Mounts {\n\t\t\terr = json.Unmarshal([]byte(names), &volumes)\n\t\t}\n\t\tif volLabel == labels.AnonymousVolumes {\n\t\t\tvar anonymous []string\n\t\t\terr = json.Unmarshal([]byte(names), &anonymous)\n\t\t\tfor _, anony := range anonymous {\n\t\t\t\tvolumes = append(volumes, &ContainerVolume{Name: anony})\n\t\t\t}\n\n\t\t}\n\t\tif err != nil {\n\t\t\tlog.L.Warn(err)\n\t\t}\n\t\tvols = append(vols, volumes...)\n\t}\n\treturn vols\n}\n\nfunc GetContainerName(containerLabels map[string]string) string {\n\tif name, ok := containerLabels[labels.Name]; ok {\n\t\treturn name\n\t}\n\n\tif ns, ok := containerLabels[k8slabels.PodNamespace]; ok {\n\t\tif podName, ok := containerLabels[k8slabels.PodName]; ok {\n\t\t\tif containerName, ok := containerLabels[k8slabels.ContainerName]; ok {\n\t\t\t\t// Container\n\t\t\t\treturn fmt.Sprintf(\"k8s://%s/%s/%s\", ns, podName, containerName)\n\t\t\t}\n\t\t\t// Pod sandbox\n\t\t\treturn fmt.Sprintf(\"k8s://%s/%s\", ns, podName)\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// EncodeContainerRmOptLabel encodes bool value for the --rm option into string value for a label.\nfunc EncodeContainerRmOptLabel(rmOpt bool) string {\n\treturn fmt.Sprintf(\"%t\", rmOpt)\n}\n\n// DecodeContainerRmOptLabel decodes bool value for the --rm option from string value for a label.\nfunc DecodeContainerRmOptLabel(rmOptLabel string) (bool, error) {\n\treturn strconv.ParseBool(rmOptLabel)\n}\n\n// ParseExtraHosts takes an array of host-to-IP mapping strings, e.g. \"localhost:127.0.0.1\",\n// and a hostGatewayIP for resolving mappings to \"host-gateway\".\n//\n// Returns a map of host-to-IPs or errors if any mapping strings are not correctly formatted.\nfunc ParseExtraHosts(extraHosts []string, hostGatewayIP, separator string) ([]string, error) {\n\thosts := make([]string, 0, len(extraHosts))\n\tfor _, hostToIP := range strutil.DedupeStrSlice(extraHosts) {\n\t\tif _, err := dockercliopts.ValidateExtraHost(hostToIP); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tparts := strings.SplitN(hostToIP, \":\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid host-to-IP map %s\", hostToIP)\n\t\t}\n\n\t\thost, ip := parts[0], parts[1]\n\n\t\t// If the IP address is a string called \"host-gateway\", replace this value with the IP address stored\n\t\t// in the daemon level HostGatewayIP config variable.\n\t\tif ip == dockeropts.HostGatewayName && hostGatewayIP == \"\" {\n\t\t\treturn nil, errors.New(\"unable to derive the IP value for host-gateway\")\n\t\t} else if ip == dockeropts.HostGatewayName {\n\t\t\tip = hostGatewayIP\n\t\t}\n\n\t\thosts = append(hosts, host+separator+ip)\n\t}\n\treturn hosts, nil\n}\n"
  },
  {
    "path": "pkg/containerutil/containerutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestParseExtraHosts(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\textraHosts     []string\n\t\thostGateway    string\n\t\tseparator      string\n\t\texpected       []string\n\t\texpectedErrStr string\n\t}{\n\t\t{\n\t\t\tname:     \"NoExtraHosts\",\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname:       \"ExtraHosts\",\n\t\t\textraHosts: []string{\"localhost:127.0.0.1\", \"localhost:[::1]\"},\n\t\t\tseparator:  \":\",\n\t\t\texpected:   []string{\"localhost:127.0.0.1\", \"localhost:[::1]\"},\n\t\t},\n\t\t{\n\t\t\tname:       \"EqualsSeperator\",\n\t\t\textraHosts: []string{\"localhost:127.0.0.1\", \"localhost:[::1]\"},\n\t\t\tseparator:  \"=\",\n\t\t\texpected:   []string{\"localhost=127.0.0.1\", \"localhost=[::1]\"},\n\t\t},\n\t\t{\n\t\t\tname:           \"InvalidExtraHostFormat\",\n\t\t\textraHosts:     []string{\"localhost\"},\n\t\t\texpectedErrStr: \"bad format for add-host: \\\"localhost\\\"\",\n\t\t},\n\t\t{\n\t\t\tname:           \"ErrorOnHostGatewayExtraHostWithNoHostGatewayIPSet\",\n\t\t\textraHosts:     []string{\"localhost:host-gateway\"},\n\t\t\tseparator:      \":\",\n\t\t\texpectedErrStr: \"unable to derive the IP value for host-gateway\",\n\t\t},\n\t\t{\n\t\t\tname:        \"HostGatewayIP\",\n\t\t\textraHosts:  []string{\"localhost:host-gateway\"},\n\t\t\thostGateway: \"10.10.0.1\",\n\t\t\tseparator:   \":\",\n\t\t\texpected:    []string{\"localhost:10.10.0.1\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\textraHosts, err := ParseExtraHosts(test.extraHosts, test.hostGateway, test.separator)\n\t\t\tif err != nil && err.Error() != test.expectedErrStr {\n\t\t\t\tt.Fatalf(\"expected '%s', actual '%v'\", test.expectedErrStr, err)\n\t\t\t} else if err == nil && test.expectedErrStr != \"\" {\n\t\t\t\tt.Fatalf(\"expected error '%s' but got none\", test.expectedErrStr)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(test.expected, extraHosts) {\n\t\t\t\tt.Fatalf(\"expected %v, actual %v\", test.expected, extraHosts)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/containerutil/cp_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/tarutil\"\n)\n\n// See https://docs.docker.com/engine/reference/commandline/cp/ for the specification.\n\nvar (\n\t// Generic and system errors\n\tErrFilesystem             = errors.New(\"filesystem error\") // lstat hard errors, etc\n\tErrContainerVanished      = errors.New(\"the container you are trying to copy to/from has been deleted\")\n\tErrRootlessCannotCp       = errors.New(\"cannot use cp with stopped containers in rootless mode\") // rootless cp with a stopped container\n\tErrFailedMountingSnapshot = errors.New(\"failed mounting snapshot\")                               // failure to mount a stopped container snapshot\n\n\t// CP specific errors\n\tErrTargetIsReadOnly           = errors.New(\"cannot copy into read-only location\")                            // ...\n\tErrSourceIsNotADir            = errors.New(\"source is not a directory\")                                      // cp SOMEFILE/ foo:/\n\tErrDestinationIsNotADir       = errors.New(\"destination is not a directory\")                                 // * cp ./ foo:/etc/issue/bah\n\tErrSourceDoesNotExist         = errors.New(\"source does not exist\")                                          // cp NONEXISTENT foo:/\n\tErrDestinationParentMustExist = errors.New(\"destination parent does not exist\")                              // nerdctl cp VALID_PATH foo:/NONEXISTENT/NONEXISTENT\n\tErrDestinationDirMustExist    = errors.New(\"the destination directory must exist to be able to copy a file\") // * cp SOMEFILE foo:/NONEXISTENT/\n\tErrCannotCopyDirToFile        = errors.New(\"cannot copy a directory to a file\")                              // cp SOMEDIR foo:/etc/issue\n)\n\n// getRoot will tentatively return the root of the container on the host (/proc/pid/root), along with the pid,\n// (eg: doable when the container is running)\nfunc getRoot(ctx context.Context, container containerd.Container) (string, int, error) {\n\ttask, err := container.Task(ctx, nil)\n\tif err != nil {\n\t\treturn \"\", 0, err\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn \"\", 0, err\n\t}\n\n\tif status.Status != containerd.Running {\n\t\treturn \"\", 0, nil\n\t}\n\tpid := int(task.Pid())\n\n\treturn fmt.Sprintf(\"/proc/%d/root\", pid), pid, nil\n}\n\n// CopyFiles implements `nerdctl cp`\n// It currently depends on the following assumptions:\n// - linux only\n// - tar binary exists on the system\n// - nsenter binary exists on the system\n// - if rootless, the container is running (aka: /proc/pid/root)\nfunc CopyFiles(ctx context.Context, client *containerd.Client, container containerd.Container, options types.ContainerCpOptions) (err error) {\n\t// We do rely on the tar binary as a shortcut - could also be replaced by archive/tar, though that would mean\n\t// we need to replace nsenter calls with re-exec\n\ttarBinary, isGNUTar, err := tarutil.FindTarBinary()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlog.G(ctx).Debugf(\"Detected tar binary %q (GNU=%v)\", tarBinary, isGNUTar)\n\n\t// This can happen if the container being passed has been deleted since in a racy way\n\tconSpec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn errors.Join(ErrContainerVanished, err)\n\t}\n\n\t// Try to get a running container root\n\troot, pid, err := getRoot(ctx, container)\n\t// If the task is \"not found\" (for example, if the container stopped), we will try to mount the snapshot\n\t// Any other type of error from Task() is fatal here.\n\tif err != nil && !errdefs.IsNotFound(err) {\n\t\treturn errors.Join(ErrContainerVanished, err)\n\t}\n\n\tlog.G(ctx).Debugf(\"We have root %s and pid %d\", root, pid)\n\n\t// If we have no root:\n\t// - bail out for rootless\n\t// - mount the snapshot for rootful\n\tif root == \"\" {\n\t\t// FIXME: Rootless does not support copying into/out of stopped/created containers as we need to nsenter into\n\t\t// the user namespace of the pid of the running container with --preserve-credentials to preserve uid/gid\n\t\t// mapping and copy files into the container.\n\t\tif rootlessutil.IsRootless() {\n\t\t\treturn ErrRootlessCannotCp\n\t\t}\n\n\t\t// See similar situation above. This may happen if we are racing against container deletion\n\t\tvar conInfo containers.Container\n\t\tconInfo, err = container.Info(ctx)\n\t\tif err != nil {\n\t\t\treturn errors.Join(ErrContainerVanished, err)\n\t\t}\n\n\t\tvar cleanup func() error\n\t\troot, cleanup, err = mountSnapshotForContainer(ctx, client, conInfo, options.GOptions.Snapshotter)\n\t\tif cleanup != nil {\n\t\t\tdefer func() {\n\t\t\t\terr = errors.Join(err, cleanup())\n\t\t\t}()\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn errors.Join(ErrFailedMountingSnapshot, err)\n\t\t}\n\n\t\tlog.G(ctx).Debugf(\"Got new root %s\", root)\n\t}\n\n\tvar sourceSpec, destinationSpec *pathSpecifier\n\tvar sourceErr, destErr error\n\tif options.Container2Host {\n\t\tsourceSpec, sourceErr = getPathSpecFromContainer(options.SrcPath, conSpec, root)\n\t\tif options.ToStdout {\n\t\t\tdestinationSpec = &pathSpecifier{\n\t\t\t\texists:   true,\n\t\t\t\tisADir:   true,\n\t\t\t\ttoStdout: true,\n\t\t\t}\n\t\t} else {\n\t\t\tdestinationSpec, destErr = getPathSpecFromHost(options.DestPath)\n\t\t}\n\t} else {\n\t\tif options.FromStdin {\n\t\t\tsourceSpec = &pathSpecifier{\n\t\t\t\texists:    true,\n\t\t\t\tisADir:    true,\n\t\t\t\tfromStdin: true,\n\t\t\t}\n\t\t} else {\n\t\t\tsourceSpec, sourceErr = getPathSpecFromHost(options.SrcPath)\n\t\t}\n\t\tdestinationSpec, destErr = getPathSpecFromContainer(options.DestPath, conSpec, root)\n\t}\n\n\tif destErr != nil {\n\t\tif errors.Is(destErr, errDoesNotExist) {\n\t\t\treturn ErrDestinationParentMustExist\n\t\t} else if errors.Is(destErr, errIsNotADir) {\n\t\t\treturn ErrDestinationIsNotADir\n\t\t}\n\n\t\treturn errors.Join(ErrFilesystem, destErr)\n\t}\n\n\tif sourceErr != nil {\n\t\tif errors.Is(sourceErr, errDoesNotExist) {\n\t\t\treturn ErrSourceDoesNotExist\n\t\t} else if errors.Is(sourceErr, errIsNotADir) {\n\t\t\treturn ErrSourceIsNotADir\n\t\t}\n\n\t\treturn errors.Join(ErrFilesystem, sourceErr)\n\t}\n\n\t// Now, resolve cp shenanigans\n\t// First, cannot copy a non-existent resource\n\tif !sourceSpec.exists {\n\t\treturn ErrSourceDoesNotExist\n\t}\n\n\t// Second, cannot copy into a readonly destination\n\tif destinationSpec.readOnly {\n\t\treturn ErrTargetIsReadOnly\n\t}\n\n\t// Cannot copy a dir into a file\n\tif sourceSpec.isADir && destinationSpec.exists && !destinationSpec.isADir {\n\t\treturn ErrCannotCopyDirToFile\n\t}\n\n\t// A file cannot be copied inside a non-existent directory with a trailing slash, or slash+dot\n\tif !sourceSpec.isADir && !destinationSpec.exists && (destinationSpec.endsWithSeparator || destinationSpec.endsWithSeparatorDot) {\n\t\treturn ErrDestinationDirMustExist\n\t}\n\n\t// XXX FIXME: this seems wrong. What about ownership? We could be doing that inside a container\n\tif !destinationSpec.exists {\n\t\tif err = os.Mkdir(destinationSpec.resolvedPath, 0o755); err != nil {\n\t\t\treturn errors.Join(ErrFilesystem, err)\n\t\t}\n\t}\n\n\tvar tarCDir, tarCArg string\n\tif sourceSpec.isADir {\n\t\tif !destinationSpec.exists || sourceSpec.endsWithSeparatorDot {\n\t\t\t// the content of the source directory is copied into this directory\n\t\t\ttarCDir = sourceSpec.resolvedPath\n\t\t\ttarCArg = \".\"\n\t\t} else {\n\t\t\t// the source directory is copied into this directory\n\t\t\ttarCDir = filepath.Dir(sourceSpec.resolvedPath)\n\t\t\ttarCArg = filepath.Base(sourceSpec.resolvedPath)\n\t\t}\n\t} else if !sourceSpec.fromStdin {\n\t\t// Prepare a single-file directory to create an archive of the source file\n\t\ttd, err := os.MkdirTemp(\"\", \"nerdctl-cp\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer os.RemoveAll(td)\n\t\ttarCDir = td\n\t\tcp := []string{\"cp\", \"-a\"}\n\t\tif options.FollowSymLink {\n\t\t\tcp = append(cp, \"-L\")\n\t\t}\n\t\tif destinationSpec.toStdout || destinationSpec.endsWithSeparator || (destinationSpec.exists && destinationSpec.isADir) {\n\t\t\ttarCArg = filepath.Base(sourceSpec.resolvedPath)\n\t\t} else {\n\t\t\t// Handle `nerdctl cp /path/to/file some-container:/path/to/file-with-another-name`\n\t\t\ttarCArg = filepath.Base(destinationSpec.resolvedPath)\n\t\t}\n\t\tcp = append(cp, sourceSpec.resolvedPath, filepath.Join(td, tarCArg))\n\t\tcpCmd := exec.CommandContext(ctx, cp[0], cp[1:]...)\n\t\tlog.G(ctx).Debugf(\"executing %v\", cpCmd.Args)\n\t\tif out, err := cpCmd.CombinedOutput(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to execute %v: %w (out=%q)\", cpCmd.Args, err, string(out))\n\t\t}\n\t}\n\tvar tarC []string\n\tif sourceSpec.fromStdin {\n\t\ttarC = []string{\"echo\", \"reading tar from stdin\"}\n\t} else {\n\t\ttarC = []string{tarBinary}\n\t\tif options.FollowSymLink {\n\t\t\ttarC = append(tarC, \"-h\")\n\t\t}\n\t\ttarC = append(tarC, \"-c\", \"-f\", \"-\", tarCArg)\n\t}\n\n\ttarXDir := destinationSpec.resolvedPath\n\tif !sourceSpec.isADir && !destinationSpec.endsWithSeparator && !(destinationSpec.exists && destinationSpec.isADir) {\n\t\ttarXDir = filepath.Dir(destinationSpec.resolvedPath)\n\t}\n\tvar tarX []string\n\tif destinationSpec.toStdout {\n\t\ttarX = []string{\"echo\", \"writing tar to stdout\"}\n\t} else {\n\t\ttarX = []string{tarBinary, \"-x\"}\n\t\tif options.Container2Host && isGNUTar {\n\t\t\ttarX = append(tarX, \"--no-same-owner\")\n\t\t}\n\t\ttarX = append(tarX, \"-f\", \"-\")\n\t}\n\n\tif rootlessutil.IsRootless() {\n\t\tnsenter := []string{\"nsenter\", \"-t\", strconv.Itoa(pid), \"-U\", \"--preserve-credentials\", \"--\"}\n\t\tif options.Container2Host {\n\t\t\ttarC = append(nsenter, tarC...)\n\t\t} else {\n\t\t\ttarX = append(nsenter, tarX...)\n\t\t}\n\t}\n\n\t// FIXME: moving to archive/tar should allow better error management than this\n\t// WARNING: some of our testing on stderr might not be portable across different versions of tar\n\t// In these cases (readonly target), we will just get the straight tar output instead\n\ttarCCmd := exec.CommandContext(ctx, tarC[0], tarC[1:]...)\n\ttarCCmd.Dir = tarCDir\n\ttarCCmd.Stdin = nil\n\ttarCCmd.Stderr = os.Stderr\n\n\ttarXCmd := exec.CommandContext(ctx, tarX[0], tarX[1:]...)\n\ttarXCmd.Dir = tarXDir\n\tif sourceSpec.fromStdin {\n\t\t// Reading from tar should pipe stdin into dst\n\t\ttarXCmd.Stdin = bufio.NewReader(os.Stdin)\n\t\ttarXCmd.Stdout = os.Stderr\n\t} else if destinationSpec.toStdout {\n\t\t// Writing to tar should just write output to stdout. (Really we don't even need tarXCmd for this case.)\n\t\ttarXCmd.Stdin = nil\n\t\ttarXCmd.Stdout = nil\n\t\ttarCCmd.Stdout = os.Stdout\n\t} else {\n\t\ttarXCmd.Stdin, err = tarCCmd.StdoutPipe()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttarXCmd.Stdout = tarCCmd.Stderr\n\t}\n\n\tvar tarErr bytes.Buffer\n\ttarXCmd.Stderr = &tarErr\n\n\tlog.G(ctx).Debugf(\"executing %v in %q\", tarCCmd.Args, tarCCmd.Dir)\n\tif err := tarCCmd.Start(); err != nil {\n\t\treturn errors.Join(fmt.Errorf(\"failed to execute %v\", tarCCmd.Args), err)\n\t}\n\n\tlog.G(ctx).Debugf(\"executing %v in %q\", tarXCmd.Args, tarXCmd.Dir)\n\tif err := tarXCmd.Start(); err != nil {\n\t\tif strings.Contains(err.Error(), \"permission denied\") {\n\t\t\treturn ErrTargetIsReadOnly\n\t\t}\n\n\t\t// Other errors, just put them back on stderr\n\t\t_, fpErr := fmt.Fprint(os.Stderr, tarErr.String())\n\t\tif fpErr != nil {\n\t\t\treturn errors.Join(fpErr, err)\n\t\t}\n\n\t\treturn errors.Join(fmt.Errorf(\"failed to execute %v\", tarXCmd.Args), err)\n\t}\n\n\tif err := tarCCmd.Wait(); err != nil {\n\t\treturn fmt.Errorf(\"failed to wait %v: %w\", tarCCmd.Args, err)\n\t}\n\n\tif err := tarXCmd.Wait(); err != nil {\n\t\tif strings.Contains(tarErr.String(), \"Read-only file system\") {\n\t\t\treturn ErrTargetIsReadOnly\n\t\t}\n\n\t\t// Other errors, just put them back on stderr\n\t\t_, fpErr := fmt.Fprint(os.Stderr, tarErr.String())\n\t\tif fpErr != nil {\n\t\t\treturn errors.Join(fpErr, err)\n\t\t}\n\n\t\treturn errors.Join(fmt.Errorf(\"failed to wait %v\", tarXCmd.Args), err)\n\t}\n\n\treturn nil\n}\n\nfunc mountSnapshotForContainer(ctx context.Context, client *containerd.Client, conInfo containers.Container, snapshotter string) (string, func() error, error) {\n\tsnapKey := conInfo.SnapshotKey\n\tresp, err := client.SnapshotService(snapshotter).Mounts(ctx, snapKey)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\ttempDir, err := os.MkdirTemp(\"\", \"nerdctl-cp-\")\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\terr = mount.All(resp, tempDir)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tcleanup := func() error {\n\t\terr = mount.Unmount(tempDir, 0)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn os.RemoveAll(tempDir)\n\t}\n\n\treturn tempDir, cleanup, nil\n}\n"
  },
  {
    "path": "pkg/containerutil/cp_resolve_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n)\n\n// volumeNameLen returns length of the leading volume name on Windows.\n// It returns 0 elsewhere.\n// FIXME: whenever we will want to port cp to windows, we will need the windows implementation of volumeNameLen\nfunc volumeNameLen(_ string) int {\n\treturn 0\n}\n\nvar (\n\terrDoesNotExist           = errors.New(\"resource does not exist\")                                            // when a path parent dir does not exist\n\terrIsNotADir              = errors.New(\"is not a dir\")                                                       // when a path is a file, ending with path separator\n\terrCannotResolvePathNoCwd = errors.New(\"unable to resolve path against undefined current working directory\") // relative host path, no cwd\n)\n\n// pathSpecifier represents a path to be used by cp\n// besides exposing relevant properties (endsWithSeparator, etc), it also provides a fully resolved *host* path to\n// access the resource\ntype pathSpecifier struct {\n\toriginalPath string\n\tresolvedPath string\n\n\tendsWithSeparator    bool\n\tendsWithSeparatorDot bool\n\texists               bool\n\tisADir               bool\n\treadOnly             bool\n\tfromStdin            bool\n\ttoStdout             bool\n}\n\n// getPathSpecFromHost builds a pathSpecifier from a host location\n// errors with errDoesNotExist, errIsNotADir, \"EvalSymlinks: too many links\", or other hard filesystem errors from lstat/stat\nfunc getPathSpecFromHost(originalPath string) (*pathSpecifier, error) {\n\tpathSpec := &pathSpecifier{\n\t\toriginalPath:         originalPath,\n\t\tendsWithSeparator:    strings.HasSuffix(originalPath, string(os.PathSeparator)),\n\t\tendsWithSeparatorDot: filepath.Base(originalPath) == \".\",\n\t}\n\n\tpath := originalPath\n\n\t// Path may still be relative at this point. If it is, figure out getwd.\n\tif !filepath.IsAbs(path) {\n\t\tcwd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn nil, errors.Join(errCannotResolvePathNoCwd, err)\n\t\t}\n\t\tpath = cwd + string(os.PathSeparator) + path\n\t}\n\n\t// Try to fully resolve the path\n\tresolvedPath, err := filepath.EvalSymlinks(path)\n\tif err != nil && !errors.Is(err, os.ErrNotExist) {\n\t\tif errors.Is(err, syscall.ENOTDIR) {\n\t\t\treturn nil, errors.Join(errIsNotADir, err)\n\t\t}\n\n\t\t// Other errors:\n\t\t// - \"EvalSymlinks: too many links\"\n\t\t// - any other error coming from lstat\n\t\treturn nil, err\n\t}\n\n\tpathSpec.exists = err == nil\n\n\t// Ensure the parent exists if the path itself does not\n\tif !pathSpec.exists {\n\t\t// Try the parent - obtain it by removing any trailing / or /., then the base\n\t\tcleaned := strings.TrimRight(strings.TrimSuffix(path, string(os.PathSeparator)+\".\"), string(os.PathSeparator))\n\t\tfor len(cleaned) < len(path) {\n\t\t\tpath = cleaned\n\t\t\tcleaned = strings.TrimRight(strings.TrimSuffix(path, string(os.PathSeparator)+\".\"), string(os.PathSeparator))\n\t\t}\n\n\t\tbase := filepath.Base(path)\n\t\tpath = strings.TrimSuffix(path, string(os.PathSeparator)+base)\n\n\t\t// Resolve it\n\t\tresolvedPath, err = filepath.EvalSymlinks(path)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\treturn nil, errors.Join(errDoesNotExist, err)\n\t\t\t} else if errors.Is(err, syscall.ENOTDIR) {\n\t\t\t\treturn nil, errors.Join(errIsNotADir, err)\n\t\t\t}\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresolvedPath = filepath.Join(resolvedPath, base)\n\t} else {\n\t\t// If it exists, we can check if it is a dir\n\t\tvar st os.FileInfo\n\t\tst, err = os.Stat(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpathSpec.isADir = st.IsDir()\n\t}\n\n\tpathSpec.resolvedPath = resolvedPath\n\n\treturn pathSpec, nil\n}\n\n// getPathSpecFromContainer builds a pathSpecifier from a container location\nfunc getPathSpecFromContainer(originalPath string, conSpec *oci.Spec, containerHostRoot string) (*pathSpecifier, error) {\n\tpathSpec := &pathSpecifier{\n\t\toriginalPath:         originalPath,\n\t\tendsWithSeparator:    strings.HasSuffix(originalPath, string(os.PathSeparator)),\n\t\tendsWithSeparatorDot: filepath.Base(originalPath) == \".\",\n\t}\n\n\tpath := originalPath\n\n\t// Path may still be relative at this point. If it is, join it to the root\n\t// NOTE: this is specifically called out in the docker reference. Paths in the container are assumed\n\t// relative to the root, and not to the current (container) working directory.\n\t// Though this seems like a questionable decision, it is set.\n\tif !filepath.IsAbs(path) {\n\t\tpath = string(os.PathSeparator) + path\n\t}\n\n\t// Now, fully resolve the path - resolving all symlinks and cleaning-up the end result, following across mounts\n\tpathResolver := newResolver(conSpec, containerHostRoot)\n\tresolvedContainerPath, err := pathResolver.resolvePath(path)\n\n\t// Errors we get from that are from Lstat or Readlink\n\t// Either the object does not exist, or we have a dangling symlink, or otherwise hosed filesystem entries\n\tif err != nil && !errors.Is(err, os.ErrNotExist) {\n\t\tif errors.Is(err, syscall.ENOTDIR) {\n\t\t\treturn nil, errors.Join(errIsNotADir, err)\n\t\t}\n\n\t\t// errors.New(\"EvalSymlinks: too many links\")\n\t\t// other errors would come from lstat\n\t\treturn nil, err\n\t}\n\n\tpathSpec.exists = err == nil\n\n\t// If the resource does not exist\n\tif !pathSpec.exists {\n\t\t// Try the parent\n\t\tcleaned := strings.TrimRight(strings.TrimSuffix(path, string(os.PathSeparator)+\".\"), string(os.PathSeparator))\n\t\tfor len(cleaned) < len(path) {\n\t\t\tpath = cleaned\n\t\t\tcleaned = strings.TrimRight(strings.TrimSuffix(path, string(os.PathSeparator)+\".\"), string(os.PathSeparator))\n\t\t}\n\n\t\tbase := filepath.Base(path)\n\t\tpath = strings.TrimSuffix(path, string(os.PathSeparator)+base)\n\n\t\tresolvedContainerPath, err = pathResolver.resolvePath(path)\n\n\t\t// Error? That is the end\n\t\tif err != nil {\n\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\treturn nil, errors.Join(errDoesNotExist, err)\n\t\t\t} else if errors.Is(err, syscall.ENOTDIR) {\n\t\t\t\treturn nil, errors.Join(errIsNotADir, err)\n\t\t\t}\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresolvedContainerPath = filepath.Join(resolvedContainerPath, base)\n\t}\n\n\t// Now, finally get the location of the fully resolved containerPath (in the root? in a volume?)\n\tcontainerMount, relativePath := pathResolver.getMount(resolvedContainerPath)\n\tpathSpec.resolvedPath = filepath.Join(containerMount.hostPath, relativePath)\n\t// If the endpoint is readonly, flag it as such\n\tif containerMount.readonly {\n\t\tpathSpec.readOnly = true\n\t}\n\n\t// If it exists, we can check if it is a dir\n\tif pathSpec.exists {\n\t\tvar st os.FileInfo\n\t\tst, err = os.Stat(pathSpec.resolvedPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpathSpec.isADir = st.IsDir()\n\t}\n\n\treturn pathSpec, nil\n}\n\n// resolver provides methods to fully resolve any given container given path to a host location\n// accounting for rootfs and mounts location\ntype resolver struct {\n\troot     *specs.Root\n\tmounts   []specs.Mount\n\thostRoot string\n}\n\n// locator represents a container mount\ntype locator struct {\n\tcontainerPath string\n\thostPath      string\n\treadonly      bool\n}\n\nfunc isParent(child []string, candidate []string) (bool, []string) {\n\tif len(child) < len(candidate) {\n\t\treturn false, child\n\t}\n\treturn slices.Equal(child[0:len(candidate)], candidate), child[len(candidate):]\n}\n\n// newResolver returns a resolver struct\nfunc newResolver(conSpec *oci.Spec, hostRoot string) *resolver {\n\treturn &resolver{\n\t\troot:     conSpec.Root,\n\t\tmounts:   conSpec.Mounts,\n\t\thostRoot: hostRoot,\n\t}\n}\n\n// pathOnHost will return the *host* location of a container path, accounting for volumes.\n// The provided path must be fully resolved, as returned by `resolvePath`.\nfunc (res *resolver) pathOnHost(path string) string {\n\thostRoot := res.hostRoot\n\tpath = filepath.Clean(path)\n\titemized := strings.Split(path, string(os.PathSeparator))\n\n\tcontainerRoot := \"/\"\n\tsub := itemized\n\n\tfor _, mnt := range res.mounts {\n\t\tif candidateIsParent, subPath := isParent(itemized, strings.Split(mnt.Destination, string(os.PathSeparator))); candidateIsParent {\n\t\t\tif len(mnt.Destination) > len(containerRoot) {\n\t\t\t\tcontainerRoot = mnt.Destination\n\t\t\t\thostRoot = mnt.Source\n\t\t\t\tsub = subPath\n\t\t\t}\n\t\t}\n\t}\n\n\treturn filepath.Join(append([]string{hostRoot}, sub...)...)\n}\n\n// getMount returns the mount locator for a given fully-resolved path, along with the corresponding subpath of the path\n// relative to the locator\nfunc (res *resolver) getMount(path string) (*locator, string) {\n\titemized := strings.Split(path, string(os.PathSeparator))\n\n\tloc := &locator{\n\t\tcontainerPath: \"/\",\n\t\thostPath:      res.hostRoot,\n\t\treadonly:      res.root.Readonly,\n\t}\n\n\tsub := itemized\n\n\tfor _, mnt := range res.mounts {\n\t\tif candidateIsParent, subPath := isParent(itemized, strings.Split(mnt.Destination, string(os.PathSeparator))); candidateIsParent {\n\t\t\tif len(mnt.Destination) > len(loc.containerPath) {\n\t\t\t\tloc.readonly = false\n\t\t\t\tfor _, option := range mnt.Options {\n\t\t\t\t\tif option == \"ro\" {\n\t\t\t\t\t\tloc.readonly = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tloc.containerPath = mnt.Destination\n\t\t\t\tloc.hostPath = mnt.Source\n\t\t\t\tsub = subPath\n\t\t\t}\n\t\t}\n\t}\n\n\treturn loc, filepath.Join(sub...)\n}\n\n// resolvePath is adapted from https://cs.opensource.google/go/go/+/go1.23.0:src/path/filepath/path.go;l=147\n// The (only) changes are on Lstat and ReadLink, which are fed the actual host path, that is computed by `res.pathOnHost`\nfunc (res *resolver) resolvePath(path string) (string, error) {\n\tvolLen := volumeNameLen(path)\n\tpathSeparator := string(os.PathSeparator)\n\n\tif volLen < len(path) && os.IsPathSeparator(path[volLen]) {\n\t\tvolLen++\n\t}\n\tvol := path[:volLen]\n\tdest := vol\n\tlinksWalked := 0\n\t//nolint:ineffassign\n\tfor start, end := volLen, volLen; start < len(path); start = end {\n\t\tfor start < len(path) && os.IsPathSeparator(path[start]) {\n\t\t\tstart++\n\t\t}\n\t\tend = start\n\t\tfor end < len(path) && !os.IsPathSeparator(path[end]) {\n\t\t\tend++\n\t\t}\n\n\t\t// On Windows, \".\" can be a symlink.\n\t\t// We look it up, and use the value if it is absolute.\n\t\t// If not, we just return \".\".\n\t\t//nolint:staticcheck\n\t\tisWindowsDot := runtime.GOOS == \"windows\" && path[volumeNameLen(path):] == \".\"\n\n\t\t// The next path component is in path[start:end].\n\t\tif end == start {\n\t\t\t// No more path components.\n\t\t\tbreak\n\t\t} else if path[start:end] == \".\" && !isWindowsDot {\n\t\t\t// Ignore path component \".\".\n\t\t\tcontinue\n\t\t} else if path[start:end] == \"..\" {\n\t\t\t// Back up to previous component if possible.\n\t\t\t// Note that volLen includes any leading slash.\n\n\t\t\t// Set r to the index of the last slash in dest,\n\t\t\t// after the volume.\n\t\t\tvar r int\n\t\t\tfor r = len(dest) - 1; r >= volLen; r-- {\n\t\t\t\tif os.IsPathSeparator(dest[r]) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r < volLen || dest[r+1:] == \"..\" {\n\t\t\t\t// Either path has no slashes\n\t\t\t\t// (it's empty or just \"C:\")\n\t\t\t\t// or it ends in a \"..\" we had to keep.\n\t\t\t\t// Either way, keep this \"..\".\n\t\t\t\tif len(dest) > volLen {\n\t\t\t\t\tdest += pathSeparator\n\t\t\t\t}\n\t\t\t\tdest += \"..\"\n\t\t\t} else {\n\t\t\t\t// Discard everything since the last slash.\n\t\t\t\tdest = dest[:r]\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Ordinary path component. Add it to result.\n\n\t\tif len(dest) > volumeNameLen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {\n\t\t\tdest += pathSeparator\n\t\t}\n\n\t\tdest += path[start:end]\n\n\t\t// Resolve symlink.\n\t\thostPath := res.pathOnHost(dest)\n\t\tfi, err := os.Lstat(hostPath)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif fi.Mode()&fs.ModeSymlink == 0 {\n\t\t\tif !fi.Mode().IsDir() && end < len(path) {\n\t\t\t\treturn \"\", syscall.ENOTDIR\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Found symlink.\n\t\tlinksWalked++\n\t\tif linksWalked > 255 {\n\t\t\treturn \"\", errors.New(\"EvalSymlinks: too many links\")\n\t\t}\n\n\t\tlink, err := os.Readlink(hostPath)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif isWindowsDot && !filepath.IsAbs(link) {\n\t\t\t// On Windows, if \".\" is a relative symlink,\n\t\t\t// just return \".\".\n\t\t\tbreak\n\t\t}\n\n\t\tpath = link + path[end:]\n\n\t\tv := volumeNameLen(link)\n\t\tif v > 0 {\n\t\t\t// Symlink to drive name is an absolute path.\n\t\t\tif v < len(link) && os.IsPathSeparator(link[v]) {\n\t\t\t\tv++\n\t\t\t}\n\t\t\tvol = link[:v]\n\t\t\tdest = vol\n\t\t\tend = len(vol)\n\t\t} else if len(link) > 0 && os.IsPathSeparator(link[0]) {\n\t\t\t// Symlink to absolute path.\n\t\t\tdest = link[:1]\n\t\t\tend = 1\n\t\t\tvol = link[:1]\n\t\t\tvolLen = 1\n\t\t} else {\n\t\t\t// Symlink to relative path; replace last\n\t\t\t// path component in dest.\n\t\t\tvar r int\n\t\t\tfor r = len(dest) - 1; r >= volLen; r-- {\n\t\t\t\tif os.IsPathSeparator(dest[r]) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif r < volLen {\n\t\t\t\tdest = vol\n\t\t\t} else {\n\t\t\t\tdest = dest[:r]\n\t\t\t}\n\t\t\tend = 0\n\t\t}\n\t}\n\treturn filepath.Clean(dest), nil\n}\n"
  },
  {
    "path": "pkg/containerutil/lock.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerutil\n\nimport (\n\t\"path/filepath\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nfunc Lock(stateDir string) (store.Store, error) {\n\tstor, err := store.New(filepath.Join(stateDir, \"oplock\"), 0, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = stor.Lock()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stor, nil\n}\n"
  },
  {
    "path": "pkg/defaults/cgroup_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage defaults\n\nimport (\n\t\"os\"\n\n\t\"github.com/containerd/cgroups/v3\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc IsSystemdAvailable() bool {\n\tfi, err := os.Lstat(\"/run/systemd/system\")\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn fi.IsDir()\n}\n\n// CgroupManager defaults to:\n// - \"systemd\"  on v2 (rootful & rootless)\n// - \"cgroupfs\" on v1 rootful\n// - \"none\"     on v1 rootless\nfunc CgroupManager() string {\n\tif cgroups.Mode() == cgroups.Unified && IsSystemdAvailable() {\n\t\treturn \"systemd\"\n\t}\n\tif rootlessutil.IsRootless() {\n\t\treturn \"none\"\n\t}\n\treturn \"cgroupfs\"\n}\n\nfunc CgroupnsMode() string {\n\tif cgroups.Mode() == cgroups.Unified {\n\t\treturn \"private\"\n\t}\n\treturn \"host\"\n}\n"
  },
  {
    "path": "pkg/defaults/defaults_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// This is a dummy file to allow usage of library functions\n// on Darwin-based systems.\n// All functions and variables are empty/no-ops\n\npackage defaults\n\nimport gocni \"github.com/containerd/go-cni\"\n\nconst (\n\tAppArmorProfileName = \"\"\n\tSeccompProfileName  = \"\"\n\tRuntime             = \"\"\n)\n\nfunc CNIPath() string {\n\treturn gocni.DefaultCNIDir\n}\n\nfunc CNIRuntimeDir() (string, error) {\n\treturn \"/var/run/cni\", nil\n}\n\nfunc CNINetConfPath() string {\n\treturn gocni.DefaultNetDir\n}\n\nfunc DataRoot() string {\n\treturn \"/var/lib/nerdctl\"\n}\n\nfunc CgroupManager() string {\n\treturn \"\"\n}\n\nfunc CgroupnsMode() string {\n\treturn \"\"\n}\n\nfunc NerdctlTOML() string {\n\treturn \"/etc/nerdctl/nerdctl.toml\"\n}\n\nfunc HostsDirs() []string {\n\treturn []string{}\n}\n\nfunc HostGatewayIP() string {\n\treturn \"\"\n}\n\nfunc CDISpecDirs() []string {\n\treturn []string{\"/etc/cdi\", \"/var/run/cdi\"}\n}\n"
  },
  {
    "path": "pkg/defaults/defaults_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage defaults\n\nimport (\n\t\"github.com/containerd/go-cni\"\n)\n\nconst (\n\tAppArmorProfileName = \"\"\n\tSeccompProfileName  = \"\"\n\tRuntime             = \"wtf.sbk.runj.v1\"\n)\n\nfunc DataRoot() string {\n\treturn \"/var/lib/nerdctl\"\n}\n\nfunc CNIPath() string {\n\t// default: /opt/cni/bin\n\treturn cni.DefaultCNIDir\n}\n\nfunc CNINetConfPath() string {\n\treturn cni.DefaultNetDir\n}\n\nfunc CNIRuntimeDir() (string, error) {\n\treturn \"/var/run/cni\", nil\n}\n\nfunc CgroupManager() string {\n\treturn \"\"\n}\n\nfunc CgroupnsMode() string {\n\treturn \"\"\n}\n\nfunc NerdctlTOML() string {\n\treturn \"/etc/nerdctl/nerdctl.toml\"\n}\n\nfunc HostsDirs() []string {\n\treturn []string{\"/etc/containerd/certs.d\", \"/etc/docker/certs.d\"}\n}\n\nfunc HostGatewayIP() string {\n\treturn \"\"\n}\n\nfunc CDISpecDirs() []string {\n\treturn []string{\"/etc/cdi\", \"/var/run/cdi\"}\n}\n"
  },
  {
    "path": "pkg/defaults/defaults_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage defaults\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/containerd/containerd/v2/plugins\"\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nconst (\n\tAppArmorProfileName = \"nerdctl-default\"\n\tSeccompProfileName  = \"builtin\"\n\tRuntime             = plugins.RuntimeRuncV2\n)\n\nfunc DataRoot() string {\n\tif !rootlessutil.IsRootless() {\n\t\treturn \"/var/lib/nerdctl\"\n\t}\n\txdh, err := rootlessutil.XDGDataHome()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn filepath.Join(xdh, \"nerdctl\")\n}\n\nfunc CNIPath() string {\n\tcandidates := []string{\n\t\tcni.DefaultCNIDir, // /opt/cni/bin\n\t\t\"/usr/local/libexec/cni\",\n\t\t\"/usr/local/lib/cni\",\n\t\t\"/home/linuxbrew/.linuxbrew/opt/cni-plugins/bin\", // Homebrew\n\t\t\"/usr/libexec/cni\",                               // Fedora\n\t\t\"/usr/lib/cni\",                                   // debian (containernetworking-plugins)\n\t}\n\tif rootlessutil.IsRootless() {\n\t\thome := os.Getenv(\"HOME\")\n\t\tif home == \"\" {\n\t\t\tpanic(\"environment variable HOME is not set\")\n\t\t}\n\t\tcandidates = append([]string{\n\t\t\t// NOTE: These user paths are not defined in XDG\n\t\t\tfilepath.Join(home, \"opt/cni/bin\"),\n\t\t\tfilepath.Join(home, \".local/libexec/cni\"),\n\t\t\tfilepath.Join(home, \".local/lib/cni\"),\n\t\t}, candidates...)\n\t}\n\n\tfor _, f := range candidates {\n\t\tif _, err := os.Stat(f); err == nil {\n\t\t\treturn f\n\t\t}\n\t}\n\n\t// default: /opt/cni/bin\n\treturn cni.DefaultCNIDir\n}\n\nfunc CNINetConfPath() string {\n\tif !rootlessutil.IsRootless() {\n\t\treturn cni.DefaultNetDir\n\t}\n\txch, err := rootlessutil.XDGConfigHome()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn filepath.Join(xch, \"cni/net.d\")\n}\n\nfunc CNIRuntimeDir() (string, error) {\n\tif !rootlessutil.IsRootless() {\n\t\treturn \"/run/cni\", nil\n\t}\n\txdr, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\tif rootlessutil.IsRootlessChild() {\n\t\t\treturn \"\", err\n\t\t}\n\t\txdr = fmt.Sprintf(\"/run/user/%d\", os.Geteuid())\n\t}\n\treturn filepath.Join(xdr, \"cni\"), nil\n}\n\nfunc NerdctlTOML() string {\n\tif !rootlessutil.IsRootless() {\n\t\treturn \"/etc/nerdctl/nerdctl.toml\"\n\t}\n\txch, err := rootlessutil.XDGConfigHome()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn filepath.Join(xch, \"nerdctl/nerdctl.toml\")\n}\n\nfunc HostsDirs() []string {\n\tif !rootlessutil.IsRootless() {\n\t\treturn []string{\"/etc/containerd/certs.d\", \"/etc/docker/certs.d\"}\n\t}\n\txch, err := rootlessutil.XDGConfigHome()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn []string{\n\t\tfilepath.Join(xch, \"containerd/certs.d\"),\n\t\tfilepath.Join(xch, \"docker/certs.d\"),\n\t}\n}\n\n// HostGatewayIP returns the non-loop-back host ip if available and returns empty string if running into error.\nfunc HostGatewayIP() string {\n\t// no need to use [rootlessutil.WithDetachedNetNSIfAny] here\n\taddrs, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tfor _, addr := range addrs {\n\t\tif ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {\n\t\t\tif ipnet.IP.To4() != nil {\n\t\t\t\treturn ipnet.IP.String()\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc CDISpecDirs() []string {\n\tif !rootlessutil.IsRootless() {\n\t\treturn []string{\"/etc/cdi\", \"/var/run/cdi\"}\n\t}\n\txch, err := rootlessutil.XDGConfigHome()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\txdr, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\tif rootlessutil.IsRootlessChild() {\n\t\t\tpanic(err)\n\t\t}\n\t\txdr = fmt.Sprintf(\"/run/user/%d\", os.Geteuid())\n\t}\n\treturn []string{\n\t\tfilepath.Join(xch, \"cdi\"),\n\t\tfilepath.Join(xdr, \"cdi\"),\n\t}\n}\n"
  },
  {
    "path": "pkg/defaults/defaults_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage defaults\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tAppArmorProfileName = \"\"\n\tSeccompProfileName  = \"\"\n\tRuntime             = \"io.containerd.runhcs.v1\"\n)\n\nfunc DataRoot() string {\n\treturn filepath.Join(os.Getenv(\"ProgramData\"), \"nerdctl\")\n}\n\nfunc CNIPath() string {\n\treturn filepath.Join(os.Getenv(\"ProgramFiles\"), \"containerd\", \"cni\", \"bin\")\n}\n\nfunc CNINetConfPath() string {\n\treturn filepath.Join(os.Getenv(\"ProgramFiles\"), \"containerd\", \"cni\", \"conf\")\n}\n\nfunc CNIRuntimeDir() (string, error) {\n\treturn \"\", nil\n}\n\nfunc IsSystemdAvailable() bool {\n\treturn false\n}\n\nfunc CgroupManager() string {\n\treturn \"\"\n}\n\nfunc CgroupnsMode() string {\n\treturn \"\"\n}\n\nfunc NerdctlTOML() string {\n\tucd, err := os.UserConfigDir()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn filepath.Join(ucd, \"nerdctl\\\\nerdctl.toml\")\n}\n\nfunc HostsDirs() []string {\n\tprogramData := os.Getenv(\"ProgramData\")\n\tif programData == \"\" {\n\t\tpanic(\"%ProgramData% needs to be set\")\n\t}\n\treturn []string{\n\t\tfilepath.Join(programData, \"containerd\\\\certs.d\"),\n\t\tfilepath.Join(programData, \"docker\\\\certs.d\"),\n\t}\n}\n\nfunc HostGatewayIP() string {\n\treturn \"\"\n}\n\nfunc CDISpecDirs() []string {\n\treturn []string{}\n}\n"
  },
  {
    "path": "pkg/dnsutil/dnsutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dnsutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc GetSlirp4netnsDNS() ([]string, error) {\n\tvar dns []string\n\trkClient, err := rootlessutil.NewRootlessKitClient()\n\tif err != nil {\n\t\treturn dns, err\n\t}\n\tinfo, err := rkClient.Info(context.TODO())\n\tif err != nil {\n\t\treturn dns, err\n\t}\n\tif info != nil && info.NetworkDriver != nil {\n\t\tfor _, dnsIP := range info.NetworkDriver.DNS {\n\t\t\tdns = append(dns, dnsIP.String())\n\t\t}\n\t}\n\treturn dns, nil\n}\n\n// ValidateIPAddress validates if the given value is a correctly formatted\n// IP address, and returns the value in normalized form. Leading and trailing\n// whitespace is allowed, but it does not allow IPv6 addresses surrounded by\n// square brackets (\"[::1]\"). Refer to [net.ParseIP] for accepted formats.\nfunc ValidateIPAddress(val string) (string, error) {\n\tif ip := net.ParseIP(strings.TrimSpace(val)); ip != nil {\n\t\treturn ip.String(), nil\n\t}\n\treturn \"\", fmt.Errorf(\"ip address is not correctly formatted: %q\", val)\n}\n"
  },
  {
    "path": "pkg/dnsutil/dnsutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dnsutil\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestValidateIPAddress(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tinput       string\n\t\texpectedOut string\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname:        \"IPv4 loopback\",\n\t\t\tinput:       `127.0.0.1`,\n\t\t\texpectedOut: `127.0.0.1`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv4 loopback with whitespace\",\n\t\t\tinput:       ` 127.0.0.1 `,\n\t\t\texpectedOut: `127.0.0.1`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv6 loopback long form\",\n\t\t\tinput:       `0:0:0:0:0:0:0:1`,\n\t\t\texpectedOut: `::1`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv6 loopback\",\n\t\t\tinput:       `::1`,\n\t\t\texpectedOut: `::1`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv6 loopback with whitespace\",\n\t\t\tinput:       ` ::1 `,\n\t\t\texpectedOut: `::1`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv6 lowercase\",\n\t\t\tinput:       `2001:db8::68`,\n\t\t\texpectedOut: `2001:db8::68`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv6 uppercase\",\n\t\t\tinput:       `2001:DB8::68`,\n\t\t\texpectedOut: `2001:db8::68`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv6 with brackets\",\n\t\t\tinput:       `[::1]`,\n\t\t\texpectedErr: `ip address is not correctly formatted: \"[::1]\"`,\n\t\t},\n\t\t{\n\t\t\tname:        \"IPv4 partial\",\n\t\t\tinput:       `127`,\n\t\t\texpectedErr: `ip address is not correctly formatted: \"127\"`,\n\t\t},\n\t\t{\n\t\t\tname:        \"random invalid string\",\n\t\t\tinput:       `random invalid string`,\n\t\t\texpectedErr: `ip address is not correctly formatted: \"random invalid string\"`,\n\t\t},\n\t\t{\n\t\t\tname:        \"empty string\",\n\t\t\tinput:       ``,\n\t\t\texpectedErr: `ip address is not correctly formatted: \"\"`,\n\t\t},\n\t\t{\n\t\t\tname:        \"only whitespace\",\n\t\t\tinput:       `   `,\n\t\t\texpectedErr: `ip address is not correctly formatted: \"   \"`,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\tactualOut, actualErr := ValidateIPAddress(tc.input)\n\t\t\tassert.Equal(t, tc.expectedOut, actualOut)\n\t\t\tif tc.expectedErr == \"\" {\n\t\t\t\tassert.Check(t, actualErr)\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, tc.expectedErr, actualErr.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/dnsutil/hostsstore/hosts.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n  Portions from https://github.com/jaytaylor/go-hostsfile/blob/59e7508e09b9e08c57183ae15eabf1b757328ebf/hosts.go\n  Copyright (c) 2016 Jay Taylor [@jtaylor]\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n*/\n\npackage hostsstore\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\nconst (\n\tMarkerBegin = \"<nerdctl>\"\n\tMarkerEnd   = \"</nerdctl>\"\n)\n\n// parseHostsButSkipMarkedRegion parses hosts file content but skips the <nerdctl> </nerdctl> region\n// mimics  https://github.com/jaytaylor/go-hostsfile/blob/59e7508e09b9e08c57183ae15eabf1b757328ebf/hosts.go#L18\n// mimics  https://github.com/norouter/norouter/blob/v0.6.2/pkg/agent/etchosts/etchosts.go#L128-L152\nfunc parseHostsButSkipMarkedRegion(w io.Writer, r io.Reader) error {\n\tscanner := bufio.NewScanner(r)\n\tskip := false\nLINE:\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tline = strings.ReplaceAll(strings.Trim(line, \" \\t\"), \"\\t\", \" \")\n\t\tsawMarkerEnd := false\n\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\tcom := strings.TrimSpace(line[1:])\n\t\t\tswitch com {\n\t\t\tcase MarkerBegin:\n\t\t\t\tskip = true\n\t\t\tcase MarkerEnd:\n\t\t\t\tsawMarkerEnd = true\n\t\t\t}\n\t\t}\n\t\tif !skip {\n\t\t\tif len(line) == 0 || line[0] == ';' || line[0] == '#' {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpieces := strings.SplitN(line, \" \", 2)\n\t\t\tif len(pieces) > 1 && len(pieces[0]) > 0 {\n\t\t\t\tif pieces[0] == \"127.0.0.1\" || pieces[0] == \"::1\" {\n\t\t\t\t\tcontinue LINE\n\t\t\t\t}\n\t\t\t\tif names := strings.Fields(pieces[1]); len(names) > 0 {\n\t\t\t\t\tfor _, name := range names {\n\t\t\t\t\t\tif strings.HasPrefix(name, \"#\") {\n\t\t\t\t\t\t\tcontinue LINE\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfmt.Fprintln(w, line)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif sawMarkerEnd {\n\t\t\tskip = false\n\t\t}\n\t}\n\treturn scanner.Err()\n}\n"
  },
  {
    "path": "pkg/dnsutil/hostsstore/hosts_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage hostsstore\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestParseHostsButSkipMarkedRegion(t *testing.T) {\n\ttype testCase struct {\n\t\thostsFileContent string\n\t\twant             string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\thostsFileContent: `\n10.4.1.6        outOfMarkedRegion\n# <nerdctl>\n127.0.0.1       localhost localhost.localdomain\n::1             localhost localhost.localdomain\n10.4.1.5        35af3f0922a9 35af3f0922a9.etcd-0 alpine-35af3 alpine-35af3.etcd-0\n10.4.1.3        993208adcae8 993208adcae8.etcd-0 alpine-99320 alpine-99320.etcd-0\n# </nerdctl>\n`,\n\t\t\twant: `10.4.1.6        outOfMarkedRegion\n`,\n\t\t},\n\t\t{\n\t\t\thostsFileContent: `\n\t\t# <nerdctl>\n\t\t127.0.0.1       localhost localhost.localdomain\n\t\t::1             localhost localhost.localdomain\n\t\t10.4.1.5        35af3f0922a9 35af3f0922a9.etcd-0 alpine-35af3 alpine-35af3.etcd-0\n\t\t10.4.1.3        993208adcae8 993208adcae8.etcd-0 alpine-99320 alpine-99320.etcd-0\n\t\t# </nerdctl>\n\t\t`,\n\t\t\twant: \"\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tvar buf bytes.Buffer\n\t\tr := strings.NewReader(tc.hostsFileContent)\n\t\terr := parseHostsButSkipMarkedRegion(&buf, r)\n\t\tassert.NilError(t, err)\n\t\tassert.DeepEqual(t, buf.String(), tc.want)\n\n\t}\n}\n"
  },
  {
    "path": "pkg/dnsutil/hostsstore/hostsstore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package hostsstore provides the interface for /var/lib/nerdctl/<ADDRHASH>/etchosts\n// Prioritizes simplicity over scalability.\n// All methods perform atomic writes and are safe to use concurrently.\n// Note that locking is done per namespace.\n// hostsstore is currently by container rename, remove, network managers, and ocihooks\n// Finally, NOTE:\n// Since we will write to the hosts file after it is mounted in the container, we cannot use our atomic write method\n// as the inode would change on rename.\n// Henceforth, hosts file mutation uses filesystem methods instead, making it the one exception that has to bypass\n// the Store implementation.\npackage hostsstore\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\ttypes100 \"github.com/containernetworking/cni/pkg/types/100\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nconst (\n\t// hostsDirBasename is the base name of /var/lib/nerdctl/<ADDRHASH>/etchosts\n\thostsDirBasename = \"etchosts\"\n\t// metaJSON is stored as hostsDirBasename/<NS>/<ID>/meta.json\n\tmetaJSON = \"meta.json\"\n\t// hostsFile is stored as hostsDirBasename/<NS>/<ID>/hosts\n\thostsFile = \"hosts\"\n)\n\n// ErrHostsStore will wrap all errors here\nvar ErrHostsStore = errors.New(\"hosts-store error\")\n\nfunc New(dataStore string, namespace string) (retStore Store, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrHostsStore, err)\n\t\t}\n\t}()\n\n\tif dataStore == \"\" || namespace == \"\" {\n\t\treturn nil, store.ErrInvalidArgument\n\t}\n\n\tst, err := store.New(filepath.Join(dataStore, hostsDirBasename, namespace), 0, 0o600)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &hostsStore{\n\t\tsafeStore: st,\n\t}, nil\n}\n\ntype Meta struct {\n\tID         string\n\tNetworks   map[string]*types100.Result\n\tHostname   string\n\tExtraHosts map[string]string // host:ip\n\tName       string\n\tDomainname string\n}\n\ntype Store interface {\n\tAcquire(Meta) error\n\tRelease(id string) error\n\tUpdate(id, newName string) error\n\tHostsPath(id string) (location string, err error)\n\tDelete(id string) (err error)\n\tAllocHostsFile(id string, content []byte) (location string, err error)\n}\n\ntype hostsStore struct {\n\tsafeStore store.Store\n}\n\nfunc (x *hostsStore) Acquire(meta Meta) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrHostsStore, err)\n\t\t}\n\t}()\n\n\treturn x.safeStore.WithLock(func() error {\n\t\tvar loc string\n\t\tloc, err = x.safeStore.Location(meta.ID, hostsFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// See https://github.com/containerd/nerdctl/issues/3907\n\t\t// Because of the way we call network manager ContainerNetworkingOpts then SetupNetworking in sequence\n\t\t// we need to make sure we do not overwrite an already allocated hosts file.\n\t\tif _, err = os.Stat(loc); os.IsNotExist(err) {\n\t\t\tif err = filesystem.WriteFile(loc, []byte{}, 0o644); err != nil {\n\t\t\t\treturn errors.Join(store.ErrSystemFailure, err)\n\t\t\t}\n\n\t\t\t// WriteFile relies on syscall.Open. Unless there are ACLs, the effective mode of the file will be matched\n\t\t\t// against the current process umask.\n\t\t\t// See https://www.man7.org/linux/man-pages/man2/open.2.html for details.\n\t\t\t// Since we must make sure that these files are world readable, explicitly chmod them here.\n\t\t\tif err = os.Chmod(loc, 0o644); err != nil {\n\t\t\t\terr = errors.Join(store.ErrSystemFailure, err)\n\t\t\t}\n\t\t}\n\n\t\tvar content []byte\n\t\tcontent, err = json.Marshal(meta)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = x.safeStore.Set(content, meta.ID, metaJSON); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn x.updateAllHosts()\n\t})\n}\n\n// Release is triggered by Poststop hooks.\n// It is called after the containerd task is deleted but before the delete operation returns.\nfunc (x *hostsStore) Release(id string) (err error) {\n\t// We remove \"meta.json\" but we still retain the \"hosts\" file\n\t// because it is needed for restarting. The \"hosts\" is removed on\n\t// `nerdctl rm`.\n\t// https://github.com/rootless-containers/rootlesskit/issues/220#issuecomment-783224610\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrHostsStore, err)\n\t\t}\n\t}()\n\n\treturn x.safeStore.WithLock(func() error {\n\t\tif err = x.safeStore.Delete(id, metaJSON); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn x.updateAllHosts()\n\t})\n}\n\n// AllocHostsFile is used for creating mount-bindable /etc/hosts file.\nfunc (x *hostsStore) AllocHostsFile(id string, content []byte) (location string, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrHostsStore, err)\n\t\t}\n\t}()\n\n\terr = x.safeStore.WithLock(func() error {\n\t\terr = x.safeStore.GroupEnsure(id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar loc string\n\t\tloc, err = x.safeStore.Location(id, hostsFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = filesystem.WriteFile(loc, content, 0o644)\n\t\tif err != nil {\n\t\t\terr = errors.Join(store.ErrSystemFailure, err)\n\t\t}\n\n\t\t// WriteFile relies on syscall.Open. Unless there are ACLs, the effective mode of the file will be matched\n\t\t// against the current process umask.\n\t\t// See https://www.man7.org/linux/man-pages/man2/open.2.html for details.\n\t\t// Since we must make sure that these files are world readable, explicitly chmod them here.\n\t\tif err = os.Chmod(loc, 0o644); err != nil {\n\t\t\terr = errors.Join(store.ErrSystemFailure, err)\n\t\t}\n\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn x.safeStore.Location(id, hostsFile)\n}\n\nfunc (x *hostsStore) Delete(id string) (err error) {\n\terr = x.safeStore.WithLock(func() error { return x.safeStore.Delete(id) })\n\tif err != nil {\n\t\terr = errors.Join(ErrHostsStore, err)\n\t}\n\n\treturn err\n}\n\nfunc (x *hostsStore) HostsPath(id string) (location string, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrHostsStore, err)\n\t\t}\n\t}()\n\n\treturn x.safeStore.Location(id, hostsFile)\n}\n\nfunc (x *hostsStore) Update(id, newName string) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrHostsStore, err)\n\t\t}\n\t}()\n\n\treturn x.safeStore.WithLock(func() error {\n\t\tvar content []byte\n\t\tif content, err = x.safeStore.Get(id, metaJSON); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tmeta := &Meta{}\n\t\tif err = json.Unmarshal(content, meta); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tmeta.Name = newName\n\t\tcontent, err = json.Marshal(meta)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = x.safeStore.Set(content, id, metaJSON); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn x.updateAllHosts()\n\t})\n}\n\nfunc (x *hostsStore) updateAllHosts() (err error) {\n\tentries, err := x.safeStore.List()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmetasByEntry := map[string]*Meta{}\n\tmetasByIP := map[string]*Meta{}\n\tnetworkNameByIP := map[string]string{}\n\n\t// Phase 1: read all meta files\n\tfor _, entry := range entries {\n\t\tvar content []byte\n\t\tcontent, err = x.safeStore.Get(entry, metaJSON)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Debugf(\"unable to read %q\", entry)\n\t\t\tcontinue\n\t\t}\n\t\tmeta := &Meta{}\n\t\tif err = json.Unmarshal(content, meta); err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"unable to unmarshell %q\", entry)\n\t\t\tcontinue\n\t\t}\n\t\tmetasByEntry[entry] = meta\n\n\t\tfor netName, cniRes := range meta.Networks {\n\t\t\tfor _, ipCfg := range cniRes.IPs {\n\t\t\t\tif ip := ipCfg.Address.IP; ip != nil {\n\t\t\t\t\tif ip.IsLoopback() || ip.IsUnspecified() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tipStr := ip.String()\n\t\t\t\t\tmetasByIP[ipStr] = meta\n\t\t\t\t\tnetworkNameByIP[ipStr] = netName\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Phase 2: write hosts files\n\tfor _, entry := range entries {\n\t\tmyMeta, ok := metasByEntry[entry]\n\t\tif !ok {\n\t\t\tlog.L.WithError(errdefs.ErrNotFound).Debugf(\"hostsstore metadata %q not found in %q?\", metaJSON, entry)\n\t\t\tcontinue\n\t\t}\n\n\t\tmyNetworks := make(map[string]struct{})\n\t\tfor nwName := range myMeta.Networks {\n\t\t\tmyNetworks[nwName] = struct{}{}\n\t\t}\n\n\t\tvar content []byte\n\t\tcontent, err = x.safeStore.Get(entry, hostsFile)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"unable to retrieve the hosts file for %q\", entry)\n\t\t\tcontinue\n\t\t}\n\n\t\t// parse the hosts file, keep the original host record\n\t\t// retain custom /etc/hosts entries outside <nerdctl> </nerdctl> region\n\t\tvar buf bytes.Buffer\n\t\tif content != nil {\n\t\t\tif err = parseHostsButSkipMarkedRegion(&buf, bytes.NewReader(content)); err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed to read hosts file for %q\", entry)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tbuf.WriteString(fmt.Sprintf(\"# %s\\n\", MarkerBegin))\n\t\tbuf.WriteString(\"127.0.0.1\tlocalhost localhost.localdomain\\n\")\n\t\tbuf.WriteString(\"::1\t\tlocalhost localhost.localdomain\\n\")\n\n\t\t// keep extra hosts first\n\t\tfor host, ip := range myMeta.ExtraHosts {\n\t\t\tbuf.WriteString(fmt.Sprintf(\"%-15s %s\\n\", ip, host))\n\t\t}\n\n\t\tfor ip, netName := range networkNameByIP {\n\t\t\tmeta := metasByIP[ip]\n\t\t\tif line := createLine(netName, meta, myNetworks); len(line) != 0 {\n\t\t\t\tbuf.WriteString(fmt.Sprintf(\"%-15s %s\\n\", ip, strings.Join(line, \" \")))\n\t\t\t}\n\t\t}\n\n\t\tbuf.WriteString(fmt.Sprintf(\"# %s\\n\", MarkerEnd))\n\n\t\tvar loc string\n\t\tloc, err = x.safeStore.Location(entry, hostsFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = filesystem.WriteFile(loc, buf.Bytes(), 0o644)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"failed to write hosts file for %q\", entry)\n\t\t}\n\t\t_ = os.Chmod(loc, 0o644)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/dnsutil/hostsstore/updater.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage hostsstore\n\nimport (\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\n// createLine returns a line string slice.\n// line is like \"bar bar.nw0 foo foo.nw0\\n\"\n// for `nerdctl --name=foo --hostname=bar --network=nw0`.\n//\n// line is line \"bar.example.com bar bar.nw0 foo foo.nw0\\n\"\n// for  `nerdctl --name=foo --hostname=bar --domainname=example.com --network=n0`.\n//\n// May return an empty string slice\nfunc createLine(thatNetwork string, meta *Meta, myNetworks map[string]struct{}) []string {\n\tline := []string{}\n\tif _, ok := myNetworks[thatNetwork]; !ok {\n\t\t// Do not add lines for other networks\n\t\treturn line\n\t}\n\n\tif meta.Domainname != \"\" {\n\t\tline = append(line, meta.Hostname+\".\"+meta.Domainname)\n\t}\n\n\tbaseHostnames := []string{meta.Hostname}\n\n\tif meta.Name != \"\" {\n\t\tbaseHostnames = append(baseHostnames, meta.Name)\n\t}\n\n\tfor _, baseHostname := range baseHostnames {\n\t\tline = append(line, baseHostname)\n\t\tif thatNetwork != netutil.DefaultNetworkName {\n\t\t\t// Do not add a entry like \"foo.bridge\"\n\t\t\tline = append(line, baseHostname+\".\"+thatNetwork)\n\t\t}\n\t}\n\treturn line\n}\n"
  },
  {
    "path": "pkg/dnsutil/hostsstore/updater_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage hostsstore\n\nimport (\n\t\"net\"\n\t\"strings\"\n\t\"testing\"\n\n\ttypes100 \"github.com/containernetworking/cni/pkg/types/100\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n)\n\nfunc TestCreateLine(t *testing.T) {\n\ttype testCase struct {\n\t\tthatIP         string\n\t\tthatNetwork    string\n\t\tthatHostname   string // nerdctl run --hostname\n\t\tthatDomainname string // nerdctl run --domainname\n\t\tthatName       string // nerdctl run --name\n\t\tmyNetwork      string\n\t\texpected       string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tthatIP:       \"10.4.2.2\",\n\t\t\tthatNetwork:  \"n1\",\n\t\t\tthatHostname: \"bar\",\n\t\t\tthatName:     \"foo\",\n\t\t\tmyNetwork:    \"n1\",\n\t\t\texpected:     \"bar bar.n1 foo foo.n1\",\n\t\t},\n\t\t{\n\t\t\tthatIP:       \"10.4.2.3\",\n\t\t\tthatNetwork:  \"n1\",\n\t\t\tthatHostname: \"bar\",\n\t\t\tmyNetwork:    \"n1\",\n\t\t\texpected:     \"bar bar.n1\",\n\t\t},\n\t\t{\n\t\t\tthatIP:       \"10.4.2.4\",\n\t\t\tthatNetwork:  netutil.DefaultNetworkName,\n\t\t\tthatHostname: \"bar\",\n\t\t\tmyNetwork:    \"n1\",\n\t\t\texpected:     \"\",\n\t\t},\n\t\t{\n\t\t\tthatIP:      \"10.4.2.5\",\n\t\t\tthatNetwork: \"n1\",\n\t\t\tthatName:    \"foo\",\n\t\t\tmyNetwork:   netutil.DefaultNetworkName,\n\t\t\texpected:    \"\",\n\t\t},\n\t\t{\n\t\t\tthatIP:      \"10.4.2.6\",\n\t\t\tthatNetwork: \"n1\",\n\t\t\tthatName:    \"foo\",\n\t\t\tmyNetwork:   \"n2\",\n\t\t\texpected:    \"\",\n\t\t},\n\t\t{\n\t\t\tthatIP:       \"10.4.2.3\",\n\t\t\tthatNetwork:  \"n1\",\n\t\t\tthatHostname: \"bar.example.com\", // using a fqdn as hostname\n\t\t\tmyNetwork:    \"n1\",\n\t\t\texpected:     \"bar.example.com bar.example.com.n1\",\n\t\t},\n\t\t{\n\t\t\tthatIP:         \"10.4.2.7\",\n\t\t\tthatNetwork:    \"n1\",\n\t\t\tthatHostname:   \"bar\", // unqualified hostname with separate domain name\n\t\t\tthatName:       \"foo\",\n\t\t\tthatDomainname: \"example.com\",\n\t\t\tmyNetwork:      \"n1\",\n\t\t\texpected:       \"bar.example.com bar bar.n1 foo foo.n1\",\n\t\t},\n\t\t{\n\t\t\tthatIP:         \"10.4.2.8\",\n\t\t\tthatNetwork:    \"n1\",\n\t\t\tthatHostname:   \"bar\",\n\t\t\tthatDomainname: \"example.com\",\n\t\t\tmyNetwork:      \"n1\",\n\t\t\texpected:       \"bar.example.com bar bar.n1\",\n\t\t},\n\t\t{\n\t\t\tthatIP:         \"10.4.2.9\",\n\t\t\tthatNetwork:    netutil.DefaultNetworkName,\n\t\t\tthatHostname:   \"bar\",\n\t\t\tthatDomainname: \"example.com\",\n\t\t\tmyNetwork:      netutil.DefaultNetworkName,\n\t\t\texpected:       \"bar.example.com bar\",\n\t\t},\n\t\t{\n\t\t\tthatIP:         \"10.4.2.9\",\n\t\t\tthatNetwork:    netutil.DefaultNetworkName,\n\t\t\tthatHostname:   \"bar.example.com\",\n\t\t\tthatDomainname: \"example.com\",\n\t\t\tmyNetwork:      netutil.DefaultNetworkName,\n\t\t\texpected:       \"bar.example.com.example.com bar.example.com\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tthatMeta := &Meta{\n\t\t\tID: \"984d63ce45ae\",\n\t\t\tNetworks: map[string]*types100.Result{\n\t\t\t\ttc.thatNetwork: {\n\t\t\t\t\tInterfaces: []*types100.Interface{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"eth0\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tIPs: []*types100.IPConfig{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAddress: net.IPNet{IP: net.ParseIP(tc.thatIP)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tHostname:   tc.thatHostname,\n\t\t\tDomainname: tc.thatDomainname,\n\t\t\tName:       tc.thatName,\n\t\t}\n\n\t\tmyNetworks := map[string]struct{}{\n\t\t\ttc.myNetwork: {},\n\t\t}\n\t\tlines := createLine(tc.thatNetwork, thatMeta, myNetworks)\n\t\tline := strings.Join(lines, \" \")\n\t\tt.Logf(\"tc=%+v, line=%q\", tc, line)\n\t\tassert.Equal(t, tc.expected, line)\n\t}\n}\n"
  },
  {
    "path": "pkg/doc.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package pkg provides non-CLI packages.\n// Must not import CLI libraries.\npackage pkg\n"
  },
  {
    "path": "pkg/errutil/errors_check.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage errutil\n\nimport \"strings\"\n\n// IsErrConnectionRefused return whether err is\n// \"connect: connection refused\"\nfunc IsErrConnectionRefused(err error) bool {\n\tconst errMessage = \"connect: connection refused\"\n\treturn strings.Contains(err.Error(), errMessage)\n}\n\n// IsErrHTTPResponseToHTTPSClient returns whether err is\n// \"http: server gave HTTP response to HTTPS client\"\nfunc IsErrHTTPResponseToHTTPSClient(err error) bool {\n\tconst errMessage = \"server gave HTTP response to HTTPS client\"\n\treturn strings.Contains(err.Error(), errMessage)\n}\n\n// IsErrTLSHandshakeFailure returns whether err is a TLS handshake or certificate verification error\nfunc IsErrTLSHandshakeFailure(err error) bool {\n\terrStr := err.Error()\n\treturn strings.Contains(errStr, \"tls:\") ||\n\t\tstrings.Contains(errStr, \"x509:\") ||\n\t\tstrings.Contains(errStr, \"certificate\")\n}\n"
  },
  {
    "path": "pkg/errutil/exit_coder.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage errutil\n\nimport (\n\t\"os\"\n)\n\ntype ExitCoder interface {\n\terror\n\tExitCode() int\n}\n\n// ExitCodeError is to allow the program to exit with status code without outputting an error message.\ntype ExitCodeError struct {\n\texitCode int\n}\n\nfunc NewExitCoderErr(exitCode int) ExitCodeError {\n\treturn ExitCodeError{\n\t\texitCode: exitCode,\n\t}\n}\n\nfunc (e ExitCodeError) ExitCode() int {\n\treturn e.exitCode\n}\n\nfunc (e ExitCodeError) Error() string {\n\treturn \"\"\n}\n\nfunc HandleExitCoder(err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\tif exitErr, ok := err.(ExitCoder); ok {\n\t\tos.Exit(exitErr.ExitCode())\n\t}\n}\n"
  },
  {
    "path": "pkg/eventutil/eventutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage eventutil\n\nimport (\n\t\"sync\"\n\n\t\"github.com/containerd/containerd/v2/core/events\"\n)\n\ntype eventHandler struct {\n\thandlers map[string]func(events.Envelope)\n\tmu       sync.Mutex\n}\n\n// InitEventHandler initializes and returns an eventHandler\nfunc InitEventHandler() *eventHandler { //nolint:revive\n\treturn &eventHandler{handlers: make(map[string]func(events.Envelope))}\n}\n\nfunc (w *eventHandler) Handle(action string, h func(events.Envelope)) {\n\tw.mu.Lock()\n\tw.handlers[action] = h\n\tw.mu.Unlock()\n}\n\n// Watch ranges over the passed in event chan and processes the events based on the\n// handlers created for a given action.\n// To stop watching, close the event chan.\nfunc (w *eventHandler) Watch(c <-chan *events.Envelope) {\n\tfor e := range c {\n\t\tw.mu.Lock()\n\t\th, exists := w.handlers[e.Topic]\n\t\tw.mu.Unlock()\n\t\tif !exists {\n\t\t\tcontinue\n\t\t}\n\t\tgo h(*e)\n\t}\n}\n"
  },
  {
    "path": "pkg/flagutil/flagutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage flagutil\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// ReplaceOrAppendEnvValues returns the defaults with the overrides either\n// replaced by env key or appended to the list\n// FYI: https://github.com/containerd/containerd/blob/698622b89a053294593b9b5a363efff7715e9394/oci/spec_opts.go#L186-L222\n// defaults should have valid `k=v` strings.\n// overrides may have the following formats: `k=v` (override k), `k=` (emptify k), `k` (remove k).\nfunc ReplaceOrAppendEnvValues(defaults, overrides []string) []string {\n\tcache := make(map[string]int, len(defaults))\n\tresults := make([]string, 0, len(defaults))\n\tfor i, e := range defaults {\n\t\tk, _, _ := strings.Cut(e, \"=\")\n\t\tresults = append(results, e)\n\t\tcache[k] = i\n\t}\n\n\tfor _, value := range overrides {\n\t\t// Values w/o = means they want this env to be removed/unset.\n\t\tk, _, ok := strings.Cut(value, \"=\")\n\t\tif !ok {\n\t\t\tif i, exists := cache[k]; exists {\n\t\t\t\tresults[i] = \"\" // Used to indicate it should be removed\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Just do a normal set/update\n\t\tif i, exists := cache[k]; exists {\n\t\t\tresults[i] = value\n\t\t} else {\n\t\t\tresults = append(results, value)\n\t\t}\n\t}\n\n\t// Now remove all entries that we want to \"unset\"\n\tfor i := 0; i < len(results); i++ {\n\t\tif results[i] == \"\" {\n\t\t\tresults = append(results[:i], results[i+1:]...)\n\t\t\ti--\n\t\t}\n\t}\n\n\treturn results\n}\n\nfunc parseEnvVars(paths []string) ([]string, error) {\n\tvars := make([]string, 0)\n\tfor _, path := range paths {\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to open env file %s: %w\", path, err)\n\t\t}\n\t\tdefer f.Close()\n\n\t\tsc := bufio.NewScanner(f)\n\t\tfor sc.Scan() {\n\t\t\tline := strings.TrimSpace(sc.Text())\n\t\t\t// skip comment lines and empty line\n\t\t\tif len(line) == 0 || strings.HasPrefix(line, \"#\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvars = append(vars, line)\n\t\t}\n\t\tif err = sc.Err(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn vars, nil\n}\n\nfunc withOSEnv(envs []string) ([]string, error) {\n\tnewEnvs := make([]string, len(envs))\n\n\t// from https://github.com/docker/cli/blob/v22.06.0-beta.0/opts/env.go#L18\n\tgetEnv := func(val string) (string, error) {\n\t\tarr := strings.SplitN(val, \"=\", 2)\n\t\tif arr[0] == \"\" {\n\t\t\treturn \"\", errors.New(\"invalid environment variable: \" + val)\n\t\t}\n\t\tif len(arr) > 1 {\n\t\t\treturn val, nil\n\t\t}\n\t\tif envVal, ok := os.LookupEnv(arr[0]); ok {\n\t\t\treturn arr[0] + \"=\" + envVal, nil\n\t\t}\n\t\treturn val, nil\n\t}\n\tfor i := range envs {\n\t\tenv, err := getEnv(envs[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnewEnvs[i] = env\n\t}\n\n\treturn newEnvs, nil\n}\n\n// MergeEnvFileAndOSEnv combines environment variables from `--env-file` and `--env`.\n// Pass an empty slice if any arg is not used.\nfunc MergeEnvFileAndOSEnv(envFile []string, env []string) ([]string, error) {\n\tvar envs []string\n\tvar err error\n\n\tif envFiles := strutil.DedupeStrSlice(envFile); len(envFiles) > 0 {\n\t\tenvs, err = parseEnvVars(envFiles)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif env := strutil.DedupeStrSlice(env); len(env) > 0 {\n\t\tenvs = append(envs, env...)\n\t}\n\n\tif envs, err = withOSEnv(envs); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn envs, nil\n}\n"
  },
  {
    "path": "pkg/flagutil/flagutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage flagutil\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\n// tmpFileWithContent will create a temp file with given content.\nfunc tmpFileWithContent(t *testing.T, content string) string {\n\tt.Helper()\n\ttmpFile, err := os.CreateTemp(t.TempDir(), \"flagutil-test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer tmpFile.Close()\n\n\t_, err = tmpFile.WriteString(content)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(func() {\n\t\t_ = os.Remove(tmpFile.Name())\n\t})\n\treturn tmpFile.Name()\n}\n\nfunc TestReplaceOrAppendEnvValues(t *testing.T) {\n\ttests := []struct {\n\t\tdefaults  []string\n\t\toverrides []string\n\t\texpected  []string\n\t}{\n\t\t// override defaults\n\t\t{\n\t\t\tdefaults:  []string{\"A=default\", \"B=default\"},\n\t\t\toverrides: []string{\"A=override\", \"C=override\"},\n\t\t\texpected:  []string{\"A=override\", \"B=default\", \"C=override\"},\n\t\t},\n\t\t// empty defaults\n\t\t{\n\t\t\tdefaults:  []string{\"A=default\", \"B=default\"},\n\t\t\toverrides: []string{\"A=override\", \"B=\"},\n\t\t\texpected:  []string{\"A=override\", \"B=\"},\n\t\t},\n\t\t// remove defaults\n\t\t{\n\t\t\tdefaults:  []string{\"A=default\", \"B=default\"},\n\t\t\toverrides: []string{\"A=override\", \"B\"},\n\t\t\texpected:  []string{\"A=override\"},\n\t\t},\n\t}\n\n\tcomparator := func(s1, s2 []string) bool {\n\t\tif len(s1) != len(s2) {\n\t\t\treturn false\n\t\t}\n\t\tsort.Slice(s1, func(i, j int) bool {\n\t\t\treturn s1[i] < s1[j]\n\t\t})\n\t\tsort.Slice(s2, func(i, j int) bool {\n\t\t\treturn s2[i] < s2[j]\n\t\t})\n\t\tfor i, v := range s1 {\n\t\t\tif v != s2[i] {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\tfor _, tt := range tests {\n\t\tactual := ReplaceOrAppendEnvValues(tt.defaults, tt.overrides)\n\t\tassert.Assert(t, comparator(actual, tt.expected), fmt.Sprintf(\"expected: %s, actual: %s\", tt.expected, actual))\n\t}\n}\n\n// Test TestParseEnvFileGoodFile for a env file with a few well formatted lines.\nfunc TestParseEnvFileGoodFile(t *testing.T) {\n\tcontent := `foo=bar\n    baz=quux\n# comment\n\n_foobar=foobaz\nwith.dots=working\nand_underscore=working too`\n\t// Adding a newline + a line with pure whitespace.\n\t// This is being done like this instead of the block above\n\t// because it's common for editors to trim trailing whitespace\n\t// from lines, which becomes annoying since that's the\n\t// exact thing we need to test.\n\tcontent += \"\\n    \\t  \"\n\ttmpFile := tmpFileWithContent(t, content)\n\n\tlines, err := parseEnvVars([]string{tmpFile})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpectedLines := []string{\n\t\t\"foo=bar\",\n\t\t\"baz=quux\",\n\t\t\"_foobar=foobaz\",\n\t\t\"with.dots=working\",\n\t\t\"and_underscore=working too\",\n\t}\n\n\tif !reflect.DeepEqual(lines, expectedLines) {\n\t\tt.Fatal(\"lines not equal to expectedLines\")\n\t}\n}\n\n// Test TestParseEnvFileEmptyFile for an empty file.\nfunc TestParseEnvFileEmptyFile(t *testing.T) {\n\ttmpFile := tmpFileWithContent(t, \"\")\n\n\tpaths := []string{tmpFile}\n\tlines, err := parseEnvVars(paths)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(lines) != 0 {\n\t\tt.Fatal(\"lines not empty; expected empty\")\n\t}\n}\n\n// Test TestParseEnvFileNonExistentFile for a non existent file.\nfunc TestParseEnvFileNonExistentFile(t *testing.T) {\n\t_, err := parseEnvVars([]string{\"foo_bar_baz\"})\n\tif err == nil {\n\t\tt.Fatal(\"ParseEnvFile succeeded; expected failure\")\n\t}\n\tif _, ok := errors.Unwrap(err).(*os.PathError); !ok {\n\t\tt.Fatalf(\"Expected a PathError, got [%v]\", err)\n\t}\n}\n\n// Test TestValidateEnv for the validate function's correctness.\nfunc TestValidateEnv(t *testing.T) {\n\ttype testCase struct {\n\t\tvalue    string\n\t\texpected string\n\t\terr      error\n\t}\n\ttests := []testCase{\n\t\t{\n\t\t\tvalue:    \"a\",\n\t\t\texpected: \"a\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"something\",\n\t\t\texpected: \"something\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"_=a\",\n\t\t\texpected: \"_=a\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"env1=value1\",\n\t\t\texpected: \"env1=value1\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"_env1=value1\",\n\t\t\texpected: \"_env1=value1\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"env2=value2=value3\",\n\t\t\texpected: \"env2=value2=value3\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"env3=abc!qwe\",\n\t\t\texpected: \"env3=abc!qwe\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"env_4=value 4\",\n\t\t\texpected: \"env_4=value 4\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"PATH\",\n\t\t\texpected: fmt.Sprintf(\"PATH=%v\", os.Getenv(\"PATH\")),\n\t\t},\n\t\t{\n\t\t\tvalue: \"=a\",\n\t\t\terr:   fmt.Errorf(\"invalid environment variable: =a\"),\n\t\t},\n\t\t{\n\t\t\tvalue:    \"PATH=\",\n\t\t\texpected: \"PATH=\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"PATH=something\",\n\t\t\texpected: \"PATH=something\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"asd!qwe\",\n\t\t\texpected: \"asd!qwe\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"1asd\",\n\t\t\texpected: \"1asd\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"123\",\n\t\t\texpected: \"123\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"some space\",\n\t\t\texpected: \"some space\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"  some space before\",\n\t\t\texpected: \"  some space before\",\n\t\t},\n\t\t{\n\t\t\tvalue:    \"some space after  \",\n\t\t\texpected: \"some space after  \",\n\t\t},\n\t\t{\n\t\t\tvalue: \"=\",\n\t\t\terr:   fmt.Errorf(\"invalid environment variable: =\"),\n\t\t},\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\t// Environment variables are case in-sensitive on Windows\n\t\ttests = append(tests, testCase{\n\t\t\tvalue:    \"PaTh\",\n\t\t\texpected: fmt.Sprintf(\"PaTh=%v\", os.Getenv(\"PATH\")),\n\t\t\terr:      nil,\n\t\t})\n\t}\n\n\tfor _, tc := range tests {\n\t\ttc := tc\n\t\tt.Run(tc.value, func(t *testing.T) {\n\t\t\tactual, err := withOSEnv([]string{tc.value})\n\t\t\tif tc.err == nil {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.Error(t, err, tc.err.Error())\n\t\t\t}\n\t\t\tif actual != nil {\n\t\t\t\tv := actual[0]\n\t\t\t\tassert.Equal(t, v, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test TestParseEnvFileLineTooLongFile for a file with a line exceeding bufio.MaxScanTokenSize.\nfunc TestParseEnvFileLineTooLongFile(t *testing.T) {\n\tcontent := \"foo=\" + strings.Repeat(\"a\", bufio.MaxScanTokenSize+42)\n\ttmpFile := tmpFileWithContent(t, content)\n\n\t_, err := MergeEnvFileAndOSEnv([]string{tmpFile}, nil)\n\tif err == nil {\n\t\tt.Fatal(\"ParseEnvFile succeeded; expected failure\")\n\t}\n}\n\n// Test TestParseEnvVariableWithNoNameFile for parsing env file with empty variable name.\nfunc TestParseEnvVariableWithNoNameFile(t *testing.T) {\n\tcontent := `# comment=\n=blank variable names are an error case\n`\n\ttmpFile := tmpFileWithContent(t, content)\n\n\t_, err := MergeEnvFileAndOSEnv([]string{tmpFile}, nil)\n\tif nil == err {\n\t\tt.Fatal(\"if a variable has no name parsing an environment file must fail\")\n\t}\n}\n\n// Test TestMergeEnvFileAndOSEnv for merging variables from env-file and env.\nfunc TestMergeEnvFileAndOSEnv(t *testing.T) {\n\tcontent := `HOME`\n\ttmpFile := tmpFileWithContent(t, content)\n\n\tvariables, err := MergeEnvFileAndOSEnv([]string{tmpFile}, []string{\"PATH\"})\n\tif nil != err {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif len(variables) != 2 {\n\t\tt.Fatal(\"variables from env-file and flag env should be merged\")\n\t}\n\n\tif \"HOME=\"+os.Getenv(\"HOME\") != variables[0] {\n\t\tt.Fatal(\"the HOME variable is not properly imported as the first variable\")\n\t}\n\n\tif \"PATH=\"+os.Getenv(\"PATH\") != variables[1] {\n\t\tt.Fatal(\"the PATH variable is not properly imported as the second variable\")\n\t}\n}\n"
  },
  {
    "path": "pkg/formatter/common.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage formatter\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"text/template\"\n\n\t\"github.com/docker/cli/templates\"\n)\n\n// Flusher is implemented by text/tabwriter.Writer\ntype Flusher interface {\n\tFlush() error\n}\n\n// FormatSlice formats the slice with `--format` flag.\n//\n// --format=\"\" (default): JSON\n// --format='{{json .}}': JSON lines\n//\n// FormatSlice is expected to be only used for `nerdctl OBJECT inspect` commands.\nfunc FormatSlice(format string, writer io.Writer, x []interface{}) error {\n\tvar tmpl *template.Template\n\tswitch format {\n\tcase \"\":\n\t\t// Avoid escaping \"<\", \">\", \"&\"\n\t\t// https://pkg.go.dev/encoding/json\n\t\tencoder := json.NewEncoder(writer)\n\t\tencoder.SetIndent(\"\", \"    \")\n\t\tencoder.SetEscapeHTML(false)\n\t\terr := encoder.Encode(x)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprint(writer, \"\\n\")\n\tcase \"raw\", \"table\", \"wide\":\n\t\treturn errors.New(\"unsupported format: \\\"raw\\\", \\\"table\\\", and \\\"wide\\\"\")\n\tdefault:\n\t\tvar err error\n\t\ttmpl, err = ParseTemplate(format)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, f := range x {\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, f); err != nil {\n\t\t\t\tif _, ok := err.(template.ExecError); ok {\n\t\t\t\t\t// FallBack to Raw Format\n\t\t\t\t\tif err = tryRawFormat(&b, f, tmpl); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif _, err = fmt.Fprintln(writer, b.String()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc tryRawFormat(b *bytes.Buffer, f interface{}, tmpl *template.Template) error {\n\tm, err := json.MarshalIndent(f, \"\", \"    \")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar raw interface{}\n\trdr := bytes.NewReader(m)\n\tdec := json.NewDecoder(rdr)\n\tdec.UseNumber()\n\n\tif rawErr := dec.Decode(&raw); rawErr != nil {\n\t\treturn fmt.Errorf(\"unable to read inspect data: %v\", rawErr)\n\t}\n\n\ttmplMissingKey := tmpl.Option(\"missingkey=error\")\n\tif rawErr := tmplMissingKey.Execute(b, raw); rawErr != nil {\n\t\treturn fmt.Errorf(\"template parsing error: %v\", rawErr)\n\t}\n\n\treturn nil\n}\n\n// ParseTemplate wraps github.com/docker/cli/templates.Parse() to allow `json` as an alias of `{{json .}}`.\n// ParseTemplate can be removed when https://github.com/docker/cli/pull/3355 gets merged and tagged (Docker 22.XX).\nfunc ParseTemplate(format string) (*template.Template, error) {\n\taliases := map[string]string{\n\t\t\"json\": \"{{json .}}\",\n\t}\n\tif alias, ok := aliases[format]; ok {\n\t\tformat = alias\n\t}\n\treturn templates.Parse(format)\n}\n"
  },
  {
    "path": "pkg/formatter/formatter.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage formatter\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/runtime/restart\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/go-cni\"\n)\n\nfunc ContainerStatus(ctx context.Context, c containerd.Container) string {\n\ttitleCaser := cases.Title(language.English)\n\ttask, err := c.Task(ctx, nil)\n\tif err != nil {\n\t\t// NOTE: NotFound doesn't mean that container hasn't started.\n\t\t// In docker/CRI-containerd plugin, the task will be deleted\n\t\t// when it exits. So, the status will be \"created\" for this\n\t\t// case.\n\t\tif errdefs.IsNotFound(err) {\n\t\t\treturn titleCaser.String(string(containerd.Created))\n\t\t}\n\t\treturn titleCaser.String(string(containerd.Unknown))\n\t}\n\n\tstatus, err := task.Status(ctx)\n\tif err != nil {\n\t\treturn titleCaser.String(string(containerd.Unknown))\n\t}\n\tlabels, err := c.Labels(ctx)\n\tif err != nil {\n\t\treturn titleCaser.String(string(containerd.Unknown))\n\t}\n\n\tswitch s := status.Status; s {\n\tcase containerd.Stopped:\n\t\tif labels[restart.StatusLabel] == string(containerd.Running) && restart.Reconcile(status, labels) {\n\t\t\treturn fmt.Sprintf(\"Restarting (%v) %s\", status.ExitStatus, TimeSinceInHuman(status.ExitTime))\n\t\t}\n\t\treturn fmt.Sprintf(\"Exited (%v) %s\", status.ExitStatus, TimeSinceInHuman(status.ExitTime))\n\tcase containerd.Running:\n\t\treturn \"Up\" // TODO: print \"status.UpTime\" (inexistent yet)\n\tdefault:\n\t\treturn titleCaser.String(string(s))\n\t}\n}\n\nfunc InspectContainerCommand(spec *oci.Spec, trunc, quote bool) string {\n\tif spec == nil || spec.Process == nil {\n\t\treturn \"\"\n\t}\n\n\tcommand := spec.Process.CommandLine + strings.Join(spec.Process.Args, \" \")\n\tif trunc {\n\t\tcommand = Ellipsis(command, 20)\n\t}\n\tif quote {\n\t\tcommand = strconv.Quote(command)\n\t}\n\treturn command\n}\n\nfunc InspectContainerCommandTrunc(spec *oci.Spec) string {\n\treturn InspectContainerCommand(spec, true, true)\n}\n\nfunc Ellipsis(str string, maxDisplayWidth int) string {\n\tif maxDisplayWidth <= 0 {\n\t\treturn \"\"\n\t}\n\n\tlenStr := len(str)\n\tif maxDisplayWidth == 1 {\n\t\tif lenStr <= maxDisplayWidth {\n\t\t\treturn str\n\t\t}\n\t\treturn string(str[0])\n\t}\n\n\tif lenStr <= maxDisplayWidth {\n\t\treturn str\n\t}\n\treturn str[:maxDisplayWidth-1] + \"…\"\n}\n\nfunc formatRange(startHost, endHost, startContainer, endContainer int32) string {\n\tif startHost == endHost && startContainer == endContainer {\n\t\treturn fmt.Sprintf(\"%d->%d\", startHost, startContainer)\n\t}\n\treturn fmt.Sprintf(\"%d-%d->%d-%d\", startHost, endHost, startContainer, endContainer)\n}\n\nfunc FormatPorts(ports []cni.PortMapping) string {\n\tif len(ports) == 0 {\n\t\treturn \"\"\n\t}\n\n\ttype key struct {\n\t\tHostIP   string\n\t\tProtocol string\n\t}\n\tgrouped := make(map[key][]cni.PortMapping)\n\n\tfor _, p := range ports {\n\t\tk := key{HostIP: p.HostIP, Protocol: p.Protocol}\n\t\tgrouped[k] = append(grouped[k], p)\n\t}\n\n\tvar displayPorts []string\n\tfor k, pms := range grouped {\n\t\tsort.Slice(pms, func(i, j int) bool {\n\t\t\treturn pms[i].HostPort < pms[j].HostPort\n\t\t})\n\n\t\tvar i int\n\t\tvar ranges []string\n\t\tfor i = 0; i < len(pms); {\n\t\t\tstart, end := pms[i], pms[i]\n\t\t\tfor i+1 < len(pms) &&\n\t\t\t\tpms[i+1].HostPort == end.HostPort+1 &&\n\t\t\t\tpms[i+1].ContainerPort == end.ContainerPort+1 {\n\t\t\t\ti++\n\t\t\t\tend = pms[i]\n\t\t\t}\n\n\t\t\tranges = append(\n\t\t\t\tranges,\n\t\t\t\tformatRange(start.HostPort, end.HostPort, start.ContainerPort, end.ContainerPort),\n\t\t\t)\n\t\t\ti++\n\t\t}\n\t\tdisplayPorts = append(\n\t\t\tdisplayPorts,\n\t\t\tfmt.Sprintf(\"%s:%s/%s\", k.HostIP, strings.Join(ranges, \", \"), k.Protocol),\n\t\t)\n\t}\n\n\tsort.Strings(displayPorts)\n\n\treturn strings.Join(displayPorts, \", \")\n}\n\nfunc TimeSinceInHuman(since time.Time) string {\n\treturn fmt.Sprintf(\"%s ago\", units.HumanDuration(time.Since(since)))\n}\n\nfunc FormatLabels(labelMap map[string]string) string {\n\tstrs := make([]string, len(labelMap))\n\tidx := 0\n\tfor i := range labelMap {\n\t\tstrs[idx] = fmt.Sprintf(\"%s=%s\", i, labelMap[i])\n\t\tidx++\n\t}\n\treturn strings.Join(strs, \",\")\n}\n\n// ToJSON return a string with the JSON representation of the interface{}\n// https://github.com/docker/compose/blob/v2/cmd/formatter/json.go#L31C4-L39\nfunc ToJSON(i interface{}, prefix string, indentation string) (string, error) {\n\tbuffer := &bytes.Buffer{}\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetEscapeHTML(false)\n\tencoder.SetIndent(prefix, indentation)\n\terr := encoder.Encode(i)\n\treturn buffer.String(), err\n}\n"
  },
  {
    "path": "pkg/formatter/formatter_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage formatter\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/go-cni\"\n)\n\nfunc TestTimeSinceInHuman(t *testing.T) {\n\tnow := time.Now()\n\tt.Parallel()\n\n\ttests := []struct {\n\t\tname     string\n\t\tinput    time.Time\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"1 second ago\",\n\t\t\tinput:    now.Add(-1 * time.Second),\n\t\t\texpected: \"1 second ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"59 seconds ago\",\n\t\t\tinput:    now.Add(-59 * time.Second),\n\t\t\texpected: \"59 seconds ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"1 minute ago\",\n\t\t\tinput:    now.Add(-1 * time.Minute),\n\t\t\texpected: \"About a minute ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"1 hour ago\",\n\t\t\tinput:    now.Add(-1 * time.Hour),\n\t\t\texpected: \"About an hour ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"1 day ago\",\n\t\t\tinput:    now.Add(-24 * time.Hour),\n\t\t\texpected: \"24 hours ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"4 days ago\",\n\t\t\tinput:    now.Add(-4 * 24 * time.Hour),\n\t\t\texpected: \"4 days ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"1 year ago\",\n\t\t\tinput:    now.Add(-365 * 24 * time.Hour),\n\t\t\texpected: \"12 months ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"4 years ago\",\n\t\t\tinput:    now.Add(-4 * 365 * 24 * time.Hour),\n\t\t\texpected: \"4 years ago\",\n\t\t},\n\t\t{\n\t\t\tname:     \"zero duration\",\n\t\t\tinput:    now,\n\t\t\texpected: \"Less than a second ago\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tresult := TimeSinceInHuman(tt.input)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestFormatPorts(t *testing.T) {\n\tt.Parallel()\n\n\ttests := []struct {\n\t\tname     string\n\t\tinput    []cni.PortMapping\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"a single tcp port on localhost\",\n\t\t\tinput: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"127.0.0.1:3000->8080/tcp\",\n\t\t},\n\t\t{\n\t\t\tname: \"consecutive tcp ports on localhost\",\n\t\t\tinput: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3001,\n\t\t\t\t\tContainerPort: 8081,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"127.0.0.1:3000-3001->8080-8081/tcp\",\n\t\t},\n\t\t{\n\t\t\tname: \"a single tcp port on anyhost\",\n\t\t\tinput: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"0.0.0.0:3000->8080/tcp\",\n\t\t},\n\t\t{\n\t\t\tname: \"a single udp port on anyhost\",\n\t\t\tinput: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"udp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"0.0.0.0:3000->8080/udp\",\n\t\t},\n\t\t{\n\t\t\tname: \"mixed tcp and udp with consecutive ports on anyhost\",\n\t\t\tinput: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3001,\n\t\t\t\t\tContainerPort: 8081,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3002,\n\t\t\t\t\tContainerPort: 8082,\n\t\t\t\t\tProtocol:      \"udp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3003,\n\t\t\t\t\tContainerPort: 8083,\n\t\t\t\t\tProtocol:      \"udp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"0.0.0.0:3000-3001->8080-8081/tcp, 0.0.0.0:3002-3003->8082-8083/udp\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tresult := FormatPorts(tt.input)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/fs/fs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage fs\n\nimport \"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\n// InitFS will set the root location to store `internal/filesystem` ops files.\n// These files are used to allow `WriteFile` to backup and rollback content.\n// While they are transient in nature, they should still persist OS crashes / reboots, so, preferably under something\n// like XDGData, rather than tmp.\nfunc InitFS(path string) error {\n\treturn filesystem.SetFilesystemOpsDirectory(path)\n}\n"
  },
  {
    "path": "pkg/healthcheck/executor.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n)\n\n// ExecuteHealthCheck executes the health check command for a container\nfunc ExecuteHealthCheck(ctx context.Context, task containerd.Task, container containerd.Container, hc *Healthcheck) error {\n\t// Prepare process spec for health check command\n\tprocessSpec, err := prepareProcessSpec(ctx, container, hc)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif processSpec == nil {\n\t\treturn nil\n\t}\n\n\tstartTime := time.Now()\n\tresult, err := probeHealthCheck(ctx, task, hc, processSpec)\n\tif err != nil {\n\t\t_ = updateHealthStatus(ctx, container, hc, &HealthcheckResult{\n\t\t\tStart:    startTime,\n\t\t\tEnd:      time.Now(),\n\t\t\tExitCode: -1,\n\t\t\tOutput:   err.Error(),\n\t\t})\n\t\treturn fmt.Errorf(\"health check probe failed: %w\", err)\n\t}\n\n\t// Success case, update health status\n\tresult.Start = startTime\n\tif err := updateHealthStatus(ctx, container, hc, result); err != nil {\n\t\treturn fmt.Errorf(\"failed to update health status: %w\", err)\n\t}\n\treturn nil\n}\n\n// probeHealthCheck executes the health check command inside the container context\nfunc probeHealthCheck(ctx context.Context, task containerd.Task, hc *Healthcheck, processSpec *specs.Process) (*HealthcheckResult, error) {\n\texecID := \"health-check-\" + idgen.TruncateID(idgen.GenerateID())\n\toutputBuf := NewResizableBuffer(MaxOutputLen)\n\n\tprocess, err := task.Exec(ctx, execID, processSpec, cio.NewCreator(\n\t\tcio.WithStreams(nil, outputBuf, outputBuf),\n\t))\n\tif err != nil {\n\t\tlog.G(ctx).Debugf(\"failed to exec health check: %v\", err)\n\t\treturn nil, fmt.Errorf(\"exec error: %w\", err)\n\t}\n\n\tif err := process.Start(ctx); err != nil {\n\t\tlog.G(ctx).Debugf(\"failed to start health check: %v\", err)\n\t\treturn nil, fmt.Errorf(\"start error: %w\", err)\n\t}\n\n\texitStatusC, err := process.Wait(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to wait for health check: %w\", err)\n\t}\n\n\tselect {\n\tcase <-time.After(hc.Timeout):\n\t\t_ = process.Kill(ctx, syscall.SIGKILL)\n\t\t<-exitStatusC\n\t\tprocess.IO().Wait()\n\t\tprocess.IO().Close()\n\t\tmsg := fmt.Sprintf(\"Health check exceeded timeout (%v)\", hc.Timeout)\n\t\tif out := outputBuf.String(); len(out) > 0 {\n\t\t\tmsg = fmt.Sprintf(\"Health check exceeded timeout (%v): %s\", hc.Timeout, out)\n\t\t}\n\n\t\tlog.G(ctx).Debugf(\"health check timed out: %s\", msg)\n\n\t\treturn &HealthcheckResult{\n\t\t\tExitCode: -1,\n\t\t\tOutput:   msg,\n\t\t\tEnd:      time.Now(),\n\t\t}, nil\n\n\tcase exitStatus := <-exitStatusC:\n\t\tprocess.IO().Wait()\n\t\tprocess.IO().Close()\n\t\tcode, _, _ := exitStatus.Result()\n\t\treturn &HealthcheckResult{\n\t\t\tExitCode: int(code),\n\t\t\tOutput:   outputBuf.String(),\n\t\t\tEnd:      time.Now(),\n\t\t}, nil\n\t}\n}\n\n// updateHealthStatus updates the health status based on the health check result\nfunc updateHealthStatus(ctx context.Context, container containerd.Container, hcConfig *Healthcheck, hcResult *HealthcheckResult) error {\n\t// Get current health state from labels\n\tcurrentHealth, err := readHealthStateFromLabels(ctx, container)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read health state from labels: %w\", err)\n\t}\n\tif currentHealth == nil {\n\t\t// Determine if we should start in the start period workflow\n\t\thasStartPeriod := hcConfig.StartPeriod > 0\n\t\tcurrentHealth = &HealthState{\n\t\t\tStatus:        Starting,\n\t\t\tFailingStreak: 0,\n\t\t\tInStartPeriod: hasStartPeriod,\n\t\t}\n\t}\n\n\t// Get container info for start period check\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container info: %w\", err)\n\t}\n\tcontainerCreated := info.CreatedAt\n\n\t// Check if we're in start period workflow\n\tinStartPeriodTime := hcResult.Start.Sub(containerCreated) < hcConfig.StartPeriod\n\tinStartPeriodState := currentHealth.InStartPeriod\n\n\tif inStartPeriodTime && inStartPeriodState {\n\t\t// Start Period Workflow\n\t\tif hcResult.ExitCode == 0 {\n\t\t\t// First healthy result transitions us out of start period\n\t\t\tcurrentHealth.Status = Healthy\n\t\t\tcurrentHealth.FailingStreak = 0\n\t\t\tcurrentHealth.InStartPeriod = false\n\t\t}\n\t\t// Ignore unhealthy results during start period\n\t} else {\n\t\t// Health Interval Workflow\n\t\tif hcResult.ExitCode == 0 {\n\t\t\tif currentHealth.Status != Healthy {\n\t\t\t\tcurrentHealth.Status = Healthy\n\t\t\t\tcurrentHealth.FailingStreak = 0\n\t\t\t}\n\t\t} else {\n\t\t\tcurrentHealth.FailingStreak++\n\t\t\tif currentHealth.FailingStreak >= hcConfig.Retries && currentHealth.Status != Unhealthy {\n\t\t\t\tcurrentHealth.Status = Unhealthy\n\t\t\t}\n\t\t}\n\t}\n\n\t// Write updated health state back to labels\n\tif err := writeHealthStateToLabels(ctx, container, currentHealth); err != nil {\n\t\treturn fmt.Errorf(\"failed to write health state to labels: %w\", err)\n\t}\n\n\t// Store the latest health check result in the log file\n\tif err := writeHealthLog(ctx, container, hcResult); err != nil {\n\t\treturn fmt.Errorf(\"failed to write health log: %w\", err)\n\t}\n\treturn nil\n}\n\n// prepareProcessSpec prepares the process spec for health check execution\nfunc prepareProcessSpec(ctx context.Context, container containerd.Container, hcConfig *Healthcheck) (*specs.Process, error) {\n\thcCommand := hcConfig.Test\n\n\tvar args []string\n\tswitch hcCommand[0] {\n\tcase TestNone, CmdNone:\n\t\tlog.G(ctx).Debug(\"health check is set to NONE, skipping execution\")\n\t\treturn nil, nil\n\tcase Cmd:\n\t\targs = hcCommand[1:]\n\tcase CmdShell:\n\t\tif len(hcCommand) < 2 || strings.TrimSpace(hcCommand[1]) == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"no health check command specified\")\n\t\t}\n\t\targs = []string{\"/bin/sh\", \"-c\", strings.Join(hcCommand[1:], \" \")}\n\tdefault:\n\t\targs = hcCommand\n\t}\n\n\tif len(args) < 1 || args[0] == \"\" {\n\t\treturn nil, fmt.Errorf(\"no health check command specified\")\n\t}\n\n\t// Get container spec for environment and working directory\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get container spec: %w\", err)\n\t}\n\tprocessSpec := &specs.Process{\n\t\tArgs: args,\n\t\tEnv:  spec.Process.Env,\n\t\tUser: spec.Process.User,\n\t\tCwd:  spec.Process.Cwd,\n\t}\n\n\treturn processSpec, nil\n}\n"
  },
  {
    "path": "pkg/healthcheck/health.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n)\n\ntype HealthStatus = string\n\n// Health states\nconst (\n\tNoHealthcheck HealthStatus = \"none\" // Indicates there is no healthcheck\n\tStarting      HealthStatus = \"starting\"\n\tHealthy       HealthStatus = \"healthy\"\n\tUnhealthy     HealthStatus = \"unhealthy\"\n)\n\n// Healthcheck cmd types\nconst (\n\tCmdNone  = \"NONE\"\n\tCmd      = \"CMD\"\n\tCmdShell = \"CMD-SHELL\"\n\tTestNone = \"\"\n)\n\nconst (\n\tDefaultProbeInterval   = 30 * time.Second // Default interval between probe runs. Also applies before the first probe.\n\tDefaultProbeTimeout    = 30 * time.Second // Max duration a single probe run may take before it's considered failed.\n\tDefaultStartPeriod     = 0 * time.Second  // Grace period for container startup before health checks count as failures.\n\tDefaultProbeRetries    = 3                // Number of consecutive failures before marking container as unhealthy.\n\tMaxLogEntries          = 5                // Maximum number of health check log entries to keep.\n\tMaxOutputLenForInspect = 4096             // Max output length (in bytes) stored in health check logs during inspect. Longer outputs are truncated.\n\tMaxOutputLen           = 1 * 1024 * 1024  // Max output size for health check logs: 1MB limit (prevents excessive memory usage)\n\tHealthLogFilename      = \"health.json\"    // HealthLogFilename is the name of the file used to persist health check status for a container.\n)\n\n// NOTE: Health, HealthcheckResult and Healthcheck types are kept Docker-compatible.\n// See: https://github.com/moby/moby/blob/9d1b069a4bfdcee368e67767978eff596b696d4c/api/types/container/health.go\n// Health stores information about the container's healthcheck results\ntype Health struct {\n\tStatus        HealthStatus         // Status is one of [Starting], [Healthy] or [Unhealthy].\n\tFailingStreak int                  // FailingStreak is the number of consecutive failures\n\tLog           []*HealthcheckResult // Log contains the last few results (oldest first)\n}\n\n// HealthcheckResult stores information about a single run of a healthcheck probe\ntype HealthcheckResult struct {\n\tStart    time.Time // Start is the time this check started\n\tEnd      time.Time // End is the time this check ended\n\tExitCode int       // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe\n\tOutput   string    // Output from last check\n}\n\n// Healthcheck represents the health check configuration\ntype Healthcheck struct {\n\tTest        []string      `json:\"Test,omitempty\"`        // Test is the check to perform that the container is healthy\n\tInterval    time.Duration `json:\"Interval,omitempty\"`    // Interval is the time to wait between checks\n\tTimeout     time.Duration `json:\"Timeout,omitempty\"`     // Timeout is the time to wait before considering the check to have hung\n\tRetries     int           `json:\"Retries,omitempty\"`     // Retries is the number of consecutive failures needed to consider a container as unhealthy\n\tStartPeriod time.Duration `json:\"StartPeriod,omitempty\"` // StartPeriod is the period for the container to initialize before the health check starts\n}\n\n// HealthState stores the current health state of a container\ntype HealthState struct {\n\tStatus        HealthStatus // Status is one of [Starting], [Healthy] or [Unhealthy]\n\tFailingStreak int          // FailingStreak is the number of consecutive failures\n\tInStartPeriod bool         // InStartPeriod indicates if we're in the start period workflow\n}\n\n// ToJSONString serializes HealthState to a JSON string for label storage\nfunc (hs *HealthState) ToJSONString() (string, error) {\n\tb, err := json.Marshal(hs)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), nil\n}\n\n// HealthStateFromJSON deserializes a JSON string into a HealthState\nfunc HealthStateFromJSON(s string) (*HealthState, error) {\n\tvar hs HealthState\n\tif err := json.Unmarshal([]byte(s), &hs); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &hs, nil\n}\n\n// ToJSONString serializes a Healthcheck struct to a JSON string\nfunc (hc *Healthcheck) ToJSONString() (string, error) {\n\tb, err := json.Marshal(hc)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), nil\n}\n\n// HealthCheckFromJSON deserializes a JSON string into a Healthcheck struct\nfunc HealthCheckFromJSON(s string) (*Healthcheck, error) {\n\tvar hc Healthcheck\n\tif err := json.Unmarshal([]byte(s), &hc); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &hc, nil\n}\n\n// ToJSONString serializes a HealthcheckResult struct to a JSON string\nfunc (r *HealthcheckResult) ToJSONString() (string, error) {\n\tb, err := json.Marshal(r)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), nil\n}\n\n// HealthcheckResultFromJSON deserializes a JSON string into a HealthcheckResult struct\nfunc HealthcheckResultFromJSON(s string) (*HealthcheckResult, error) {\n\tvar r HealthcheckResult\n\tif err := json.Unmarshal([]byte(s), &r); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &r, nil\n}\n\n// ApplyDefaults sets default values for unset healthcheck fields\nfunc (hc *Healthcheck) ApplyDefaults() {\n\tif hc.Interval == 0 {\n\t\thc.Interval = DefaultProbeInterval\n\t}\n\tif hc.Timeout == 0 {\n\t\thc.Timeout = DefaultProbeTimeout\n\t}\n\tif hc.StartPeriod == 0 {\n\t\thc.StartPeriod = DefaultStartPeriod\n\t}\n\tif hc.Retries == 0 {\n\t\thc.Retries = DefaultProbeRetries\n\t}\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_manager_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n)\n\n// CreateTimer sets up the transient systemd timer and service for healthchecks.\nfunc CreateTimer(ctx context.Context, container containerd.Container, cfg *config.Config, nerdctlCmd string, nerdctlArgs []string) error {\n\treturn nil\n}\n\n// StartTimer starts the healthcheck timer unit.\nfunc StartTimer(ctx context.Context, container containerd.Container, cfg *config.Config) error {\n\treturn nil\n}\n\n// RemoveTransientHealthCheckFiles stops and cleans up the transient timer and service.\nfunc RemoveTransientHealthCheckFiles(ctx context.Context, container containerd.Container) error {\n\treturn nil\n}\n\n// ForceRemoveTransientHealthCheckFiles forcefully stops and cleans up the transient timer and service\n// using just the container ID. This function is non-blocking and uses timeouts to prevent hanging\n// on systemd operations. On Darwin, this is a no-op since systemd is not available.\nfunc ForceRemoveTransientHealthCheckFiles(ctx context.Context, containerID string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_manager_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n)\n\n// CreateTimer sets up the transient systemd timer and service for healthchecks.\nfunc CreateTimer(ctx context.Context, container containerd.Container, cfg *config.Config, nerdctlCmd string, nerdctlArgs []string) error {\n\treturn nil\n}\n\n// StartTimer starts the healthcheck timer unit.\nfunc StartTimer(ctx context.Context, container containerd.Container, cfg *config.Config) error {\n\treturn nil\n}\n\n// RemoveTransientHealthCheckFiles stops and cleans up the transient timer and service.\nfunc RemoveTransientHealthCheckFiles(ctx context.Context, container containerd.Container) error {\n\treturn nil\n}\n\n// ForceRemoveTransientHealthCheckFiles forcefully stops and cleans up the transient timer and service\n// using just the container ID. This function is non-blocking and uses timeouts to prevent hanging\n// on systemd operations. On Darwin, this is a no-op since systemd is not available.\nfunc ForceRemoveTransientHealthCheckFiles(ctx context.Context, containerID string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_manager_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/coreos/go-systemd/v22/dbus\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\n// CreateTimer sets up the transient systemd timer and service for healthchecks.\nfunc CreateTimer(ctx context.Context, container containerd.Container, cfg *config.Config, nerdctlCmd string, nerdctlArgs []string) error {\n\thc := extractHealthcheck(ctx, container)\n\tif hc == nil {\n\t\treturn nil\n\t}\n\tif shouldSkipHealthCheckSystemd(hc, cfg) {\n\t\treturn nil\n\t}\n\n\tcontainerID := container.ID()\n\tlog.G(ctx).Debugf(\"Creating healthcheck timer unit: %s\", containerID)\n\n\t// Set all environment variables so that they are available for the nerdctl commands run via the systemd service file\n\tcmdOpts := []string{}\n\tif path := os.Getenv(\"PATH\"); path != \"\" {\n\t\tcmdOpts = append(cmdOpts, \"--setenv=PATH=\"+path)\n\t}\n\n\tif nerdctlToml := os.Getenv(\"NERDCTL_TOML\"); nerdctlToml != \"\" {\n\t\tcmdOpts = append(cmdOpts, \"--setenv=NERDCTL_TOML=\"+nerdctlToml)\n\t}\n\n\tif buildKitHost := os.Getenv(\"BUILDKIT_HOST\"); buildKitHost != \"\" {\n\t\tcmdOpts = append(cmdOpts, \"--setenv=BUILDKIT_HOST=\"+buildKitHost)\n\t}\n\n\t// Always use health-interval for timer frequency\n\tcmdOpts = append(cmdOpts, \"--unit\", containerID, \"--on-unit-inactive=\"+hc.Interval.String(), \"--timer-property=AccuracySec=1s\")\n\n\tcmdOpts = append(cmdOpts, nerdctlCmd)\n\tcmdOpts = append(cmdOpts, nerdctlArgs...)\n\tcmdOpts = append(cmdOpts, \"container\", \"healthcheck\", containerID)\n\n\tlog.G(ctx).Debugf(\"creating healthcheck timer with: systemd-run %s\", strings.Join(cmdOpts, \" \"))\n\trun := exec.Command(\"systemd-run\", cmdOpts...)\n\tif out, err := run.CombinedOutput(); err != nil {\n\t\treturn fmt.Errorf(\"systemd-run failed: %w\\noutput: %s\", err, strings.TrimSpace(string(out)))\n\t}\n\n\treturn nil\n}\n\n// StartTimer starts the healthcheck timer unit.\nfunc StartTimer(ctx context.Context, container containerd.Container, cfg *config.Config) error {\n\thc := extractHealthcheck(ctx, container)\n\tif hc == nil {\n\t\treturn nil\n\t}\n\tif shouldSkipHealthCheckSystemd(hc, cfg) {\n\t\treturn nil\n\t}\n\n\tcontainerID := container.ID()\n\tvar conn *dbus.Conn\n\tvar err error\n\tif rootlessutil.IsRootless() {\n\t\tconn, err = dbus.NewUserConnectionContext(ctx)\n\t} else {\n\t\tconn, err = dbus.NewSystemConnectionContext(ctx)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"systemd DBUS connect error: %w\", err)\n\t}\n\tdefer conn.Close()\n\n\tstartChan := make(chan string)\n\tunit := containerID + \".service\"\n\tif _, err := conn.RestartUnitContext(context.Background(), unit, \"fail\", startChan); err != nil {\n\t\treturn err\n\t}\n\tif msg := <-startChan; msg != \"done\" {\n\t\treturn fmt.Errorf(\"unexpected systemd restart result: %s\", msg)\n\t}\n\treturn nil\n}\n\n// RemoveTransientHealthCheckFiles stops and cleans up the transient timer and service.\nfunc RemoveTransientHealthCheckFiles(ctx context.Context, container containerd.Container) error {\n\thc := extractHealthcheck(ctx, container)\n\tif hc == nil {\n\t\treturn nil\n\t}\n\n\treturn ForceRemoveTransientHealthCheckFiles(ctx, container.ID())\n}\n\n// ForceRemoveTransientHealthCheckFiles forcefully stops and cleans up the transient timer and service\n// using just the container ID. This function is non-blocking and uses timeouts to prevent hanging\n// on systemd operations. It logs errors as warnings but continues cleanup attempts.\nfunc ForceRemoveTransientHealthCheckFiles(ctx context.Context, containerID string) error {\n\tlog.G(ctx).Debugf(\"Force removing healthcheck timer unit: %s\", containerID)\n\n\t// Create a timeout context for systemd operations\n\ttimeoutCtx, cancel := context.WithTimeout(ctx, 3*time.Second)\n\tdefer cancel()\n\n\ttimer := containerID + \".timer\"\n\tservice := containerID + \".service\"\n\n\t// Channel to collect any critical errors (though we'll continue cleanup regardless)\n\terrChan := make(chan error, 3)\n\n\t// Goroutine for DBUS connection and cleanup operations\n\tgo func() {\n\t\tdefer close(errChan)\n\n\t\tvar conn *dbus.Conn\n\t\tvar err error\n\t\tif rootlessutil.IsRootless() {\n\t\t\tconn, err = dbus.NewUserConnectionContext(ctx)\n\t\t} else {\n\t\t\tconn, err = dbus.NewSystemConnectionContext(ctx)\n\t\t}\n\t\tif err != nil {\n\t\t\tlog.G(ctx).Warnf(\"systemd DBUS connect error during force cleanup: %v\", err)\n\t\t\terrChan <- fmt.Errorf(\"systemd DBUS connect error: %w\", err)\n\t\t\treturn\n\t\t}\n\t\tdefer conn.Close()\n\n\t\t// Stop timer with timeout\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-timeoutCtx.Done():\n\t\t\t\tlog.G(ctx).Warnf(\"timeout stopping timer %s during force cleanup\", timer)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\ttChan := make(chan string, 1)\n\t\t\t\tif _, err := conn.StopUnitContext(timeoutCtx, timer, \"ignore-dependencies\", tChan); err == nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase msg := <-tChan:\n\t\t\t\t\t\tif msg != \"done\" {\n\t\t\t\t\t\t\tlog.G(ctx).Warnf(\"timer stop message during force cleanup: %s\", msg)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-timeoutCtx.Done():\n\t\t\t\t\t\tlog.G(ctx).Warnf(\"timeout waiting for timer stop confirmation: %s\", timer)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlog.G(ctx).Warnf(\"failed to stop timer %s during force cleanup: %v\", timer, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t// Stop service with timeout\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-timeoutCtx.Done():\n\t\t\t\tlog.G(ctx).Warnf(\"timeout stopping service %s during force cleanup\", service)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tsChan := make(chan string, 1)\n\t\t\t\tif _, err := conn.StopUnitContext(timeoutCtx, service, \"ignore-dependencies\", sChan); err == nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase msg := <-sChan:\n\t\t\t\t\t\tif msg != \"done\" {\n\t\t\t\t\t\t\tlog.G(ctx).Warnf(\"service stop message during force cleanup: %s\", msg)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-timeoutCtx.Done():\n\t\t\t\t\t\tlog.G(ctx).Warnf(\"timeout waiting for service stop confirmation: %s\", service)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlog.G(ctx).Warnf(\"failed to stop service %s during force cleanup: %v\", service, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t// Reset failed units (best effort, non-blocking)\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-timeoutCtx.Done():\n\t\t\t\tlog.G(ctx).Warnf(\"timeout resetting failed unit %s during force cleanup\", service)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tif err := conn.ResetFailedUnitContext(timeoutCtx, service); err != nil {\n\t\t\t\t\tlog.G(ctx).Warnf(\"failed to reset failed unit %s during force cleanup: %v\", service, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\t// Wait a short time for operations to complete, but don't block indefinitely\n\t\tselect {\n\t\tcase <-time.After(3 * time.Second):\n\t\t\tlog.G(ctx).Debugf(\"force cleanup operations completed for container %s\", containerID)\n\t\tcase <-timeoutCtx.Done():\n\t\t\tlog.G(ctx).Warnf(\"force cleanup timed out for container %s\", containerID)\n\t\t}\n\t}()\n\n\t// Wait for the cleanup goroutine to finish or timeout\n\tselect {\n\tcase err := <-errChan:\n\t\tif err != nil {\n\t\t\tlog.G(ctx).Warnf(\"force cleanup encountered errors but continuing: %v\", err)\n\t\t}\n\tcase <-timeoutCtx.Done():\n\t\tlog.G(ctx).Warnf(\"force cleanup timed out for container %s, but cleanup may continue in background\", containerID)\n\t}\n\n\t// Always return nil - this function should never block the caller\n\t// even if systemd operations fail or timeout\n\tlog.G(ctx).Debugf(\"force cleanup completed (non-blocking) for container %s\", containerID)\n\treturn nil\n}\n\nfunc extractHealthcheck(ctx context.Context, container containerd.Container) *Healthcheck {\n\tl, err := container.Labels(ctx)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Debugf(\"could not get labels for container %s\", container.ID())\n\t\treturn nil\n\t}\n\thcStr, ok := l[labels.HealthCheck]\n\tif !ok || hcStr == \"\" {\n\t\treturn nil\n\t}\n\thc, err := HealthCheckFromJSON(hcStr)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Debugf(\"invalid healthcheck config on container %s\", container.ID())\n\t\treturn nil\n\t}\n\treturn hc\n}\n\n// shouldSkipHealthCheckSystemd determines if healthcheck timers should be skipped.\nfunc shouldSkipHealthCheckSystemd(hc *Healthcheck, cfg *config.Config) bool {\n\t// Don't proceed if systemd is unavailable or disabled\n\tif !defaults.IsSystemdAvailable() || cfg.DisableHCSystemd || rootlessutil.IsRootless() {\n\t\treturn true\n\t}\n\n\t// Don't proceed if health check is nil, empty or explicitly NONE.\n\tif hc == nil || len(hc.Test) == 0 || hc.Test[0] == \"NONE\" {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_manager_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/config\"\n)\n\n// CreateTimer sets up the transient systemd timer and service for healthchecks.\nfunc CreateTimer(ctx context.Context, container containerd.Container, cfg *config.Config, nerdctlCmd string, nerdctlArgs []string) error {\n\treturn nil\n}\n\n// StartTimer starts the healthcheck timer unit.\nfunc StartTimer(ctx context.Context, container containerd.Container, cfg *config.Config) error {\n\treturn nil\n}\n\n// RemoveTransientHealthCheckFiles stops and cleans up the transient timer and service.\nfunc RemoveTransientHealthCheckFiles(ctx context.Context, container containerd.Container) error {\n\treturn nil\n}\n\n// ForceRemoveTransientHealthCheckFiles forcefully stops and cleans up the transient timer and service\n// using just the container ID. This function is non-blocking and uses timeouts to prevent hanging\n// on systemd operations. On Windows, this is a no-op since systemd is not available.\nfunc ForceRemoveTransientHealthCheckFiles(ctx context.Context, containerID string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/healthcheck/log.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage healthcheck\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\n// writeHealthLog writes the latest health check result to the log file, appending it to existing logs.\nfunc writeHealthLog(ctx context.Context, container containerd.Container, result *HealthcheckResult) error {\n\tstateDir, err := getContainerStateDir(ctx, container)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error fetching container state dir: %v\", err)\n\t}\n\n\tdata, err := result.ToJSONString()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal health log: %w\", err)\n\t}\n\n\t// Write the latest result to the file\n\tlogPath := filepath.Join(stateDir, HealthLogFilename)\n\treturn filesystem.WithLock(stateDir, func() error {\n\t\tfile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY, 0o600)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer file.Close()\n\t\tif _, err = file.Seek(0, io.SeekEnd); err != nil {\n\t\t\treturn fmt.Errorf(\"seek error: %w\", err)\n\t\t}\n\t\tif _, err = file.Write(append([]byte(data), '\\n')); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write health log: %w\", err)\n\t\t}\n\n\t\treturn file.Sync()\n\t})\n}\n\n// ReadHealthStatusForInspect reads the health state from labels and the last MaxLogEntries health check result logs.\nfunc ReadHealthStatusForInspect(stateDir, healthState string) (*Health, error) {\n\tstate, err := HealthStateFromJSON(healthState)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse health state: %w\", err)\n\t}\n\n\tlogPath := filepath.Join(stateDir, HealthLogFilename)\n\tvar logs []*HealthcheckResult\n\terr = filesystem.WithReadOnlyLock(logPath, func() error {\n\t\tfile, err := os.Open(logPath)\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tdefer file.Close()\n\n\t\treader := bufio.NewReader(file)\n\t\tfor {\n\t\t\tline, err := reader.ReadString('\\n')\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tline = strings.TrimRight(line, \"\\n\")\n\t\t\tresult, err := HealthcheckResultFromJSON(line)\n\t\t\tif err != nil {\n\t\t\t\tlog.L.Warnf(\"failed to parse healthcheck log line: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlogs = append(logs, result)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Keep only the last MaxLogEntries\n\tn := len(logs)\n\tif n > MaxLogEntries {\n\t\tlogs = logs[n-MaxLogEntries:]\n\t}\n\n\t// Reverse for newest-first order\n\tfor i, j := 0, len(logs)-1; i < j; i, j = i+1, j-1 {\n\t\tlogs[i], logs[j] = logs[j], logs[i]\n\t}\n\n\t// Truncate log outputs to avoid flooding inspect output\n\tfor _, logEntry := range logs {\n\t\tif len(logEntry.Output) > MaxOutputLenForInspect {\n\t\t\tbuf := NewResizableBuffer(MaxOutputLenForInspect)\n\t\t\t_, _ = buf.Write([]byte(logEntry.Output))\n\t\t\tlogEntry.Output = buf.String()\n\t\t}\n\t}\n\n\t// Create a Health object with the health state and logs\n\thealth := &Health{\n\t\tStatus:        state.Status,\n\t\tFailingStreak: state.FailingStreak,\n\t\tLog:           logs,\n\t}\n\n\treturn health, nil\n}\n\n// writeHealthStateToLabels writes the health state to container labels\nfunc writeHealthStateToLabels(ctx context.Context, container containerd.Container, healthState *HealthState) error {\n\ths, err := healthState.ToJSONString()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal health healthState: %w\", err)\n\t}\n\n\tlbs, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get container labels: %w\", err)\n\t}\n\n\t// Update healthState label\n\tlbs[labels.HealthState] = hs\n\t_, err = container.SetLabels(ctx, lbs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to update container labels: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// readHealthStateFromLabels reads the health state from container labels\nfunc readHealthStateFromLabels(ctx context.Context, container containerd.Container) (*HealthState, error) {\n\tlbs, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get container labels: %w\", err)\n\t}\n\n\t// Check if health state label exists\n\tstateJSON, ok := lbs[labels.HealthState]\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\n\t// HealthCheckFromJSON health state from JSON\n\tstate, err := HealthStateFromJSON(stateJSON)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse health state: %w\", err)\n\t}\n\n\treturn state, nil\n}\n\n// getContainerStateDir returns the container's state directory from labels.\nfunc getContainerStateDir(ctx context.Context, container containerd.Container) (string, error) {\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tstateDir, ok := info.Labels[labels.StateDir]\n\tif !ok {\n\t\treturn \"\", err\n\t}\n\treturn stateDir, nil\n}\n\n// ResizableBuffer collects output with a configurable upper limit.\ntype ResizableBuffer struct {\n\tmu        sync.Mutex\n\tbuf       bytes.Buffer\n\tmaxSize   int\n\ttruncated bool\n}\n\n// NewResizableBuffer returns a new buffer with the given size limit in bytes.\nfunc NewResizableBuffer(maxSize int) *ResizableBuffer {\n\treturn &ResizableBuffer{maxSize: maxSize}\n}\n\nfunc (b *ResizableBuffer) Write(p []byte) (int, error) {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\tremaining := b.maxSize - b.buf.Len()\n\tif remaining <= 0 {\n\t\tb.truncated = true\n\t\treturn len(p), nil\n\t}\n\n\tif len(p) > remaining {\n\t\tb.truncated = true\n\t\tp = p[:remaining]\n\t}\n\n\treturn b.buf.Write(p)\n}\n\nfunc (b *ResizableBuffer) String() string {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\n\ts := b.buf.String()\n\tif b.truncated {\n\t\ts += \"... [truncated]\"\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "pkg/identifiers/validate.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package identifiers implements functions for docker compatible identifier validation.\npackage identifiers\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/containerd/errdefs\"\n)\n\nconst AllowedIdentfierChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`\n\nvar AllowedIdentifierPattern = regexp.MustCompile(`^` + AllowedIdentfierChars + `+$`)\n\n// ValidateDockerCompat implements docker compatible identifier validation.\n// The containerd implementation allows single character identifiers, while the\n// Docker compatible implementation requires at least 2 characters for identifiers.\n// The containerd implementation enforces a maximum length constraint of 76 characters,\n// while the Docker compatible implementation omits the length check entirely.\nfunc ValidateDockerCompat(s string) error {\n\tif len(s) == 0 {\n\t\treturn fmt.Errorf(\"identifier must not be empty %w\", errdefs.ErrInvalidArgument)\n\t}\n\n\tif !AllowedIdentifierPattern.MatchString(s) {\n\t\treturn fmt.Errorf(\"identifier %q must match pattern %q: %w\", s, AllowedIdentfierChars, errdefs.ErrInvalidArgument)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/idgen/idgen.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage idgen\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"fmt\"\n)\n\nconst (\n\tIDLength      = 64\n\tShortIDLength = 12\n)\n\nfunc GenerateID() string {\n\tbytesLength := IDLength / 2\n\tb := make([]byte, bytesLength)\n\tn, err := rand.Read(b)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif n != bytesLength {\n\t\tpanic(fmt.Errorf(\"expected %d bytes, got %d bytes\", bytesLength, n))\n\t}\n\treturn hex.EncodeToString(b)\n}\n\nfunc TruncateID(id string) string {\n\tif len(id) < ShortIDLength {\n\t\treturn id\n\t}\n\treturn id[:ShortIDLength]\n}\n"
  },
  {
    "path": "pkg/idutil/containerwalker/containerwalker.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage containerwalker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\ntype Found struct {\n\tContainer  containerd.Container\n\tReq        string // The raw request string. name, short ID, or long ID.\n\tMatchIndex int    // Begins with 0, up to MatchCount - 1.\n\tMatchCount int    // 1 on exact match. > 1 on ambiguous match. Never be <= 0.\n}\n\ntype OnFound func(ctx context.Context, found Found) error\n\ntype ContainerWalker struct {\n\tClient  *containerd.Client\n\tOnFound OnFound\n}\n\n// Walk walks containers and calls w.OnFound .\n// Req is name, short ID, or long ID.\n// Returns the number of the found entries.\nfunc (w *ContainerWalker) Walk(ctx context.Context, req string) (int, error) {\n\tif strings.HasPrefix(req, \"k8s://\") {\n\t\treturn -1, fmt.Errorf(\"specifying \\\"k8s://...\\\" form is not supported (Hint: specify ID instead): %q\", req)\n\t}\n\tfilters := []string{\n\t\tfmt.Sprintf(\"labels.%q==%s\", labels.Name, req),\n\t\tfmt.Sprintf(\"id~=^%s.*$\", regexp.QuoteMeta(req)),\n\t}\n\n\tcontainers, err := w.Client.Containers(ctx, filters...)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\tmatchCount := len(containers)\n\tfor i, c := range containers {\n\t\tf := Found{\n\t\t\tContainer:  c,\n\t\t\tReq:        req,\n\t\t\tMatchIndex: i,\n\t\t\tMatchCount: matchCount,\n\t\t}\n\t\tif e := w.OnFound(ctx, f); e != nil {\n\t\t\treturn -1, e\n\t\t}\n\t}\n\treturn matchCount, nil\n}\n\n// WalkAll calls `Walk` for each req in `reqs`.\n//\n// It can be used when the matchCount is not important (e.g., only care if there\n// is any error or if matchCount == 0 (not found error) when walking all reqs).\n// If `forceAll`, it calls `Walk` on every req\n// and return all errors joined by `\\n`. If not `forceAll`, it returns the first error\n// encountered while calling `Walk`.\nfunc (w *ContainerWalker) WalkAll(ctx context.Context, reqs []string, forceAll bool) error {\n\tvar errs []string\n\tfor _, req := range reqs {\n\t\tn, err := w.Walk(ctx, req)\n\t\tif err == nil && n == 0 {\n\t\t\terr = fmt.Errorf(\"no such container: %s\", req)\n\t\t}\n\t\tif err != nil {\n\t\t\tif !forceAll {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrs = append(errs, err.Error())\n\t\t}\n\t}\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"%d errors:\\n%s\", len(errs), strings.Join(errs, \"\\n\"))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/idutil/imagewalker/imagewalker.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imagewalker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/opencontainers/go-digest\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\ntype Found struct {\n\tImage          images.Image\n\tReq            string // The raw request string. name, short ID, or long ID.\n\tMatchIndex     int    // Begins with 0, up to MatchCount - 1.\n\tMatchCount     int    // 1 on exact match. > 1 on ambiguous match. Never be <= 0.\n\tUniqueImages   int    // Number of unique images in all found images.\n\tNameMatchIndex int    // Image index with a name matching the argument for `nerdctl rmi`.\n}\n\ntype OnFound func(ctx context.Context, found Found) error\n\n/*\nIn order to resolve the issue with OnFoundCriRm, the same imageId under\nk8s.io is showing multiple results: repo:tag, repo:digest, configID. We expect\nto display only repo:tag, consistent with other namespaces and CRI.\ne.g.\n\n\tnerdctl -n k8s.io images\n\tREPOSITORY    TAG       IMAGE ID        CREATED        PLATFORM       SIZE         BLOB SIZE\n\tcentos        7         be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\tcentos        <none>    be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\t<none>        <none>    be65f488b776    3 hours ago    linux/amd64    211.5 MiB    72.6 MiB\n\nThe boolean value will return true only when the repo:tag is successfully\ndeleted for each image. Once all repo:tag entries are deleted, it is necessary\nto clean up the remaining repo:digest and configID.\n*/\ntype OnFoundCriRm func(ctx context.Context, found Found) (bool, error)\n\ntype ImageWalker struct {\n\tClient       *containerd.Client\n\tOnFound      OnFound\n\tOnFoundCriRm OnFoundCriRm\n}\n\n// Walk walks images and calls w.OnFound .\n// Req is name, short ID, or long ID.\n// Returns the number of the found entries.\nfunc (w *ImageWalker) Walk(ctx context.Context, req string) (int, error) {\n\tvar filters []string\n\tvar parsedReferenceStr string\n\n\tparsedReference, err := referenceutil.Parse(req)\n\tif err == nil {\n\t\tparsedReferenceStr = parsedReference.String()\n\t\tfilters = append(filters, fmt.Sprintf(\"name==%s\", parsedReferenceStr))\n\t}\n\tfilters = append(filters,\n\t\tfmt.Sprintf(\"name==%s\", req),\n\t\tfmt.Sprintf(\"target.digest~=^sha256:%s.*$\", regexp.QuoteMeta(req)),\n\t\tfmt.Sprintf(\"target.digest~=^%s.*$\", regexp.QuoteMeta(req)),\n\t)\n\n\timages, err := w.Client.ImageService().List(ctx, filters...)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\tmatchCount := len(images)\n\t// to handle the `rmi -f` case where returned images are different but\n\t// have the same short prefix.\n\tuniqueImages := make(map[digest.Digest]bool)\n\tnameMatchIndex := -1\n\tfor i, image := range images {\n\t\tuniqueImages[image.Target.Digest] = true\n\t\t// to get target image index for `nerdctl rmi <short digest ids of another images>`.\n\t\tif (parsedReferenceStr != \"\" && image.Name == parsedReferenceStr) || image.Name == req {\n\t\t\tnameMatchIndex = i\n\t\t}\n\t}\n\n\tfor i, img := range images {\n\t\tf := Found{\n\t\t\tImage:          img,\n\t\t\tReq:            req,\n\t\t\tMatchIndex:     i,\n\t\t\tMatchCount:     matchCount,\n\t\t\tUniqueImages:   len(uniqueImages),\n\t\t\tNameMatchIndex: nameMatchIndex,\n\t\t}\n\t\tif e := w.OnFound(ctx, f); e != nil {\n\t\t\treturn -1, e\n\t\t}\n\t}\n\treturn matchCount, nil\n}\n\n// WalkCriRm walks images and calls w.OnFoundCriRm .\n// Only effective when in the k8s.io namespace and kube-hide-dupe is enabled.\n// The WalkCriRm deletes non-repo:tag items such as repo:digest when in the no-other-repo:tag scenario.\nfunc (w *ImageWalker) WalkCriRm(ctx context.Context, req string) (int, error) {\n\tvar filters []string\n\tvar parsedReferenceStr, repo string\n\tvar imageTag, imagesRepo []images.Image\n\tvar tagNum int\n\n\tparsedReference, err := referenceutil.Parse(req)\n\tif err == nil {\n\t\tparsedReferenceStr = parsedReference.String()\n\t\tfilters = append(filters, fmt.Sprintf(\"name==%s\", parsedReferenceStr))\n\t}\n\t//Get the image ID , if reg == imageTag use\n\timage, err := w.Client.GetImage(ctx, parsedReferenceStr)\n\tif err != nil {\n\t\trepo = req\n\t} else {\n\t\trepo = strings.Split(image.Target().Digest.String(), \":\")[1][:12]\n\t}\n\n\tfilters = append(filters,\n\t\tfmt.Sprintf(\"name==%s\", req),\n\t\tfmt.Sprintf(\"target.digest~=^sha256:%s.*$\", regexp.QuoteMeta(repo)),\n\t\tfmt.Sprintf(\"target.digest~=^%s.*$\", regexp.QuoteMeta(repo)),\n\t)\n\n\timages, err := w.Client.ImageService().List(ctx, filters...)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\t// to handle the `rmi -f` case where returned images are different but\n\t// have the same short prefix.\n\tuniqueImages := make(map[digest.Digest]bool)\n\tnameMatchIndex := -1\n\n\t//Distinguish between tag and non-tag\n\tfor _, img := range images {\n\t\tref := img.Name\n\t\tparsed, err := referenceutil.Parse(ref)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif parsed.Tag != \"\" {\n\t\t\timageTag = append(imageTag, img)\n\t\t\ttagNum++\n\t\t\tuniqueImages[img.Target.Digest] = true\n\t\t\t// to get target image index for `nerdctl rmi <short digest ids of another images>`.\n\t\t\tif (parsedReferenceStr != \"\" && img.Name == parsedReferenceStr) || img.Name == req {\n\t\t\t\tnameMatchIndex = len(imageTag) - 1\n\t\t\t}\n\t\t} else {\n\t\t\timagesRepo = append(imagesRepo, img)\n\t\t}\n\t}\n\n\tmatchCount := len(imageTag)\n\tif matchCount < 1 && len(imagesRepo) > 0 {\n\t\tmatchCount = 1\n\t}\n\n\tfor i, img := range imageTag {\n\t\tf := Found{\n\t\t\tImage:          img,\n\t\t\tReq:            req,\n\t\t\tMatchIndex:     i,\n\t\t\tMatchCount:     matchCount,\n\t\t\tUniqueImages:   len(uniqueImages),\n\t\t\tNameMatchIndex: nameMatchIndex,\n\t\t}\n\t\tif ok, e := w.OnFoundCriRm(ctx, f); e != nil {\n\t\t\treturn -1, e\n\t\t} else if ok {\n\t\t\ttagNum = tagNum - 1\n\t\t}\n\t}\n\t//If the corresponding imageTag does not exist, delete the repoDigests\n\tif tagNum == 0 {\n\t\tfor i, img := range imagesRepo {\n\t\t\tf := Found{\n\t\t\t\tImage:          img,\n\t\t\t\tReq:            req,\n\t\t\t\tMatchIndex:     i,\n\t\t\t\tMatchCount:     1,\n\t\t\t\tUniqueImages:   1,\n\t\t\t\tNameMatchIndex: -1,\n\t\t\t}\n\t\t\tif _, e := w.OnFoundCriRm(ctx, f); e != nil {\n\t\t\t\treturn -1, e\n\t\t\t}\n\t\t}\n\t}\n\treturn matchCount, nil\n}\n\n// WalkAll calls `Walk` for each req in `reqs`.\n//\n// It can be used when the matchCount is not important (e.g., only care if there\n// is any error or if matchCount == 0 (not found error) when walking all reqs).\n// If `forceAll`, it calls `Walk` on every req\n// and return all errors joined by `\\n`. If not `forceAll`, it returns the first error\n// encountered while calling `Walk`.\nfunc (w *ImageWalker) WalkAll(ctx context.Context, reqs []string, forceAll bool) error {\n\tvar errs []string\n\tfor _, req := range reqs {\n\t\tn, err := w.Walk(ctx, req)\n\t\tif err == nil && n == 0 {\n\t\t\terr = fmt.Errorf(\"no such image: %s\", req)\n\t\t}\n\t\tif err != nil {\n\t\t\tif !forceAll {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrs = append(errs, err.Error())\n\t\t}\n\t}\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"%d errors:\\n%s\", len(errs), strings.Join(errs, \"\\n\"))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/imageinspector/imageinspector.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imageinspector\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n)\n\n// Inspect inspects the image, for the platform specified in image.platform.\nfunc Inspect(ctx context.Context, client *containerd.Client, image images.Image, snapshotter snapshots.Snapshotter) (*native.Image, error) {\n\n\tn := &native.Image{}\n\n\timg := containerd.NewImage(client, image)\n\tidx, idxDesc, err := imgutil.ReadIndex(ctx, img)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", image.Name).Warnf(\"failed to inspect index\")\n\t} else {\n\t\tn.IndexDesc = idxDesc\n\t\tn.Index = idx\n\t}\n\n\tmani, maniDesc, err := imgutil.ReadManifest(ctx, img)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", image.Name).Warnf(\"failed to inspect manifest\")\n\t} else {\n\t\tn.ManifestDesc = maniDesc\n\t\tn.Manifest = mani\n\t}\n\n\timageConfig, imageConfigDesc, err := imgutil.ReadImageConfig(ctx, img)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", image.Name).Warnf(\"failed to inspect image config\")\n\t} else {\n\t\tn.ImageConfigDesc = imageConfigDesc\n\t\tn.ImageConfig = imageConfig\n\t}\n\tn.Size, err = imgutil.UnpackedImageSize(ctx, snapshotter, img)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).WithField(\"id\", image.Name).Warnf(\"failed to inspect calculate size\")\n\t}\n\tn.Image = image\n\n\treturn n, nil\n}\n"
  },
  {
    "path": "pkg/imgutil/commit/commit.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage commit\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/klauspost/compress/zstd\"\n\t\"github.com/opencontainers/go-digest\"\n\t\"github.com/opencontainers/image-spec/identity\"\n\t\"github.com/opencontainers/image-spec/specs-go\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/diff\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/leases\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/containerd/v2/pkg/rootfs\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\t\"github.com/containerd/stargz-snapshotter/estargz\"\n\testargzconvert \"github.com/containerd/stargz-snapshotter/nativeconverter/estargz\"\n\tzstdchunkedconvert \"github.com/containerd/stargz-snapshotter/nativeconverter/zstdchunked\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/cmd/image\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\ntype Changes struct {\n\tCMD, Entrypoint []string\n}\n\ntype Opts struct {\n\tAuthor      string\n\tMessage     string\n\tRef         string\n\tPause       bool\n\tChanges     Changes\n\tCompression types.CompressionType\n\tFormat      types.ImageFormat\n\ttypes.EstargzOptions\n\ttypes.ZstdChunkedOptions\n}\n\nvar (\n\temptyGZLayer = digest.Digest(\"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1\")\n\temptyDigest  = digest.Digest(\"\")\n)\n\nfunc Commit(ctx context.Context, client *containerd.Client, container containerd.Container, opts *Opts, globalOptions types.GlobalCommandOptions) (digest.Digest, error) {\n\t// Get labels\n\tcontainerLabels, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\n\t// Get datastore\n\tdataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\n\t// Ensure we do have a stateDir label\n\tstateDir := containerLabels[labels.StateDir]\n\tif stateDir == \"\" {\n\t\tstateDir, err = containerutil.ContainerStateDirPath(globalOptions.Namespace, dataStore, container.ID())\n\t\tif err != nil {\n\t\t\treturn emptyDigest, err\n\t\t}\n\t}\n\n\tlf, err := containerutil.Lock(stateDir)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\tdefer lf.Release()\n\n\tid := container.ID()\n\tinfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\n\t// NOTE: Moby uses provided rootfs to run container. It doesn't support\n\t// to commit container created by moby.\n\tbaseImgWithoutPlatform, err := client.ImageService().Get(ctx, info.Image)\n\tif err != nil {\n\t\treturn emptyDigest, fmt.Errorf(\"container %q lacks image (wasn't created by nerdctl?): %w\", id, err)\n\t}\n\tplatformLabel := info.Labels[labels.Platform]\n\tif platformLabel == \"\" {\n\t\tplatformLabel = platforms.DefaultString()\n\t\tlog.G(ctx).Warnf(\"Image lacks label %q, assuming the platform to be %q\", labels.Platform, platformLabel)\n\t}\n\tocispecPlatform, err := platforms.Parse(platformLabel)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\tlog.G(ctx).Debugf(\"ocispecPlatform=%q\", platforms.Format(ocispecPlatform))\n\tplatformMC := platforms.Only(ocispecPlatform)\n\tbaseImg := containerd.NewImageWithPlatform(client, baseImgWithoutPlatform, platformMC)\n\n\tbaseImgConfig, _, err := imgutil.ReadImageConfig(ctx, baseImg)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\n\t// Ensure all the layers are here: https://github.com/containerd/nerdctl/issues/3425\n\terr = image.EnsureAllContent(ctx, client, baseImg.Name(), platformMC, globalOptions)\n\tif err != nil {\n\t\tlog.G(ctx).Warn(\"Unable to fetch missing layers before committing. \" +\n\t\t\t\"If you try to save or push this image, it might fail. See https://github.com/containerd/nerdctl/issues/3439.\")\n\t}\n\n\tif opts.Pause {\n\t\ttask, err := container.Task(ctx, cio.Load)\n\t\tif err != nil {\n\t\t\treturn emptyDigest, err\n\t\t}\n\n\t\tstatus, err := task.Status(ctx)\n\t\tif err != nil {\n\t\t\treturn emptyDigest, err\n\t\t}\n\n\t\tswitch status.Status {\n\t\tcase containerd.Paused, containerd.Created, containerd.Stopped:\n\t\tdefault:\n\t\t\tif err := task.Pause(ctx); err != nil {\n\t\t\t\treturn emptyDigest, fmt.Errorf(\"failed to pause container: %w\", err)\n\t\t\t}\n\n\t\t\tdefer func() {\n\t\t\t\tif err := task.Resume(ctx); err != nil {\n\t\t\t\t\tlog.G(ctx).Warnf(\"failed to unpause container %v: %v\", id, err)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n\n\tvar (\n\t\tdiffer = client.DiffService()\n\t\tsnName = info.Snapshotter\n\t\tsn     = client.SnapshotService(snName)\n\t)\n\n\t// Don't gc me and clean the dirty data after 1 hour!\n\tctx, done, err := client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))\n\tif err != nil {\n\t\treturn emptyDigest, fmt.Errorf(\"failed to create lease for commit: %w\", err)\n\t}\n\tdefer done(ctx)\n\n\t// Sync filesystem to make sure that all the data writes in container could be persisted to disk.\n\tSync()\n\n\tif opts.ZstdChunked {\n\t\topts.Compression = types.Zstd\n\t}\n\tdiffLayerDesc, diffID, err := createDiff(ctx, id, sn, client.ContentStore(), differ, opts.Compression, opts)\n\tif err != nil {\n\t\treturn emptyDigest, fmt.Errorf(\"failed to export layer: %w\", err)\n\t}\n\n\timageConfig, err := generateCommitImageConfig(ctx, container, baseImg, diffID, opts)\n\tif err != nil {\n\t\treturn emptyDigest, fmt.Errorf(\"failed to generate commit image config: %w\", err)\n\t}\n\n\trootfsID := identity.ChainID(imageConfig.RootFS.DiffIDs).String()\n\tif err := applyDiffLayer(ctx, rootfsID, baseImgConfig, sn, differ, diffLayerDesc); err != nil {\n\t\treturn emptyDigest, fmt.Errorf(\"failed to apply diff: %w\", err)\n\t}\n\n\tcommitManifestDesc, configDigest, err := writeContentsForImage(ctx, snName, baseImg, imageConfig, diffLayerDesc, opts)\n\tif err != nil {\n\t\treturn emptyDigest, err\n\t}\n\n\t// image create\n\timg := images.Image{\n\t\tName:      opts.Ref,\n\t\tTarget:    commitManifestDesc,\n\t\tCreatedAt: time.Now(),\n\t}\n\n\tif _, err := client.ImageService().Update(ctx, img); err != nil {\n\t\tif !errdefs.IsNotFound(err) {\n\t\t\treturn emptyDigest, err\n\t\t}\n\n\t\tif _, err := client.ImageService().Create(ctx, img); err != nil {\n\t\t\treturn emptyDigest, fmt.Errorf(\"failed to create new image %s: %w\", opts.Ref, err)\n\t\t}\n\t}\n\n\t// unpack the image to snapshotter\n\tcimg := containerd.NewImage(client, img)\n\tif err := cimg.Unpack(ctx, snName); err != nil {\n\t\treturn emptyDigest, err\n\t}\n\n\treturn configDigest, nil\n}\n\n// generateCommitImageConfig returns commit oci image config based on the container's image.\nfunc generateCommitImageConfig(ctx context.Context, container containerd.Container, img containerd.Image, diffID digest.Digest, opts *Opts) (ocispec.Image, error) {\n\tspec, err := container.Spec(ctx)\n\tif err != nil {\n\t\treturn ocispec.Image{}, err\n\t}\n\n\tbaseConfig, _, err := imgutil.ReadImageConfig(ctx, img) // aware of img.platform\n\tif err != nil {\n\t\treturn ocispec.Image{}, err\n\t}\n\n\t// TODO(fuweid): support updating the USER/ENV/... fields?\n\tif opts.Changes.CMD != nil {\n\t\tbaseConfig.Config.Cmd = opts.Changes.CMD\n\t}\n\tif opts.Changes.Entrypoint != nil {\n\t\tbaseConfig.Config.Entrypoint = opts.Changes.Entrypoint\n\t}\n\tif opts.Author == \"\" {\n\t\topts.Author = baseConfig.Author\n\t}\n\n\tcreatedBy := \"\"\n\tif spec.Process != nil {\n\t\tcreatedBy = strings.Join(spec.Process.Args, \" \")\n\t}\n\n\tcreatedTime := time.Now()\n\tarch := baseConfig.Architecture\n\tif arch == \"\" {\n\t\tarch = runtime.GOARCH\n\t\tlog.G(ctx).Warnf(\"assuming arch=%q\", arch)\n\t}\n\tos := baseConfig.OS\n\tif os == \"\" {\n\t\tos = runtime.GOOS\n\t\tlog.G(ctx).Warnf(\"assuming os=%q\", os)\n\t}\n\tlog.G(ctx).Debugf(\"generateCommitImageConfig(): arch=%q, os=%q\", arch, os)\n\treturn ocispec.Image{\n\t\tPlatform: ocispec.Platform{\n\t\t\tArchitecture: arch,\n\t\t\tOS:           os,\n\t\t},\n\n\t\tCreated: &createdTime,\n\t\tAuthor:  opts.Author,\n\t\tConfig:  baseConfig.Config,\n\t\tRootFS: ocispec.RootFS{\n\t\t\tType:    \"layers\",\n\t\t\tDiffIDs: append(baseConfig.RootFS.DiffIDs, diffID),\n\t\t},\n\t\tHistory: append(baseConfig.History, ocispec.History{\n\t\t\tCreated:    &createdTime,\n\t\t\tCreatedBy:  createdBy,\n\t\t\tAuthor:     opts.Author,\n\t\t\tComment:    opts.Message,\n\t\t\tEmptyLayer: (diffID == emptyGZLayer),\n\t\t}),\n\t}, nil\n}\n\n// writeContentsForImage will commit oci image config and manifest into containerd's content store.\nfunc writeContentsForImage(ctx context.Context, snName string, baseImg containerd.Image, newConfig ocispec.Image, diffLayerDesc ocispec.Descriptor, opts *Opts) (ocispec.Descriptor, digest.Digest, error) {\n\tnewConfigJSON, err := json.Marshal(newConfig)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, emptyDigest, err\n\t}\n\n\t// Select media types based on format choice\n\tvar configMediaType, manifestMediaType string\n\tswitch opts.Format {\n\tcase types.ImageFormatOCI:\n\t\tconfigMediaType = ocispec.MediaTypeImageConfig\n\t\tmanifestMediaType = ocispec.MediaTypeImageManifest\n\tcase types.ImageFormatDocker:\n\t\tconfigMediaType = images.MediaTypeDockerSchema2Config\n\t\tmanifestMediaType = images.MediaTypeDockerSchema2Manifest\n\tdefault:\n\t\t// Default to Docker Schema2 for compatibility\n\t\tconfigMediaType = images.MediaTypeDockerSchema2Config\n\t\tmanifestMediaType = images.MediaTypeDockerSchema2Manifest\n\t}\n\n\tconfigDesc := ocispec.Descriptor{\n\t\tMediaType: configMediaType,\n\t\tDigest:    digest.FromBytes(newConfigJSON),\n\t\tSize:      int64(len(newConfigJSON)),\n\t}\n\n\tcs := baseImg.ContentStore()\n\tbaseMfst, _, err := imgutil.ReadManifest(ctx, baseImg)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, emptyDigest, err\n\t}\n\tlayers := append(baseMfst.Layers, diffLayerDesc)\n\n\tnewMfst := struct {\n\t\tMediaType string `json:\"mediaType,omitempty\"`\n\t\tocispec.Manifest\n\t}{\n\t\tMediaType: manifestMediaType,\n\t\tManifest: ocispec.Manifest{\n\t\t\tVersioned: specs.Versioned{\n\t\t\t\tSchemaVersion: 2,\n\t\t\t},\n\t\t\tConfig: configDesc,\n\t\t\tLayers: layers,\n\t\t},\n\t}\n\n\tnewMfstJSON, err := json.MarshalIndent(newMfst, \"\", \"    \")\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, emptyDigest, err\n\t}\n\n\tnewMfstDesc := ocispec.Descriptor{\n\t\tMediaType: manifestMediaType,\n\t\tDigest:    digest.FromBytes(newMfstJSON),\n\t\tSize:      int64(len(newMfstJSON)),\n\t}\n\n\t// new manifest should reference the layers and config content\n\tlabels := map[string]string{\n\t\t\"containerd.io/gc.ref.content.0\": configDesc.Digest.String(),\n\t}\n\tfor i, l := range layers {\n\t\tlabels[fmt.Sprintf(\"containerd.io/gc.ref.content.%d\", i+1)] = l.Digest.String()\n\t}\n\n\terr = content.WriteBlob(ctx, cs, newMfstDesc.Digest.String(), bytes.NewReader(newMfstJSON), newMfstDesc, content.WithLabels(labels))\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, emptyDigest, err\n\t}\n\n\t// config should reference to snapshotter\n\tlabelOpt := content.WithLabels(map[string]string{\n\t\tfmt.Sprintf(\"containerd.io/gc.ref.snapshot.%s\", snName): identity.ChainID(newConfig.RootFS.DiffIDs).String(),\n\t})\n\terr = content.WriteBlob(ctx, cs, configDesc.Digest.String(), bytes.NewReader(newConfigJSON), configDesc, labelOpt)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, emptyDigest, err\n\t}\n\n\treturn newMfstDesc, configDesc.Digest, nil\n}\n\n// createDiff creates a layer diff into containerd's content store.\nfunc createDiff(ctx context.Context, name string, sn snapshots.Snapshotter, cs content.Store, comparer diff.Comparer, compression types.CompressionType, opts *Opts) (ocispec.Descriptor, digest.Digest, error) {\n\tdiffOpts := make([]diff.Opt, 0)\n\tvar mediaType string\n\n\t// Select media type based on format and compression\n\tswitch opts.Format {\n\tcase types.ImageFormatOCI:\n\t\t// Use OCI media types\n\t\tswitch compression {\n\t\tcase types.Zstd:\n\t\t\tdiffOpts = append(diffOpts, diff.WithMediaType(ocispec.MediaTypeImageLayerZstd))\n\t\t\tmediaType = ocispec.MediaTypeImageLayerZstd\n\t\tdefault:\n\t\t\tdiffOpts = append(diffOpts, diff.WithMediaType(ocispec.MediaTypeImageLayerGzip))\n\t\t\tmediaType = ocispec.MediaTypeImageLayerGzip\n\t\t}\n\tcase types.ImageFormatDocker:\n\t\t// Use Docker Schema2 media types for compatibility\n\t\tswitch compression {\n\t\tcase types.Zstd:\n\t\t\tdiffOpts = append(diffOpts, diff.WithMediaType(ocispec.MediaTypeImageLayerZstd))\n\t\t\tmediaType = images.MediaTypeDockerSchema2LayerZstd\n\t\tdefault:\n\t\t\tdiffOpts = append(diffOpts, diff.WithMediaType(ocispec.MediaTypeImageLayerGzip))\n\t\t\tmediaType = images.MediaTypeDockerSchema2LayerGzip\n\t\t}\n\tdefault:\n\t\t// Default to Docker Schema2 media types for compatibility\n\t\tswitch compression {\n\t\tcase types.Zstd:\n\t\t\tdiffOpts = append(diffOpts, diff.WithMediaType(ocispec.MediaTypeImageLayerZstd))\n\t\t\tmediaType = images.MediaTypeDockerSchema2LayerZstd\n\t\tdefault:\n\t\t\tdiffOpts = append(diffOpts, diff.WithMediaType(ocispec.MediaTypeImageLayerGzip))\n\t\t\tmediaType = images.MediaTypeDockerSchema2LayerGzip\n\t\t}\n\t}\n\n\tnewDesc, err := rootfs.CreateDiff(ctx, name, sn, comparer, diffOpts...)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t}\n\n\tinfo, err := cs.Info(ctx, newDesc.Digest)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t}\n\n\tdiffIDStr, ok := info.Labels[\"containerd.io/uncompressed\"]\n\tif !ok {\n\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), fmt.Errorf(\"invalid differ response with no diffID\")\n\t}\n\n\tdiffID, err := digest.Parse(diffIDStr)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t}\n\n\t// Convert to eStargz if requested\n\tif opts.Estargz {\n\t\tlog.G(ctx).Infof(\"Converting diff layer to eStargz format\")\n\n\t\tesgzOpts := []estargz.Option{\n\t\t\testargz.WithCompressionLevel(opts.EstargzCompressionLevel),\n\t\t}\n\t\tif opts.EstargzChunkSize > 0 {\n\t\t\tesgzOpts = append(esgzOpts, estargz.WithChunkSize(opts.EstargzChunkSize))\n\t\t}\n\t\tif opts.EstargzMinChunkSize > 0 {\n\t\t\tesgzOpts = append(esgzOpts, estargz.WithMinChunkSize(opts.EstargzMinChunkSize))\n\t\t}\n\n\t\tconvertFunc := estargzconvert.LayerConvertFunc(esgzOpts...)\n\n\t\tesgzDesc, err := convertFunc(ctx, cs, newDesc)\n\t\tif err != nil {\n\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), fmt.Errorf(\"failed to convert diff layer to eStargz: %w\", err)\n\t\t} else if esgzDesc != nil {\n\t\t\tesgzDesc.MediaType = mediaType\n\t\t\tesgzInfo, err := cs.Info(ctx, esgzDesc.Digest)\n\t\t\tif err != nil {\n\t\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t\t\t}\n\n\t\t\tesgzDiffIDStr, ok := esgzInfo.Labels[\"containerd.io/uncompressed\"]\n\t\t\tif !ok {\n\t\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), fmt.Errorf(\"invalid differ response with no diffID\")\n\t\t\t}\n\n\t\t\tesgzDiffID, err := digest.Parse(esgzDiffIDStr)\n\t\t\tif err != nil {\n\t\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t\t\t}\n\t\t\treturn ocispec.Descriptor{\n\t\t\t\tMediaType:   esgzDesc.MediaType,\n\t\t\t\tDigest:      esgzDesc.Digest,\n\t\t\t\tSize:        esgzDesc.Size,\n\t\t\t\tAnnotations: esgzDesc.Annotations,\n\t\t\t}, esgzDiffID, nil\n\t\t}\n\t}\n\n\t// Convert to zstd:chunked if requested\n\tif opts.ZstdChunked {\n\t\tlog.G(ctx).Infof(\"Converting diff layer to zstd:chunked format\")\n\n\t\tesgzOpts := []estargz.Option{\n\t\t\testargz.WithChunkSize(opts.ZstdChunkedChunkSize),\n\t\t}\n\n\t\tconvertFunc := zstdchunkedconvert.LayerConvertFuncWithCompressionLevel(zstd.EncoderLevelFromZstd(opts.ZstdChunkedCompressionLevel), esgzOpts...)\n\n\t\tzstdchunkedDesc, err := convertFunc(ctx, cs, newDesc)\n\t\tif err != nil {\n\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), fmt.Errorf(\"failed to convert diff layer to zstd:chunked: %w\", err)\n\t\t} else if zstdchunkedDesc != nil {\n\t\t\tzstdchunkedDesc.MediaType = mediaType\n\t\t\tzstdchunkedInfo, err := cs.Info(ctx, zstdchunkedDesc.Digest)\n\t\t\tif err != nil {\n\t\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t\t\t}\n\n\t\t\tzstdchunkedDiffIDStr, ok := zstdchunkedInfo.Labels[\"containerd.io/uncompressed\"]\n\t\t\tif !ok {\n\t\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), fmt.Errorf(\"invalid differ response with no diffID\")\n\t\t\t}\n\n\t\t\tzstdchunkedDiffID, err := digest.Parse(zstdchunkedDiffIDStr)\n\t\t\tif err != nil {\n\t\t\t\treturn ocispec.Descriptor{}, digest.Digest(\"\"), err\n\t\t\t}\n\t\t\treturn ocispec.Descriptor{\n\t\t\t\tMediaType:   zstdchunkedDesc.MediaType,\n\t\t\t\tDigest:      zstdchunkedDesc.Digest,\n\t\t\t\tSize:        zstdchunkedDesc.Size,\n\t\t\t\tAnnotations: zstdchunkedDesc.Annotations,\n\t\t\t}, zstdchunkedDiffID, nil\n\t\t}\n\t}\n\n\treturn ocispec.Descriptor{\n\t\tMediaType: mediaType,\n\t\tDigest:    newDesc.Digest,\n\t\tSize:      info.Size,\n\t}, diffID, nil\n}\n\n// applyDiffLayer will apply diff layer content created by createDiff into the snapshotter.\nfunc applyDiffLayer(ctx context.Context, name string, baseImg ocispec.Image, sn snapshots.Snapshotter, differ diff.Applier, diffDesc ocispec.Descriptor) (retErr error) {\n\tvar (\n\t\tkey    = uniquePart() + \"-\" + name\n\t\tparent = identity.ChainID(baseImg.RootFS.DiffIDs).String()\n\t)\n\n\tmount, err := sn.Prepare(ctx, key, parent)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif retErr != nil {\n\t\t\t// NOTE: the snapshotter should be hold by lease. Even\n\t\t\t// if the cleanup fails, the containerd gc can delete it.\n\t\t\tif err := sn.Remove(ctx, key); err != nil {\n\t\t\t\tlog.G(ctx).Warnf(\"failed to cleanup aborted apply %s: %s\", key, err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tif _, err = differ.Apply(ctx, diffDesc, mount); err != nil {\n\t\treturn err\n\t}\n\n\tif err = sn.Commit(ctx, name, key); err != nil {\n\t\tif errdefs.IsAlreadyExists(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// copied from github.com/containerd/containerd/rootfs/apply.go\nfunc uniquePart() string {\n\tt := time.Now()\n\tvar b [3]byte\n\t// Ignore read failures, just decreases uniqueness\n\trand.Read(b[:])\n\treturn fmt.Sprintf(\"%d-%s\", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))\n}\n"
  },
  {
    "path": "pkg/imgutil/commit/commit_other.go",
    "content": "//go:build !unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage commit\n\nfunc Sync() {\n\n}\n"
  },
  {
    "path": "pkg/imgutil/commit/commit_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage commit\n\nimport \"syscall\"\n\nfunc Sync() {\n\tsyscall.Sync()\n}\n"
  },
  {
    "path": "pkg/imgutil/converter/convert.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage converter\n\nimport (\n\t\"context\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n)\n\n// Something seems wrong in converter.Convert.\n// When dstRef != srcRef, convert will first forcefully delete dstRef,\n// *asynchronously*, then create the image.\n// This seems to cause a race conditions, and the deletion may kick in after the creation.\n// This here is to workaround the bug, by manually creating the image first,\n// then converting it in place (which avoid the problematic code-path).\n// See containerd upstream discussion https://github.com/containerd/containerd/pull/11628 and\n// nerdctl issues:\n// https://github.com/containerd/nerdctl/issues/3509#issuecomment-2398236766\n// https://github.com/containerd/nerdctl/issues/3513\n// Note this should be remove if/when containerd merges in a fix.\n\nfunc Convert(ctx context.Context, client converter.Client, dstRef, srcRef string, opts ...converter.Opt) (*images.Image, error) {\n\timageService := client.ImageService()\n\n\timg, err := imageService.Get(ctx, srcRef)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\timg.Name = dstRef\n\n\t_ = imageService.Delete(ctx, img.Name, images.SynchronousDelete())\n\n\tif _, err = imageService.Create(ctx, img); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn converter.Convert(ctx, client, dstRef, dstRef, opts...)\n}\n"
  },
  {
    "path": "pkg/imgutil/converter/info.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage converter\n\n// ConvertedImageInfo is information of the images created by a conversion.\ntype ConvertedImageInfo struct {\n\t// Image is the reference of the converted image.\n\t// The reference is the image's name and digest concatenated with \"@\" (i.e. `<name>@<digest>`).\n\tImage string `json:\"Image\"`\n\n\t// ExtraImages is a set of converter-specific additional images (e.g. external TOC image of eStargz).\n\t// The reference format is the same as the \"Image\" field.\n\tExtraImages []string `json:\"ExtraImages\"`\n}\n"
  },
  {
    "path": "pkg/imgutil/converter/zstd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage converter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/klauspost/compress/zstd\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n\t\"github.com/containerd/containerd/v2/core/images/converter/uncompress\"\n\t\"github.com/containerd/containerd/v2/pkg/archive/compression\"\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\n// ZstdLayerConvertFunc converts legacy tar.gz layers into zstd layers with\n// the specified compression level.\nfunc ZstdLayerConvertFunc(options types.ImageConvertOptions) (converter.ConvertFunc, error) {\n\treturn func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {\n\t\tif !images.IsLayerType(desc.MediaType) {\n\t\t\t// No conversion. No need to return an error here.\n\t\t\treturn nil, nil\n\t\t}\n\t\tvar err error\n\t\t// Read it\n\t\treaderAt, err := cs.ReaderAt(ctx, desc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer readerAt.Close()\n\t\tsectionReader := io.NewSectionReader(readerAt, 0, desc.Size)\n\n\t\tinfo, err := cs.Info(ctx, desc.Digest)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar oldReader io.Reader\n\t\t// If it is compressed, get a decompressed stream\n\t\tif !uncompress.IsUncompressedType(desc.MediaType) {\n\t\t\tdecompStream, err := compression.DecompressStream(sectionReader)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdefer decompStream.Close()\n\t\t\toldReader = decompStream\n\t\t} else {\n\t\t\toldReader = sectionReader\n\t\t}\n\n\t\tref := fmt.Sprintf(\"convert-zstd-from-%s\", desc.Digest)\n\t\tw, err := content.OpenWriter(ctx, cs, content.WithRef(ref))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer w.Close()\n\n\t\t// Reset the writing position\n\t\t// Old writer possibly remains without aborted\n\t\t// (e.g. conversion interrupted by a signal)\n\t\tif err := w.Truncate(0); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpr, pw := io.Pipe()\n\t\tenc, err := zstd.NewWriter(pw, zstd.WithEncoderLevel(zstd.EncoderLevelFromZstd(options.ZstdCompressionLevel)))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgo func() {\n\t\t\tif _, err := io.Copy(enc, oldReader); err != nil {\n\t\t\t\tpr.CloseWithError(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = enc.Close(); err != nil {\n\t\t\t\tpr.CloseWithError(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = pw.Close(); err != nil {\n\t\t\t\tpr.CloseWithError(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tn, err := io.Copy(w, pr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err = w.Commit(ctx, 0, \"\", content.WithLabels(info.Labels)); err != nil && !errdefs.IsAlreadyExists(err) {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := w.Close(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnewDesc := desc\n\t\tnewDesc.Digest = w.Digest()\n\t\tnewDesc.Size = n\n\t\tnewDesc.MediaType = ocispec.MediaTypeImageLayerZstd\n\t\treturn &newDesc, nil\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/credentialsstore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/docker/cli/cli/config\"\n\t\"github.com/docker/cli/cli/config/configfile\"\n\t\"github.com/docker/cli/cli/config/types\"\n)\n\ntype Credentials = types.AuthConfig\n\n// NewCredentialsStore returns a CredentialsStore from a directory\n// If path is left empty, the default docker `~/.docker/config.json` will be used\n// In case the docker call fails, we wrap the error with ErrUnableToInstantiate\nfunc NewCredentialsStore(path string) (*CredentialsStore, error) {\n\tdockerConfigFile, err := config.Load(path)\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrUnableToInstantiate, err)\n\t}\n\n\treturn &CredentialsStore{\n\t\tdockerConfigFile: dockerConfigFile,\n\t}, nil\n}\n\n// CredentialsStore is an abstraction in front of docker config API manipulation\n// exposing just the limited functions we need and hiding away url normalization / identifiers magic, and handling of\n// backward compatibility\ntype CredentialsStore struct {\n\tdockerConfigFile *configfile.ConfigFile\n}\n\n// Erase will remove any and all stored credentials for that registry namespace (including all legacy variants)\n// If we do not find at least ONE variant matching the namespace, this will error with ErrUnableToErase\nfunc (cs *CredentialsStore) Erase(registryURL *RegistryURL) (map[string]error, error) {\n\t// Get all associated identifiers for that registry including legacy ones and variants\n\tlogoutList := registryURL.AllIdentifiers()\n\n\t// Iterate through and delete them one by one\n\terrs := make(map[string]error)\n\tfor _, serverAddress := range logoutList {\n\t\tif err := cs.dockerConfigFile.GetCredentialsStore(serverAddress).Erase(serverAddress); err != nil {\n\t\t\terrs[serverAddress] = err\n\t\t}\n\t}\n\n\t// If we succeeded removing at least one, it is a success.\n\t// The only error condition is if we failed removing anything - meaning there was no such credential information\n\t// in whatever format - or the store is broken.\n\tif len(errs) == len(logoutList) {\n\t\treturn errs, ErrUnableToErase\n\t}\n\n\treturn nil, nil\n}\n\n// Store will save credentials for a given registry\n// On error, ErrUnableToStore\nfunc (cs *CredentialsStore) Store(registryURL *RegistryURL, credentials *Credentials) error {\n\t// We just overwrite the server property here with the host\n\t// Whether it was one of the variants, or was not set at all (see for example Amazon ECR, https://github.com/containerd/nerdctl/issues/733\n\t// - which is likely a bug in docker) it doesn't matter.\n\t// This is the credentials that were returned for that host, by the docker credentials store.\n\tif registryURL.Namespace != nil {\n\t\tcredentials.ServerAddress = fmt.Sprintf(\"%s%s?%s\", registryURL.Host, registryURL.Path, registryURL.RawQuery)\n\t} else {\n\t\tcredentials.ServerAddress = registryURL.CanonicalIdentifier()\n\t}\n\n\t// XXX future namespaced url likely require special handling here\n\tif err := cs.dockerConfigFile.GetCredentialsStore(registryURL.CanonicalIdentifier()).Store(*(credentials)); err != nil {\n\t\treturn errors.Join(ErrUnableToStore, err)\n\t}\n\n\treturn nil\n}\n\n// ShellCompletion will return candidate strings for nerdctl logout\nfunc (cs *CredentialsStore) ShellCompletion() []string {\n\tcandidates := []string{}\n\tfor key := range cs.dockerConfigFile.AuthConfigs {\n\t\tcandidates = append(candidates, key)\n\t}\n\n\treturn candidates\n}\n\n// FileStorageLocation will return the file where credentials are stored for a given registry, or the empty string\n// if it is stored / to be stored in a different place (like an OS keychain, with docker credential helpers)\nfunc (cs *CredentialsStore) FileStorageLocation(registryURL *RegistryURL) string {\n\tif store, isFile := (cs.dockerConfigFile.GetCredentialsStore(registryURL.CanonicalIdentifier())).(isFileStore); isFile {\n\t\treturn store.GetFilename()\n\t}\n\n\treturn \"\"\n}\n\n// Retrieve gets existing credentials from the store for a certain registry.\n// If none are found, an empty Credentials struct is returned.\n// If we hard-fail reading from the store, indicative of a broken system, we wrap the error with ErrUnableToRetrieve\nfunc (cs *CredentialsStore) Retrieve(registryURL *RegistryURL, checkCredStore bool) (*Credentials, error) {\n\tvar err error\n\treturnedCredentials := &Credentials{}\n\n\t// As long as we depend on .ServerAddress, make sure it is populated correctly\n\t// It does not matter what was stored - the docker cli clearly has issues with this\n\t// What matters is that the credentials retrieved from the docker credentials store are *for that registryURL*\n\t// and that is what ServerAddress should point to\n\tdefer func() {\n\t\tif registryURL.Namespace != nil {\n\t\t\treturnedCredentials.ServerAddress = fmt.Sprintf(\"%s%s?%s\", registryURL.Host, registryURL.Path, registryURL.RawQuery)\n\t\t} else {\n\t\t\treturnedCredentials.ServerAddress = registryURL.Host\n\t\t}\n\t}()\n\n\tif !checkCredStore {\n\t\treturn returnedCredentials, nil\n\t}\n\n\t// Get the legacy variants (w/o scheme or port), and iterate over until we find one with credentials\n\tvariants := registryURL.AllIdentifiers()\n\n\tfor _, identifier := range variants {\n\t\tvar credentials types.AuthConfig\n\t\t// Note that Get does not raise an error on ENOENT\n\t\tcredentials, err = cs.dockerConfigFile.GetCredentialsStore(identifier).Get(identifier)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\treturnedCredentials = &credentials\n\t\t// Clean-up the username\n\t\treturnedCredentials.Username = strings.TrimSpace(returnedCredentials.Username)\n\t\t// Stop here if we found credentials with this variant\n\t\tif returnedCredentials.IdentityToken != \"\" ||\n\t\t\treturnedCredentials.Username != \"\" ||\n\t\t\treturnedCredentials.Password != \"\" ||\n\t\t\treturnedCredentials.RegistryToken != \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// (Last non nil) credential store error gets wrapped into ErrUnableToRetrieve\n\tif err != nil {\n\t\terr = errors.Join(ErrUnableToRetrieve, err)\n\t}\n\n\treturn returnedCredentials, err\n}\n\n// isFileStore is an internal mock interface purely meant to help identify that the docker credential backend is a filesystem one\ntype isFileStore interface {\n\tIsFileStore() bool\n\tGetFilename() string\n}\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/credentialsstore_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc createTempDir(t *testing.T, mode os.FileMode) string {\n\ttmpDir, err := os.MkdirTemp(t.TempDir(), \"docker-config\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = os.Chmod(tmpDir, mode)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn tmpDir\n}\n\nfunc TestBrokenCredentialsStore(t *testing.T) {\n\tif !rootlessutil.IsRootless() {\n\t\tt.Skip(\"test is for rootless\")\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\t// Same as above\n\t\tt.Skip(\"test is not compatible with windows\")\n\t}\n\n\ttestCases := []struct {\n\t\tdescription string\n\t\tsetup       func() string\n\t\terrorNew    error\n\t\terrorRead   error\n\t\terrorWrite  error\n\t}{\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a non-existent directory inside an unreadable directory will prevent instantiation\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0000)\n\t\t\t\treturn filepath.Join(tmpDir, \"doesnotexistcantcreate\")\n\t\t\t},\n\t\t\terrorNew: ErrUnableToInstantiate,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a non-existent directory inside a read-only directory will prevent saving credentials\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0500)\n\t\t\t\treturn filepath.Join(tmpDir, \"doesnotexistcantcreate\")\n\t\t\t},\n\t\t\terrorWrite: ErrUnableToStore,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at an unreadable directory will prevent instantiation\",\n\t\t\tsetup: func() string {\n\t\t\t\treturn createTempDir(t, 0000)\n\t\t\t},\n\t\t\terrorNew: ErrUnableToInstantiate,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a read-only directory will prevent saving credentials\",\n\t\t\tsetup: func() string {\n\t\t\t\treturn createTempDir(t, 0500)\n\t\t\t},\n\t\t\terrorWrite: ErrUnableToStore,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a directory containing am unparsable `config.json` will prevent instantiation\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0700)\n\t\t\t\terr := filesystem.WriteFile(filepath.Join(tmpDir, \"config.json\"), []byte(\"porked\"), 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn tmpDir\n\t\t\t},\n\t\t\terrorNew: ErrUnableToInstantiate,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a file instead of a directory will prevent instantiation\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0700)\n\t\t\t\tfd, err := os.OpenFile(filepath.Join(tmpDir, \"isafile\"), os.O_CREATE, 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\terr = fd.Close()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn filepath.Join(tmpDir, \"isafile\")\n\t\t\t},\n\t\t\terrorNew: ErrUnableToInstantiate,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a directory containing a `config.json` directory will prevent instantiation\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0700)\n\t\t\t\terr := os.Mkdir(filepath.Join(tmpDir, \"config.json\"), 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn tmpDir\n\t\t\t},\n\t\t\terrorNew: ErrUnableToInstantiate,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a directory containing a `config.json` dangling symlink will still work\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0700)\n\t\t\t\terr := os.Symlink(\"doesnotexist\", filepath.Join(tmpDir, \"config.json\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn tmpDir\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a directory containing an unreadable, valid `config.json` file will prevent instantiation\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0700)\n\t\t\t\terr := filesystem.WriteFile(filepath.Join(tmpDir, \"config.json\"), []byte(\"{}\"), 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\terr = os.Chmod(filepath.Join(tmpDir, \"config.json\"), 0000)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn tmpDir\n\t\t\t},\n\t\t\terrorNew: ErrUnableToInstantiate,\n\t\t},\n\t\t{\n\t\t\tdescription: \"Pointing DOCKER_CONFIG at a directory containing a read-only, valid `config.json` file will NOT prevent saving credentials\",\n\t\t\tsetup: func() string {\n\t\t\t\ttmpDir := createTempDir(t, 0700)\n\t\t\t\terr := filesystem.WriteFile(filepath.Join(tmpDir, \"config.json\"), []byte(\"{}\"), 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\terr = os.Chmod(filepath.Join(tmpDir, \"config.json\"), 0400)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\treturn tmpDir\n\t\t\t},\n\t\t},\n\t}\n\n\tt.Run(\"Docker Config testing with a variety of filesystem situations\", func(t *testing.T) {\n\t\t// Do NOT parallelize this test, as it relies on Chdir, which would have side effects for other tests.\n\t\tregistryURL, err := Parse(\"registry\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfor _, testCase := range testCases {\n\t\t\ttc := testCase\n\t\t\tt.Run(tc.description, func(tt *testing.T) {\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/3413\n\t\t\t\tvar oldpwd string\n\t\t\t\tdirectory := tc.setup()\n\t\t\t\toldpwd, err = os.Getwd()\n\t\t\t\tassert.NilError(tt, err)\n\t\t\t\t// Ignore the error, as the destination may not be a directory\n\t\t\t\t_ = os.Chdir(directory)\n\t\t\t\ttt.Cleanup(func() {\n\t\t\t\t\terr = os.Chdir(oldpwd)\n\t\t\t\t\tassert.NilError(tt, err)\n\t\t\t\t})\n\n\t\t\t\tvar cs *CredentialsStore\n\t\t\t\tcs, err = NewCredentialsStore(directory)\n\t\t\t\tassert.ErrorIs(tt, err, tc.errorNew)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tvar af *Credentials\n\t\t\t\taf, err = cs.Retrieve(registryURL, true)\n\t\t\t\tassert.ErrorIs(tt, err, tc.errorRead)\n\n\t\t\t\terr = cs.Store(registryURL, af)\n\t\t\t\tassert.ErrorIs(tt, err, tc.errorWrite)\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc writeContent(t *testing.T, content string) string {\n\tt.Helper()\n\ttmpDir := createTempDir(t, 0700)\n\terr := filesystem.WriteFile(filepath.Join(tmpDir, \"config.json\"), []byte(content), 0600)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn tmpDir\n}\n\nfunc TestWorkingCredentialsStore(t *testing.T) {\n\ttestCases := []struct {\n\t\tdescription string\n\t\tsetup       func() string\n\t\tusername    string\n\t\tpassword    string\n\t}{\n\t\t{\n\t\t\tdescription: \"Reading credentials from `auth` using canonical identifier\",\n\t\t\tusername:    \"username\",\n\t\t\tpassword:    \"password\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := fmt.Sprintf(`{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"registry.example:443\": {\n\t\t\t\t\t\t\"auth\": %q\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`, base64.StdEncoding.EncodeToString([]byte(\"username:password\")))\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Reading from legacy / alternative identifiers: registry.example\",\n\t\t\tusername:    \"username\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := `{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"registry.example\": {\n\t\t\t\t\t\t\"username\": \"username\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Reading from legacy / alternative identifiers: http://registry.example\",\n\t\t\tusername:    \"username\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := `{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"http://registry.example\": {\n\t\t\t\t\t\t\"username\": \"username\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Reading from legacy / alternative identifiers: https://registry.example\",\n\t\t\tusername:    \"username\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := `{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"https://registry.example\": {\n\t\t\t\t\t\t\"username\": \"username\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Reading from legacy / alternative identifiers: http://registry.example:443\",\n\t\t\tusername:    \"username\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := `{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"http://registry.example:443\": {\n\t\t\t\t\t\t\"username\": \"username\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Reading from legacy / alternative identifiers: https://registry.example:443\",\n\t\t\tusername:    \"username\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := `{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"https://registry.example:443\": {\n\t\t\t\t\t\t\"username\": \"username\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"Canonical form is preferred over legacy forms\",\n\t\t\tusername:    \"pick\",\n\t\t\tsetup: func() string {\n\t\t\t\tcontent := `{\n\t\"auths\": {\n\t\t\"http://registry.example:443\": {\n\t\t\t\"username\": \"ignore\"\n\t\t},\n\t\t\"https://registry.example:443\": {\n\t\t\t\"username\": \"ignore\"\n\t\t},\n\t\t\"registry.example\": {\n\t\t\t\"username\": \"ignore\"\n\t\t},\n\t\t\"registry.example:443\": {\n\t\t\t\"serveraddress\": \"bla\",\n\t\t\t\"username\": \"pick\"\n\t\t},\n\t\t\"http://registry.example\": {\n\t\t\t\"username\": \"ignore\"\n\t\t},\n\t\t\"https://registry.example\": {\n\t\t\t\"username\": \"ignore\"\n\t\t}\n\t}\n}`\n\t\t\t\treturn writeContent(t, content)\n\t\t\t},\n\t\t},\n\t}\n\n\tt.Run(\"Working credentials store\", func(t *testing.T) {\n\n\t\tfor _, tc := range testCases {\n\t\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\t\tregistryURL, err := Parse(\"registry.example\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tcs, err := NewCredentialsStore(tc.setup())\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tvar af *Credentials\n\t\t\t\taf, err = cs.Retrieve(registryURL, true)\n\t\t\t\tassert.ErrorIs(t, err, nil)\n\t\t\t\tassert.Equal(t, af.Username, tc.username)\n\t\t\t\tassert.Equal(t, af.ServerAddress, \"registry.example:443\")\n\t\t\t\tassert.Equal(t, af.Password, tc.password)\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"Namespaced host\", func(t *testing.T) {\n\t\tserver := \"host.example/path?ns=namespace.example\"\n\t\tregistryURL, err := Parse(server)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tcontent := `{\n\t\t\t\t\"auths\": {\n\t\t\t\t\t\"nerdctl-experimental://namespace.example:443/host/host.example:443/path\": {\n\t\t\t\t\t\t\"username\": \"username\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`\n\t\tdir := writeContent(t, content)\n\t\tcs, err := NewCredentialsStore(dir)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tvar af *Credentials\n\t\taf, err = cs.Retrieve(registryURL, true)\n\t\tassert.ErrorIs(t, err, nil)\n\t\tassert.Equal(t, af.Username, \"username\")\n\t\tassert.Equal(t, af.ServerAddress, \"host.example:443/path?ns=namespace.example\")\n\n\t})\n}\n\n// TODO: add more tests that write credentials (specifically to hub locations) to verify they use the canonical id properly\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/defaults.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport \"errors\"\n\ntype scheme string\n\nconst (\n\tStandardHTTPSPort = \"443\"\n\n\tschemeHTTPS scheme = \"https\"\n\tschemeHTTP  scheme = \"http\"\n\t// schemeNerdctlExperimental is currently provisional, to unlock namespace based host authentication\n\t// This may change or break without notice, and you should have no expectations that credentials saved like that\n\t// will be supported in the future\n\tschemeNerdctlExperimental scheme = \"nerdctl-experimental\"\n\t// See https://github.com/moby/moby/blob/v27.1.1/registry/config.go#L42-L48\n\t//nolint:misspell\n\t// especially Sebastiaan comments on future domain consolidation\n\tdockerIndexServer = \"https://index.docker.io/v1/\"\n\t// The query parameter that containerd will slap on namespaced hosts\n\tnamespaceQueryParameter = \"ns\"\n)\n\n// Errors returned by the credentials store\nvar (\n\tErrUnableToInstantiate = errors.New(\"unable to instantiate docker credentials store\")\n\tErrUnableToErase       = errors.New(\"unable to erase credentials\")\n\tErrUnableToStore       = errors.New(\"unable to store credentials\")\n\tErrUnableToRetrieve    = errors.New(\"unable to retrieve credentials\")\n)\n\n// Errors returned by `Parse`\nvar (\n\tErrUnparsableURL     = errors.New(\"unparsable registry URL\")\n\tErrUnsupportedScheme = errors.New(\"unsupported scheme in registry URL\")\n)\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/dockerconfigresolver.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/pelletier/go-toml/v2\"\n\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/containerd/v2/core/remotes/docker\"\n\tdockerconfig \"github.com/containerd/containerd/v2/core/remotes/docker/config\"\n\t\"github.com/containerd/containerd/v2/core/transfer/registry\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n)\n\nvar PushTracker = docker.NewInMemoryTracker()\n\ntype opts struct {\n\tplainHTTP       bool\n\tskipVerifyCerts bool\n\thostsDirs       []string\n\tauthCreds       AuthCreds\n}\n\n// Opt for New\ntype Opt func(*opts)\n\n// WithPlainHTTP enables insecure plain HTTP\nfunc WithPlainHTTP(b bool) Opt {\n\treturn func(o *opts) {\n\t\to.plainHTTP = b\n\t}\n}\n\n// WithSkipVerifyCerts skips verifying TLS certs\nfunc WithSkipVerifyCerts(b bool) Opt {\n\treturn func(o *opts) {\n\t\to.skipVerifyCerts = b\n\t}\n}\n\n// WithHostsDirs specifies directories like /etc/containerd/certs.d and /etc/docker/certs.d\nfunc WithHostsDirs(orig []string) Opt {\n\tvalidDirs := validateDirectories(orig)\n\treturn func(o *opts) {\n\t\to.hostsDirs = validDirs\n\t}\n}\n\nfunc WithAuthCreds(ac AuthCreds) Opt {\n\treturn func(o *opts) {\n\t\to.authCreds = ac\n\t}\n}\n\n// NewHostOptions instantiates a HostOptions struct using $DOCKER_CONFIG/config.json .\n//\n// $DOCKER_CONFIG defaults to \"~/.docker\".\n//\n// refHostname is like \"docker.io\".\nfunc NewHostOptions(ctx context.Context, refHostname string, optFuncs ...Opt) (*dockerconfig.HostOptions, error) {\n\tvar o opts\n\tfor _, of := range optFuncs {\n\t\tof(&o)\n\t}\n\tvar ho dockerconfig.HostOptions\n\n\tho.HostDir = func(hostURL string) (string, error) {\n\t\tregURL, err := Parse(hostURL)\n\t\t// Docker inconsistencies handling: `index.docker.io` actually expects `docker.io` for hosts.toml on the filesystem\n\t\t// See https://github.com/containerd/nerdctl/issues/3697\n\t\t// FIXME: we need to reevaluate this comparing with what docker does. What should happen for FQ images with alternate docker domains? (eg: registry-1.docker.io)\n\t\tif regURL.Hostname() == \"index.docker.io\" {\n\t\t\tregURL.Host = \"docker.io\"\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tdir, err := hostDirsFromRoot(regURL, o.hostsDirs)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, errdefs.ErrNotFound) {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn dir, nil\n\t}\n\n\tif o.authCreds != nil {\n\t\tho.Credentials = o.authCreds\n\t} else {\n\t\tauthCreds, err := NewAuthCreds(refHostname)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tho.Credentials = authCreds\n\n\t}\n\n\tif o.skipVerifyCerts {\n\t\tho.DefaultTLS = &tls.Config{\n\t\t\tInsecureSkipVerify: true,\n\t\t}\n\t}\n\n\tif o.plainHTTP {\n\t\tho.DefaultScheme = \"http\"\n\t} else {\n\t\tif isLocalHost, err := docker.MatchLocalhost(refHostname); err != nil {\n\t\t\treturn nil, err\n\t\t} else if isLocalHost {\n\t\t\tho.DefaultScheme = \"http\"\n\t\t}\n\t}\n\tif ho.DefaultScheme == \"http\" {\n\t\t// https://github.com/containerd/containerd/issues/9208\n\t\tho.DefaultTLS = nil\n\t}\n\treturn &ho, nil\n}\n\n// New instantiates a resolver using $DOCKER_CONFIG/config.json .\n//\n// $DOCKER_CONFIG defaults to \"~/.docker\".\n//\n// refHostname is like \"docker.io\".\nfunc New(ctx context.Context, refHostname string, optFuncs ...Opt) (remotes.Resolver, error) {\n\tho, err := NewHostOptions(ctx, refHostname, optFuncs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresolverOpts := docker.ResolverOptions{\n\t\tTracker: PushTracker,\n\t\tHosts:   dockerconfig.ConfigureHosts(ctx, *ho),\n\t}\n\n\tresolver := docker.NewResolver(resolverOpts)\n\treturn resolver, nil\n}\n\n// AuthCreds is for docker.WithAuthCreds\ntype AuthCreds func(string) (string, string, error)\n\n// NewAuthCreds returns AuthCreds that uses $DOCKER_CONFIG/config.json .\n// AuthCreds can be nil.\nfunc NewAuthCreds(refHostname string) (AuthCreds, error) {\n\t// Note: does not raise an error on ENOENT\n\tcredStore, err := NewCredentialsStore(\"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcredFunc := func(host string) (string, string, error) {\n\t\trHost, err := Parse(host)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\n\t\tac, err := credStore.Retrieve(rHost, true)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\n\t\tif ac.IdentityToken != \"\" {\n\t\t\treturn \"\", ac.IdentityToken, nil\n\t\t}\n\n\t\tif ac.RegistryToken != \"\" {\n\t\t\t// Even containerd/CRI does not support RegistryToken as of v1.4.3,\n\t\t\t// so, nobody is actually using RegistryToken?\n\t\t\tlog.L.Warnf(\"ac.RegistryToken (for %q) is not supported yet (FIXME)\", rHost.Host)\n\t\t}\n\n\t\treturn ac.Username, ac.Password, nil\n\t}\n\n\treturn credFunc, nil\n}\n\nfunc NewCredentialHelper(refHostname string) (registry.CredentialHelper, error) {\n\tauthCreds, err := NewAuthCreds(refHostname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &credentialHelper{authCreds: authCreds}, nil\n}\n\ntype credentialHelper struct {\n\tauthCreds AuthCreds\n}\n\nfunc (ch *credentialHelper) GetCredentials(ctx context.Context, ref, host string) (registry.Credentials, error) {\n\tusername, secret, err := ch.authCreds(host)\n\tif err != nil {\n\t\treturn registry.Credentials{}, err\n\t}\n\treturn registry.Credentials{\n\t\tHost:     host,\n\t\tUsername: username,\n\t\tSecret:   secret,\n\t}, nil\n}\n\ntype hostFileConfig struct {\n\tSkipVerify *bool `toml:\"skip_verify,omitempty\"`\n}\n\n// CreateTmpHostsConfig creates a temporary hosts directory with hosts.toml configured for skip_verify\n// Returns the temporary directory path or empty string if creation failed\nfunc CreateTmpHostsConfig(hostname string, skipVerify bool) (string, error) {\n\tif !skipVerify {\n\t\treturn \"\", nil\n\t}\n\n\ttempDir, err := os.MkdirTemp(\"\", \"nerdctl-hosts-*\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create temp directory: %w\", err)\n\t}\n\n\thostDir := filepath.Join(tempDir, hostname)\n\tif err := os.MkdirAll(hostDir, 0755); err != nil {\n\t\tos.RemoveAll(tempDir)\n\t\treturn \"\", fmt.Errorf(\"failed to create host directory: %w\", err)\n\t}\n\n\tconfig := hostFileConfig{}\n\tif skipVerify {\n\t\tskip := true\n\t\tconfig.SkipVerify = &skip\n\t}\n\n\tdata, err := toml.Marshal(config)\n\tif err != nil {\n\t\tos.RemoveAll(tempDir)\n\t\treturn \"\", fmt.Errorf(\"failed to marshal hosts config: %w\", err)\n\t}\n\n\thostsTomlPath := filepath.Join(hostDir, \"hosts.toml\")\n\tif err := os.WriteFile(hostsTomlPath, data, 0644); err != nil {\n\t\tos.RemoveAll(tempDir)\n\t\treturn \"\", fmt.Errorf(\"failed to write hosts.toml: %w\", err)\n\t}\n\n\treturn tempDir, nil\n}\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/hostsstore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/containerd/containerd/v2/core/remotes/docker/config\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n)\n\n// validateDirectories inspect a slice of strings and returns the ones that are valid readable directories\nfunc validateDirectories(orig []string) []string {\n\tss := []string{}\n\tfor _, v := range orig {\n\t\tfi, err := os.Stat(v)\n\t\tif err != nil || !fi.IsDir() {\n\t\t\tif !errors.Is(err, os.ErrNotExist) {\n\t\t\t\tlog.L.WithError(err).Warnf(\"Ignoring hosts location %q\", v)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tss = append(ss, v)\n\t}\n\treturn ss\n}\n\n// hostDirsFromRoot will retrieve a host.toml file for the namespace host, possibly trying without port\n// if the requested port is standard.\n// https://github.com/containerd/nerdctl/issues/3047\nfunc hostDirsFromRoot(registryURL *RegistryURL, dirs []string) (string, error) {\n\thostsDirs := validateDirectories(dirs)\n\n\t// Go through the configured system location to consider for hosts.toml files\n\tfor _, hostsDir := range hostsDirs {\n\t\tfound, err := config.HostDirFromRoot(hostsDir)(registryURL.Host)\n\t\t// If we errored with anything but NotFound, or if we found one, return now\n\t\tif (err != nil && !errdefs.IsNotFound(err)) || (found != \"\") {\n\t\t\treturn found, err\n\t\t}\n\t\t// If not found, and the port is standard, try again without the port\n\t\tif registryURL.Port() == StandardHTTPSPort {\n\t\t\tfound, err = config.HostDirFromRoot(hostsDir)(registryURL.Hostname())\n\t\t\tif (err != nil && !errors.Is(err, errdefs.ErrNotFound)) || (found != \"\") {\n\t\t\t\treturn found, err\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", nil\n}\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/registryurl.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n)\n\n// Parse will return a normalized Docker Registry url from the provided string address\nfunc Parse(address string) (*RegistryURL, error) {\n\tvar err error\n\t// No address or address as docker.io? Default to standardized index\n\tif address == \"\" || address == \"docker.io\" {\n\t\taddress = dockerIndexServer\n\t}\n\t// If it has no scheme, slap one just so we can parse\n\tif !strings.Contains(address, \"://\") {\n\t\taddress = fmt.Sprintf(\"%s://%s\", schemeHTTPS, address)\n\t}\n\t// Parse it\n\tu, err := url.Parse(address)\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrUnparsableURL, err)\n\t}\n\tsch := scheme(u.Scheme)\n\t// Scheme is entirely disregarded anyhow, so, just drop it all and set to https\n\tif sch == schemeHTTP {\n\t\tu.Scheme = string(schemeHTTPS)\n\t} else if sch != schemeHTTPS && sch != schemeNerdctlExperimental {\n\t\t// Docker is wildly buggy when it comes to non-http schemes. Being more defensive.\n\t\treturn nil, ErrUnsupportedScheme\n\t}\n\t// If it has no port, add the standard port explicitly\n\tif u.Port() == \"\" {\n\t\tu.Host = u.Hostname() + \":\" + StandardHTTPSPort\n\t}\n\treg := &RegistryURL{URL: *u}\n\tqueryParams := u.Query()\n\tnsQuery := queryParams.Get(namespaceQueryParameter)\n\tif nsQuery != \"\" {\n\t\treg.Namespace, err = Parse(nsQuery)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn reg, nil\n}\n\n// RegistryURL is a struct that represents a registry namespace or host, meant specifically to deal with\n// credentials storage and retrieval inside Docker config file.\ntype RegistryURL struct {\n\turl.URL\n\tNamespace *RegistryURL\n}\n\n// CanonicalIdentifier returns the identifier expected to be used to save credentials to docker auth config\nfunc (rn *RegistryURL) CanonicalIdentifier() string {\n\t// If it is the docker index over https, port 443, on the /v1/ path, we use the docker fully qualified identifier\n\tif rn.Scheme == string(schemeHTTPS) && rn.Hostname() == \"index.docker.io\" && rn.Path == \"/v1/\" && rn.Port() == StandardHTTPSPort ||\n\t\trn.URL.String() == dockerIndexServer {\n\t\treturn dockerIndexServer\n\t}\n\t// Otherwise, for anything else, we use the hostname+port part\n\tidentifier := rn.Host\n\t// If this is a namespaced entry, wrap it, and slap the path as well, as hosts are allowed to be non-compliant\n\tif rn.Namespace != nil {\n\t\tidentifier = fmt.Sprintf(\"%s://%s/host/%s%s\", schemeNerdctlExperimental, rn.Namespace.CanonicalIdentifier(), identifier, rn.Path)\n\t}\n\treturn identifier\n}\n\n// AllIdentifiers returns a list of identifiers that may have been used to save credentials,\n// accounting for legacy formats including scheme, with and without ports\nfunc (rn *RegistryURL) AllIdentifiers() []string {\n\tcanonicalID := rn.CanonicalIdentifier()\n\tfullList := []string{\n\t\t// This is rn.Host, and always have a port (see parsing)\n\t\tcanonicalID,\n\t}\n\t// If the canonical identifier points to Docker Hub, or is one of our experimental ids, there is no alternative / legacy id\n\tif canonicalID == dockerIndexServer || rn.Namespace != nil {\n\t\treturn fullList\n\t}\n\n\t// Docker behavior: if the domain was index.docker.io over 443, we are allowed to additionally read the canonical\n\t// docker credentials\n\tif rn.Port() == StandardHTTPSPort {\n\t\tif rn.Hostname() == \"index.docker.io\" || rn.Hostname() == \"registry-1.docker.io\" {\n\t\t\tfullList = append(fullList, dockerIndexServer)\n\t\t}\n\t}\n\n\t// Add legacy variants\n\tfullList = append(fullList,\n\t\tfmt.Sprintf(\"%s://%s\", schemeHTTPS, rn.Host),\n\t\tfmt.Sprintf(\"%s://%s\", schemeHTTP, rn.Host),\n\t)\n\n\t// Note that docker does not try to be smart wrt explicit port vs. implied port\n\t// If standard port, allow retrieving credentials from the variant without a port as well\n\tif rn.Port() == StandardHTTPSPort {\n\t\tfullList = append(\n\t\t\tfullList,\n\t\t\trn.Hostname(),\n\t\t\tfmt.Sprintf(\"%s://%s\", schemeHTTPS, rn.Hostname()),\n\t\t\tfmt.Sprintf(\"%s://%s\", schemeHTTP, rn.Hostname()),\n\t\t)\n\t}\n\n\treturn fullList\n}\n\nfunc (rn *RegistryURL) IsLocalhost() bool {\n\t// Containerd exposes both a IsLocalhost and a MatchLocalhost method\n\t// There does not seem to be a clear reason for the duplication, nor the differences in implementation.\n\t// Either way, they both reparse the host with net.SplitHostPort, which is unnecessary here\n\treturn rn.Hostname() == \"localhost\" || net.ParseIP(rn.Hostname()).IsLoopback()\n}\n"
  },
  {
    "path": "pkg/imgutil/dockerconfigresolver/registryurl_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockerconfigresolver\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestURLParsingAndID(t *testing.T) {\n\ttests := []struct {\n\t\taddress     string\n\t\terror       error\n\t\tidentifier  string\n\t\tallIDs      []string\n\t\tisLocalhost bool\n\t}{\n\t\t{\n\t\t\taddress: \"∞://\",\n\t\t\terror:   ErrUnparsableURL,\n\t\t},\n\t\t{\n\t\t\taddress: \"whatever://\",\n\t\t\terror:   ErrUnsupportedScheme,\n\t\t},\n\t\t{\n\t\t\taddress:    \"\",\n\t\t\tidentifier: \"https://index.docker.io/v1/\",\n\t\t\tallIDs:     []string{\"https://index.docker.io/v1/\"},\n\t\t},\n\t\t{\n\t\t\taddress:    \"https://index.docker.io/v1/\",\n\t\t\tidentifier: \"https://index.docker.io/v1/\",\n\t\t\tallIDs:     []string{\"https://index.docker.io/v1/\"},\n\t\t},\n\t\t{\n\t\t\taddress:    \"index.docker.io\",\n\t\t\tidentifier: \"index.docker.io:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"index.docker.io:443\",\n\t\t\t\t\"https://index.docker.io/v1/\",\n\t\t\t\t\"https://index.docker.io:443\", \"http://index.docker.io:443\",\n\t\t\t\t\"index.docker.io\", \"https://index.docker.io\", \"http://index.docker.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"index.docker.io/whatever\",\n\t\t\tidentifier: \"index.docker.io:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"index.docker.io:443\",\n\t\t\t\t\"https://index.docker.io/v1/\",\n\t\t\t\t\"https://index.docker.io:443\", \"http://index.docker.io:443\",\n\t\t\t\t\"index.docker.io\", \"https://index.docker.io\", \"http://index.docker.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"http://index.docker.io\",\n\t\t\tidentifier: \"index.docker.io:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"index.docker.io:443\",\n\t\t\t\t\"https://index.docker.io/v1/\",\n\t\t\t\t\"https://index.docker.io:443\", \"http://index.docker.io:443\",\n\t\t\t\t\"index.docker.io\", \"https://index.docker.io\", \"http://index.docker.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"index.docker.io:80\",\n\t\t\tidentifier: \"index.docker.io:80\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"index.docker.io:80\",\n\t\t\t\t\"https://index.docker.io:80\", \"http://index.docker.io:80\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"index.docker.io:8080\",\n\t\t\tidentifier: \"index.docker.io:8080\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"index.docker.io:8080\",\n\t\t\t\t\"https://index.docker.io:8080\", \"http://index.docker.io:8080\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"foo.docker.io\",\n\t\t\tidentifier: \"foo.docker.io:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"foo.docker.io:443\", \"https://foo.docker.io:443\", \"http://foo.docker.io:443\",\n\t\t\t\t\"foo.docker.io\", \"https://foo.docker.io\", \"http://foo.docker.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"docker.io\",\n\t\t\tidentifier: \"https://index.docker.io/v1/\",\n\t\t\tallIDs:     []string{\"https://index.docker.io/v1/\"},\n\t\t},\n\t\t{\n\t\t\taddress:    \"docker.io/whatever\",\n\t\t\tidentifier: \"docker.io:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"docker.io:443\", \"https://docker.io:443\", \"http://docker.io:443\",\n\t\t\t\t\"docker.io\", \"https://docker.io\", \"http://docker.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"http://docker.io\",\n\t\t\tidentifier: \"docker.io:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"docker.io:443\", \"https://docker.io:443\", \"http://docker.io:443\",\n\t\t\t\t\"docker.io\", \"https://docker.io\", \"http://docker.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"docker.io:80\",\n\t\t\tidentifier: \"docker.io:80\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"docker.io:80\",\n\t\t\t\t\"https://docker.io:80\", \"http://docker.io:80\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"docker.io:8080\",\n\t\t\tidentifier: \"docker.io:8080\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"docker.io:8080\",\n\t\t\t\t\"https://docker.io:8080\", \"http://docker.io:8080\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"anything/whatever?u=v&w=y;foo=bar#frag=o\",\n\t\t\tidentifier: \"anything:443\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"anything:443\", \"https://anything:443\", \"http://anything:443\",\n\t\t\t\t\"anything\", \"https://anything\", \"http://anything\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"https://registry-host.com/subpath/something?bar=bar&ns=registry-namespace.com&foo=foo\",\n\t\t\tidentifier: \"nerdctl-experimental://registry-namespace.com:443/host/registry-host.com:443/subpath/something\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"nerdctl-experimental://registry-namespace.com:443/host/registry-host.com:443/subpath/something\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"localhost:1234\",\n\t\t\tidentifier: \"localhost:1234\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"localhost:1234\", \"https://localhost:1234\", \"http://localhost:1234\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"127.0.0.1:1234\",\n\t\t\tidentifier: \"127.0.0.1:1234\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"127.0.0.1:1234\", \"https://127.0.0.1:1234\", \"http://127.0.0.1:1234\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\taddress:    \"[::1]:1234\",\n\t\t\tidentifier: \"[::1]:1234\",\n\t\t\tallIDs: []string{\n\t\t\t\t\"[::1]:1234\", \"https://[::1]:1234\", \"http://[::1]:1234\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.address, func(t *testing.T) {\n\t\t\treg, err := Parse(tc.address)\n\t\t\tassert.ErrorIs(t, err, tc.error)\n\t\t\tif err == nil {\n\t\t\t\tassert.Equal(t, reg.CanonicalIdentifier(), tc.identifier)\n\t\t\t\tallIDs := reg.AllIdentifiers()\n\t\t\t\tassert.Equal(t, len(allIDs), len(tc.allIDs))\n\t\t\t\tfor k, v := range tc.allIDs {\n\t\t\t\t\tassert.Equal(t, allIDs[k], v)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/imgutil/fetch/fetch.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage fetch\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/jobs\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n)\n\n// Config for content fetch\ntype Config struct {\n\t// Resolver\n\tResolver remotes.Resolver\n\t// ProgressOutput to display progress\n\tProgressOutput io.Writer\n\t// RemoteOpts, e.g. containerd.WithPullUnpack.\n\t//\n\t// Regardless to RemoteOpts, the following opts are always set:\n\t// WithResolver, WithImageHandler\n\t//\n\t// RemoteOpts related to unpacking can be set only when len(Platforms) is 1.\n\tRemoteOpts []containerd.RemoteOpt\n\tPlatforms  []ocispec.Platform // empty for all-platforms\n}\n\nfunc Fetch(ctx context.Context, client *containerd.Client, ref string, config *Config) error {\n\tongoing := jobs.New(ref)\n\n\tpctx, stopProgress := context.WithCancel(ctx)\n\tprogress := make(chan struct{})\n\n\tgo func() {\n\t\tif config.ProgressOutput != nil {\n\t\t\t// no progress bar, because it hides some debug logs\n\t\t\tjobs.ShowProgress(pctx, ongoing, client.ContentStore(), config.ProgressOutput)\n\t\t}\n\t\tclose(progress)\n\t}()\n\n\th := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\tif desc.MediaType != images.MediaTypeDockerSchema1Manifest {\n\t\t\tongoing.Add(desc)\n\t\t}\n\t\treturn nil, nil\n\t})\n\n\tlog.G(pctx).WithField(\"image\", ref).Debug(\"fetching\")\n\tplatformMC := platformutil.NewMatchComparerFromOCISpecPlatformSlice(config.Platforms)\n\topts := []containerd.RemoteOpt{\n\t\tcontainerd.WithResolver(config.Resolver),\n\t\tcontainerd.WithImageHandler(h),\n\t\tcontainerd.WithPlatformMatcher(platformMC),\n\t}\n\topts = append(opts, config.RemoteOpts...)\n\n\t// Note that client.Fetch does not unpack\n\t_, err := client.Fetch(pctx, ref, opts...)\n\n\tstopProgress()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t<-progress\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/imgutil/filtering.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\n// Filter types supported to filter images.\nconst (\n\tFilterBeforeType    = \"before\"\n\tFilterSinceType     = \"since\"\n\tFilterUntilType     = \"until\"\n\tFilterLabelType     = \"label\"\n\tFilterReferenceType = \"reference\"\n\tFilterDanglingType  = \"dangling\"\n)\n\nvar (\n\terrMultipleUntilFilters     = errors.New(\"more than one until filter provided\")\n\terrNoUntilTimestamp         = errors.New(\"no until timestamp provided\")\n\terrUnparsableUntilTimestamp = errors.New(\"unable to parse until timestamp\")\n)\n\n// Filters contains all types of filters to filter images.\ntype Filters struct {\n\tBefore    []string\n\tSince     []string\n\tUntil     string\n\tLabels    map[string]string\n\tReference []string\n\tDangling  *bool\n}\n\ntype Filter func([]images.Image) ([]images.Image, error)\n\n// ParseFilters parse filter strings.\nfunc ParseFilters(filters []string) (*Filters, error) {\n\tf := &Filters{Labels: make(map[string]string)}\n\tfor _, filter := range filters {\n\t\ttempFilterToken := strings.Split(filter, \"=\")\n\t\tswitch len(tempFilterToken) {\n\t\tcase 1:\n\t\t\treturn nil, fmt.Errorf(\"invalid filter %q\", filter)\n\t\tcase 2:\n\t\t\tif tempFilterToken[0] == FilterDanglingType {\n\t\t\t\tvar isDangling bool\n\t\t\t\tif tempFilterToken[1] == \"true\" {\n\t\t\t\t\tisDangling = true\n\t\t\t\t} else if tempFilterToken[1] == \"false\" {\n\t\t\t\t\tisDangling = false\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid filter %q\", filter)\n\t\t\t\t}\n\t\t\t\tf.Dangling = &isDangling\n\t\t\t} else if tempFilterToken[0] == FilterBeforeType {\n\t\t\t\tparsedReference, err := referenceutil.Parse(tempFilterToken[1])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tf.Before = append(f.Before, fmt.Sprintf(\"name==%s\", parsedReference.String()))\n\t\t\t\tf.Before = append(f.Before, fmt.Sprintf(\"name==%s\", tempFilterToken[1]))\n\t\t\t} else if tempFilterToken[0] == FilterSinceType {\n\t\t\t\tparsedReference, err := referenceutil.Parse(tempFilterToken[1])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tf.Since = append(f.Since, fmt.Sprintf(\"name==%s\", parsedReference.String()))\n\t\t\t\tf.Since = append(f.Since, fmt.Sprintf(\"name==%s\", tempFilterToken[1]))\n\t\t\t} else if tempFilterToken[0] == FilterUntilType {\n\t\t\t\tif len(tempFilterToken[0]) == 0 {\n\t\t\t\t\treturn nil, errNoUntilTimestamp\n\t\t\t\t} else if len(f.Until) > 0 {\n\t\t\t\t\treturn nil, errMultipleUntilFilters\n\t\t\t\t}\n\t\t\t\tf.Until = tempFilterToken[1]\n\t\t\t} else if tempFilterToken[0] == FilterLabelType {\n\t\t\t\t// To support filtering labels by keys.\n\t\t\t\tf.Labels[tempFilterToken[1]] = \"\"\n\t\t\t} else if tempFilterToken[0] == FilterReferenceType {\n\t\t\t\tf.Reference = append(f.Reference, tempFilterToken[1])\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid filter %q\", filter)\n\t\t\t}\n\t\tcase 3:\n\t\t\tif tempFilterToken[0] == FilterLabelType {\n\t\t\t\tf.Labels[tempFilterToken[1]] = tempFilterToken[2]\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid filter %q\", filter)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"invalid filter %q\", filter)\n\t\t}\n\t}\n\treturn f, nil\n}\n\n// ApplyFilters applies each filter function in the order provided\n// and returns the resulting filtered image list.\nfunc ApplyFilters(imageList []images.Image, filters ...Filter) ([]images.Image, error) {\n\tvar err error\n\tfor _, filter := range filters {\n\t\timageList, err = filter(imageList)\n\t\tif err != nil {\n\t\t\treturn []images.Image{}, err\n\t\t}\n\t}\n\treturn imageList, nil\n}\n\n// FilterByCreatedAt filters an image list to images created before MAX(before.<Image>.CreatedAt)\n// and after MIN(since.<Image>.CreatedAt).\nfunc FilterByCreatedAt(ctx context.Context, client *containerd.Client, before []string, since []string) Filter {\n\treturn func(imageList []images.Image) ([]images.Image, error) {\n\t\tvar (\n\t\t\tminTime = time.Date(1970, time.Month(1), 1, 0, 0, 0, 0, time.UTC)\n\t\t\tmaxTime = time.Now()\n\t\t)\n\n\t\tfetchImageNames := func(names []string) string {\n\t\t\tparsedNames := make([]string, 0, len(names))\n\t\t\tfor _, name := range names {\n\t\t\t\tparsedNames = append(parsedNames, strings.TrimPrefix(name, \"name==\"))\n\t\t\t}\n\t\t\treturn strings.Join(parsedNames, \",\")\n\t\t}\n\n\t\timageStore := client.ImageService()\n\t\tif len(before) > 0 {\n\t\t\tbeforeImages, err := imageStore.List(ctx, before...)\n\t\t\tif err != nil {\n\t\t\t\treturn []images.Image{}, err\n\t\t\t}\n\t\t\tif len(beforeImages) == 0 {\n\t\t\t\treturn []images.Image{}, fmt.Errorf(\"no such image: %s\", fetchImageNames(before))\n\t\t\t}\n\t\t\tmaxTime = beforeImages[0].CreatedAt\n\t\t\tfor _, image := range beforeImages {\n\t\t\t\tif image.CreatedAt.After(maxTime) {\n\t\t\t\t\tmaxTime = image.CreatedAt\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(since) > 0 {\n\t\t\tsinceImages, err := imageStore.List(ctx, since...)\n\t\t\tif err != nil {\n\t\t\t\treturn []images.Image{}, err\n\t\t\t}\n\t\t\tif len(sinceImages) == 0 {\n\t\t\t\treturn []images.Image{}, fmt.Errorf(\"no such image: %s\", fetchImageNames(since))\n\t\t\t}\n\t\t\tminTime = sinceImages[0].CreatedAt\n\t\t\tfor _, image := range sinceImages {\n\t\t\t\tif image.CreatedAt.Before(minTime) {\n\t\t\t\t\tminTime = image.CreatedAt\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn filter(imageList, func(i images.Image) (bool, error) {\n\t\t\treturn imageCreatedBetween(i, minTime, maxTime), nil\n\t\t})\n\t}\n}\n\n// FilterUntil filters images created before the provided timestamp.\nfunc FilterUntil(until string) Filter {\n\treturn func(imageList []images.Image) ([]images.Image, error) {\n\t\tif len(until) == 0 {\n\t\t\treturn []images.Image{}, errNoUntilTimestamp\n\t\t}\n\n\t\tvar (\n\t\t\tparsedTime time.Time\n\t\t\terr        error\n\t\t)\n\n\t\ttype parseUntilFunc func(string) (time.Time, error)\n\t\tparsingFuncs := []parseUntilFunc{\n\t\t\tfunc(until string) (time.Time, error) {\n\t\t\t\treturn time.Parse(time.RFC3339, until)\n\t\t\t},\n\t\t\tfunc(until string) (time.Time, error) {\n\t\t\t\treturn time.Parse(time.RFC3339Nano, until)\n\t\t\t},\n\t\t\tfunc(until string) (time.Time, error) {\n\t\t\t\treturn time.Parse(time.DateOnly, until)\n\t\t\t},\n\t\t\tfunc(until string) (time.Time, error) {\n\t\t\t\t// Go duration strings\n\t\t\t\td, err := time.ParseDuration(until)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn time.Time{}, err\n\t\t\t\t}\n\t\t\t\treturn time.Now().Add(-d), nil\n\t\t\t},\n\t\t}\n\n\t\tfor _, parse := range parsingFuncs {\n\t\t\tparsedTime, err = parse(until)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn []images.Image{}, errUnparsableUntilTimestamp\n\t\t}\n\n\t\treturn filter(imageList, func(i images.Image) (bool, error) {\n\t\t\treturn imageCreatedBefore(i, parsedTime), nil\n\t\t})\n\t}\n}\n\n// FilterByLabel filters an image list based on labels applied to the image's config specification for the platform.\n// Any matching label will include the image in the list.\nfunc FilterByLabel(ctx context.Context, client *containerd.Client, labels map[string]string) Filter {\n\treturn func(imageList []images.Image) ([]images.Image, error) {\n\t\treturn filter(imageList, func(i images.Image) (bool, error) {\n\t\t\tclientImage := containerd.NewImage(client, i)\n\t\t\timageCfg, _, err := ReadImageConfig(ctx, clientImage)\n\t\t\tif err != nil {\n\t\t\t\t// Stop-gap measure. Do not hard error if some images config cannot be read.\n\t\t\t\t// See https://github.com/containerd/nerdctl/issues/3516\n\t\t\t\tlog.G(ctx).WithError(err).Errorf(\"failed reading image config for %s (%s)\", clientImage.Name(), clientImage.Platform())\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t\treturn matchesAllLabels(imageCfg.Config.Labels, labels), nil\n\t\t})\n\t}\n}\n\n// FilterByReference filters an image list based on <image:tag>\n// matching the provided reference patterns\nfunc FilterByReference(referencePatterns []string) Filter {\n\treturn func(imageList []images.Image) ([]images.Image, error) {\n\t\treturn filter(imageList, func(i images.Image) (bool, error) {\n\t\t\treturn matchesReferences(i, referencePatterns)\n\t\t})\n\t}\n}\n\n// FilterDanglingImages filters an image list for dangling (untagged) images.\nfunc FilterDanglingImages() Filter {\n\treturn func(imageList []images.Image) ([]images.Image, error) {\n\t\treturn filter(imageList, func(i images.Image) (bool, error) {\n\t\t\treturn isDangling(i), nil\n\t\t})\n\t}\n}\n\n// FilterTaggedImages filters an image list for tagged images.\nfunc FilterTaggedImages() Filter {\n\treturn func(imageList []images.Image) ([]images.Image, error) {\n\t\treturn filter(imageList, func(i images.Image) (bool, error) {\n\t\t\treturn !isDangling(i), nil\n\t\t})\n\t}\n}\n\nfunc filter[T any](items []T, f func(item T) (bool, error)) ([]T, error) {\n\tfilteredItems := make([]T, 0, len(items))\n\tfor _, item := range items {\n\t\tok, err := f(item)\n\t\tif err != nil {\n\t\t\treturn []T{}, err\n\t\t} else if ok {\n\t\t\tfilteredItems = append(filteredItems, item)\n\t\t}\n\t}\n\treturn filteredItems, nil\n}\n\nfunc imageCreatedBetween(image images.Image, minTime time.Time, maxTime time.Time) bool {\n\treturn image.CreatedAt.After(minTime) && image.CreatedAt.Before(maxTime)\n}\n\nfunc imageCreatedBefore(image images.Image, maxTime time.Time) bool {\n\treturn image.CreatedAt.Before(maxTime)\n}\n\nfunc matchesAllLabels(imageCfgLabels map[string]string, filterLabels map[string]string) bool {\n\tvar matches int\n\tfor lk, lv := range filterLabels {\n\t\tif val, ok := imageCfgLabels[lk]; ok {\n\t\t\tif val == lv || lv == \"\" {\n\t\t\t\tmatches++\n\t\t\t}\n\t\t}\n\t}\n\treturn matches == len(filterLabels)\n}\n\nfunc matchesReferences(image images.Image, referencePatterns []string) (bool, error) {\n\tvar matches int\n\n\t// Containerd returns \":\" for dangling untagged images - see https://github.com/containerd/nerdctl/issues/3852\n\tif image.Name == \":\" {\n\t\treturn false, nil\n\t}\n\n\tparsedReference, err := referenceutil.Parse(image.Name)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, pattern := range referencePatterns {\n\t\tfamiliarMatch, err := parsedReference.FamiliarMatch(pattern)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tregexpMatch, err := regexp.MatchString(pattern, image.Name)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif familiarMatch || regexpMatch {\n\t\t\tmatches++\n\t\t}\n\t}\n\n\treturn matches == len(referencePatterns), nil\n}\n\nfunc isDangling(image images.Image) bool {\n\t_, tag := ParseRepoTag(image.Name)\n\treturn tag == \"\"\n}\n"
  },
  {
    "path": "pkg/imgutil/filtering_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n)\n\nfunc TestApplyFilters(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\timages         []images.Image\n\t\tfilters        []Filter\n\t\texpectedImages []images.Image\n\t\texpectedErr    error\n\t}{\n\t\t{\n\t\t\tname:   \"EmptyList\",\n\t\t\timages: []images.Image{},\n\t\t\tfilters: []Filter{\n\t\t\t\tFilterDanglingImages(),\n\t\t\t},\n\t\t\texpectedImages: []images.Image{},\n\t\t},\n\t\t{\n\t\t\tname: \"ApplyNoFilters\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"<none>\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfilters: []Filter{},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"<none>\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"ApplySingleFilter\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"<none>\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfilters: []Filter{\n\t\t\t\tFilterDanglingImages(),\n\t\t\t},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"<none>\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"ApplyMultipleFilters\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"<none>\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"alpine:3.19\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"public.ecr.aws/docker/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfilters: []Filter{\n\t\t\t\tFilterTaggedImages(),\n\t\t\t\tFilterByReference([]string{\"hello-world\"}),\n\t\t\t},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"public.ecr.aws/docker/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"ReturnErrorAndEmptyListOnFilterError\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"<none>:<none>\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfilters: []Filter{\n\t\t\t\tFilterDanglingImages(),\n\t\t\t\tFilterUntil(\"\"),\n\t\t\t},\n\t\t\texpectedImages: []images.Image{},\n\t\t\texpectedErr:    errNoUntilTimestamp,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactualImages, err := ApplyFilters(test.images, test.filters...)\n\t\t\tif test.expectedErr == nil {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.ErrorIs(t, err, test.expectedErr)\n\t\t\t}\n\t\t\tassert.Equal(t, len(actualImages), len(test.expectedImages))\n\t\t\tassert.DeepEqual(t, actualImages, test.expectedImages)\n\t\t})\n\t}\n}\n\nfunc TestFilterUntil(t *testing.T) {\n\tnow := time.Now().UTC()\n\n\ttests := []struct {\n\t\tname           string\n\t\tuntil          string\n\t\timages         []images.Image\n\t\texpectedImages []images.Image\n\t\texpectedErr    error\n\t}{\n\t\t{\n\t\t\tname:           \"EmptyTimestampReturnsError\",\n\t\t\tuntil:          \"\",\n\t\t\timages:         []images.Image{},\n\t\t\texpectedImages: []images.Image{},\n\t\t\texpectedErr:    errNoUntilTimestamp,\n\t\t},\n\t\t{\n\t\t\tname:           \"UnparseableTimestampReturnsError\",\n\t\t\tuntil:          \"-2006-01-02T15:04:05Z07:00\",\n\t\t\timages:         []images.Image{},\n\t\t\texpectedImages: []images.Image{},\n\t\t\texpectedErr:    errUnparsableUntilTimestamp,\n\t\t},\n\t\t{\n\t\t\tname:  \"ImagesOlderThan3Hours(Go duration)\",\n\t\t\tuntil: \"3h\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName:      \"image:yesterday\",\n\t\t\t\t\tCreatedAt: now.Add(-24 * time.Hour),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:      \"image:today\",\n\t\t\t\t\tCreatedAt: now.Add(-12 * time.Hour),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:      \"image:latest\",\n\t\t\t\t\tCreatedAt: now,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName:      \"image:yesterday\",\n\t\t\t\t\tCreatedAt: now.Add(-24 * time.Hour),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:      \"image:today\",\n\t\t\t\t\tCreatedAt: now.Add(-12 * time.Hour),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactualImages, err := FilterUntil(test.until)(test.images)\n\t\t\tif test.expectedErr == nil {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.ErrorIs(t, err, test.expectedErr)\n\t\t\t}\n\t\t\tassert.Equal(t, len(actualImages), len(test.expectedImages))\n\t\t\tassert.DeepEqual(t, actualImages, test.expectedImages)\n\t\t})\n\t}\n}\n\nfunc TestFilterByReference(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\treferencePatterns []string\n\t\timages            []images.Image\n\t\texpectedImages    []images.Image\n\t\texpectedErr       error\n\t}{\n\t\t{\n\t\t\tname:           \"EmptyList\",\n\t\t\timages:         []images.Image{},\n\t\t\texpectedImages: []images.Image{},\n\t\t},\n\t\t{\n\t\t\tname: \"MatchByReference\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"foo:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"public.ecr.aws/docker/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\treferencePatterns: []string{\"hello-world\"},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"public.ecr.aws/docker/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"NoMatchExists\",\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"foo:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"public.ecr.aws/docker/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t\treferencePatterns: []string{\"foobar\"},\n\t\t\texpectedImages:    []images.Image{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactualImages, err := FilterByReference(test.referencePatterns)(test.images)\n\t\t\tif test.expectedErr == nil {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.ErrorIs(t, err, test.expectedErr)\n\t\t\t}\n\t\t\tassert.Equal(t, len(actualImages), len(test.expectedImages))\n\t\t\tassert.DeepEqual(t, actualImages, test.expectedImages)\n\t\t})\n\t}\n}\n\nfunc TestFilterDanglingImages(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tdangling       bool\n\t\timages         []images.Image\n\t\texpectedImages []images.Image\n\t}{\n\t\t{\n\t\t\tname:           \"EmptyList\",\n\t\t\tdangling:       true,\n\t\t\timages:         []images.Image{},\n\t\t\texpectedImages: []images.Image{},\n\t\t},\n\t\t{\n\t\t\tname:     \"IsDangling\",\n\t\t\tdangling: true,\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName:   \"\",\n\t\t\t\t\tLabels: map[string]string{\"ref\": \"dangling1\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:   \"<none>\",\n\t\t\t\t\tLabels: map[string]string{\"ref\": \"dangling2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName:   \"\",\n\t\t\t\t\tLabels: map[string]string{\"ref\": \"dangling1\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:   \"<none>\",\n\t\t\t\t\tLabels: map[string]string{\"ref\": \"dangling2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactualImages, err := FilterDanglingImages()(test.images)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, len(actualImages), len(test.expectedImages))\n\t\t\tassert.DeepEqual(t, actualImages, test.expectedImages)\n\t\t})\n\t}\n}\n\nfunc TestFilterTaggedImages(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tdangling       bool\n\t\timages         []images.Image\n\t\texpectedImages []images.Image\n\t}{\n\t\t{\n\t\t\tname:           \"EmptyList\",\n\t\t\tdangling:       true,\n\t\t\timages:         []images.Image{},\n\t\t\texpectedImages: []images.Image{},\n\t\t},\n\t\t{\n\t\t\tname:     \"IsTagged\",\n\t\t\tdangling: true,\n\t\t\timages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName:   \"\",\n\t\t\t\t\tLabels: map[string]string{\"ref\": \"dangling1\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:   \"<none>\",\n\t\t\t\t\tLabels: map[string]string{\"ref\": \"dangling2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedImages: []images.Image{\n\t\t\t\t{\n\t\t\t\t\tName: \"docker.io/library/hello-world:latest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactualImages, err := FilterTaggedImages()(test.images)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, len(actualImages), len(test.expectedImages))\n\t\t\tassert.DeepEqual(t, actualImages, test.expectedImages)\n\t\t})\n\t}\n}\n\nfunc TestImageCreatedBetween(t *testing.T) {\n\tvar (\n\t\tunixEpoch = time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)\n\t\ty2k       = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)\n\t\tnow       = time.Now()\n\t)\n\ttests := []struct {\n\t\tname         string\n\t\timage        images.Image\n\t\tlhs          time.Time\n\t\trhs          time.Time\n\t\tfallsBetween bool\n\t}{\n\t\t{\n\t\t\tname: \"PreviousImage\",\n\t\t\timage: images.Image{\n\t\t\t\tCreatedAt: unixEpoch,\n\t\t\t},\n\t\t\tlhs:          y2k,\n\t\t\trhs:          now,\n\t\t\tfallsBetween: false,\n\t\t},\n\t\t{\n\t\t\tname: \"AfterImage\",\n\t\t\timage: images.Image{\n\t\t\t\tCreatedAt: now,\n\t\t\t},\n\t\t\tlhs:          unixEpoch,\n\t\t\trhs:          y2k,\n\t\t\tfallsBetween: false,\n\t\t},\n\t\t{\n\t\t\tname: \"InBetweenTimeImage\",\n\t\t\timage: images.Image{\n\t\t\t\tCreatedAt: y2k,\n\t\t\t},\n\t\t\tlhs:          unixEpoch,\n\t\t\trhs:          now,\n\t\t\tfallsBetween: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ExclusiveLeft\",\n\t\t\timage: images.Image{\n\t\t\t\tCreatedAt: unixEpoch,\n\t\t\t},\n\t\t\tlhs:          unixEpoch,\n\t\t\trhs:          now,\n\t\t\tfallsBetween: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ExclusiveRight\",\n\t\t\timage: images.Image{\n\t\t\t\tCreatedAt: now,\n\t\t\t},\n\t\t\tlhs:          unixEpoch,\n\t\t\trhs:          now,\n\t\t\tfallsBetween: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, imageCreatedBetween(test.image, test.lhs, test.rhs), test.fallsBetween)\n\t\t})\n\t}\n}\n\nfunc TestMatchesAnyLabel(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\timageLabels   map[string]string\n\t\tlabelsToMatch map[string]string\n\t\tmatches       bool\n\t}{\n\t\t{\n\t\t\tname:          \"ImageHasNoLabels\",\n\t\t\timageLabels:   map[string]string{},\n\t\t\tlabelsToMatch: map[string]string{\"foo\": \"bar\"},\n\t\t\tmatches:       false,\n\t\t},\n\t\t{\n\t\t\tname:          \"SingleMatchingLabel\",\n\t\t\timageLabels:   map[string]string{\"org\": \"com.example.nerdctl\"},\n\t\t\tlabelsToMatch: map[string]string{\"org\": \"com.example.nerdctl\"},\n\t\t\tmatches:       true,\n\t\t},\n\t\t{\n\t\t\tname:          \"KeyOnlyMatchingLabel\",\n\t\t\timageLabels:   map[string]string{\"org\": \"com.example.nerdctl\"},\n\t\t\tlabelsToMatch: map[string]string{\"org\": \"\"},\n\t\t\tmatches:       true,\n\t\t},\n\t\t{\n\t\t\tname:          \"KeyValueDoesNotMatch\",\n\t\t\timageLabels:   map[string]string{\"org\": \"com.example.nerdctl\"},\n\t\t\tlabelsToMatch: map[string]string{\"org\": \"com.example.containerd\"},\n\t\t\tmatches:       false,\n\t\t},\n\t\t{\n\t\t\tname:          \"AllMatchingLabel\",\n\t\t\timageLabels:   map[string]string{\"org\": \"com.example.nerdctl\", \"foo\": \"bar\"},\n\t\t\tlabelsToMatch: map[string]string{\"org\": \"com.example.containerd\", \"foo\": \"bar\"},\n\t\t\tmatches:       false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, matchesAllLabels(test.imageLabels, test.labelsToMatch), test.matches)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/imgutil/imgutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"reflect\"\n\n\t\"github.com/opencontainers/image-spec/identity\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/containerd/v2/core/snapshots\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/imgcrypt/v2\"\n\t\"github.com/containerd/imgcrypt/v2/images/encryption\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/pull\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\n// EnsuredImage contains the image existed in containerd and its metadata.\ntype EnsuredImage struct {\n\tRef         string\n\tImage       containerd.Image\n\tImageConfig ocispec.ImageConfig\n\tSnapshotter string\n\tRemote      bool // true for stargz or overlaybd\n}\n\n// PullMode is either one of \"always\", \"missing\", \"never\"\ntype PullMode = string\n\n// GetExistingImage returns the specified image if exists in containerd. Return errdefs.NotFound() if not exists.\nfunc GetExistingImage(ctx context.Context, client *containerd.Client, snapshotter, rawRef string, platform ocispec.Platform) (*EnsuredImage, error) {\n\tvar res *EnsuredImage\n\timgwalker := &imagewalker.ImageWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found imagewalker.Found) error {\n\t\t\tif res != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\timage := containerd.NewImageWithPlatform(client, found.Image, platforms.OnlyStrict(platform))\n\t\t\timgConfig, err := getImageConfig(ctx, image)\n\t\t\tif err != nil {\n\t\t\t\t// Image found but blob not found for foreign arch\n\t\t\t\t// Ignore err and return nil, so that the walker can visit the next candidate.\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tres = &EnsuredImage{\n\t\t\t\tRef:         found.Image.Name,\n\t\t\t\tImage:       image,\n\t\t\t\tImageConfig: *imgConfig,\n\t\t\t\tSnapshotter: snapshotter,\n\t\t\t\tRemote:      getSnapshotterOpts(snapshotter).isRemote(),\n\t\t\t}\n\t\t\tif unpacked, err := image.IsUnpacked(ctx, snapshotter); err == nil && !unpacked {\n\t\t\t\tif err := image.Unpack(ctx, snapshotter); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcount, err := imgwalker.Walk(ctx, rawRef)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif count == 0 {\n\t\treturn nil, errors.Join(errdefs.ErrNotFound, errors.New(\"got count 0 after walking\"))\n\t}\n\tif res == nil {\n\t\treturn nil, errors.Join(errdefs.ErrNotFound, errors.New(\"got nil res after walking\"))\n\t}\n\treturn res, nil\n}\n\n// EnsureImage ensures the image.\n//\n// # When insecure is set, skips verifying certs, and also falls back to HTTP when the registry does not speak HTTPS\nfunc EnsureImage(ctx context.Context, client *containerd.Client, rawRef string, options types.ImagePullOptions) (*EnsuredImage, error) {\n\tswitch options.Mode {\n\tcase \"always\", \"missing\", \"never\":\n\t\t// NOP\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected pull mode: %q\", options.Mode)\n\t}\n\n\t// if not `always` pull and given one platform and image found locally, return existing image directly.\n\tif options.Mode != \"always\" && len(options.OCISpecPlatform) == 1 {\n\t\tif res, err := GetExistingImage(ctx, client, options.GOptions.Snapshotter, rawRef, options.OCISpecPlatform[0]); err == nil {\n\t\t\treturn res, nil\n\t\t} else if !errdefs.IsNotFound(err) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif options.Mode == \"never\" {\n\t\treturn nil, fmt.Errorf(\"image not available: %q\", rawRef)\n\t}\n\n\tparsedReference, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Transfer service is available in containerd 1.7, but full support is only in 2.0+\n\t// For containerd 1.7, use the legacy resolver-based pull method for better compatibility\n\tuseTransferAPI := containerdutil.SupportsFullTransferService(ctx, client)\n\tif !useTransferAPI {\n\t\tlog.G(ctx).Debug(\"Detected containerd < 2.0, using legacy pull method\")\n\t}\n\n\tif useTransferAPI {\n\t\treturn PullImageWithTransfer(ctx, client, parsedReference, rawRef, options)\n\t}\n\n\tvar dOpts []dockerconfigresolver.Opt\n\tif options.GOptions.InsecureRegistry {\n\t\tlog.G(ctx).Warnf(\"skipping verifying HTTPS certs for %q\", parsedReference.Domain)\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t}\n\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(options.GOptions.HostsDir))\n\tresolver, err := dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\timg, err := PullImage(ctx, client, resolver, parsedReference.String(), options)\n\tif err != nil {\n\t\t// In some circumstance (e.g. people just use 80 port to support pure http), the error will contain message like \"dial tcp <port>: connection refused\".\n\t\tif !errors.Is(err, http.ErrSchemeMismatch) && !errutil.IsErrConnectionRefused(err) {\n\t\t\treturn nil, err\n\t\t}\n\t\tif options.GOptions.InsecureRegistry {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"server %q does not seem to support HTTPS, falling back to plain HTTP\", parsedReference.Domain)\n\t\t\tdOpts = append(dOpts, dockerconfigresolver.WithPlainHTTP(true))\n\t\t\tresolver, err = dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn PullImage(ctx, client, resolver, parsedReference.String(), options)\n\t\t}\n\t\tlog.G(ctx).WithError(err).Errorf(\"server %q does not seem to support HTTPS\", parsedReference.Domain)\n\t\tlog.G(ctx).Info(\"Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)\")\n\t\treturn nil, err\n\n\t}\n\treturn img, nil\n}\n\n// ResolveDigest resolves `rawRef` and returns its descriptor digest.\nfunc ResolveDigest(ctx context.Context, rawRef string, insecure bool, hostsDirs []string) (string, error) {\n\tparsedReference, err := referenceutil.Parse(rawRef)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar dOpts []dockerconfigresolver.Opt\n\tif insecure {\n\t\tlog.G(ctx).Warnf(\"skipping verifying HTTPS certs for %q\", parsedReference.Domain)\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t}\n\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(hostsDirs))\n\tresolver, err := dockerconfigresolver.New(ctx, parsedReference.Domain, dOpts...)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t_, desc, err := resolver.Resolve(ctx, parsedReference.String())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn desc.Digest.String(), nil\n}\n\n// PullImage pulls an image using the specified resolver.\nfunc PullImage(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, ref string, options types.ImagePullOptions) (*EnsuredImage, error) {\n\tctx, done, err := client.WithLease(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer done(ctx)\n\n\tvar containerdImage containerd.Image\n\tconfig := &pull.Config{\n\t\tResolver:   resolver,\n\t\tRemoteOpts: []containerd.RemoteOpt{},\n\t\tPlatforms:  options.OCISpecPlatform, // empty for all-platforms\n\t}\n\tif !options.Quiet {\n\t\tconfig.ProgressOutput = options.Stderr\n\t\tif options.ProgressOutputToStdout {\n\t\t\tconfig.ProgressOutput = options.Stdout\n\t\t}\n\t}\n\n\t// unpack(B) if given 1 platform unless specified by `unpack`\n\tunpackB := len(options.OCISpecPlatform) == 1\n\tif options.Unpack != nil {\n\t\tunpackB = *options.Unpack\n\t\tif unpackB && len(options.OCISpecPlatform) != 1 {\n\t\t\treturn nil, fmt.Errorf(\"unpacking requires a single platform to be specified (e.g., --platform=amd64)\")\n\t\t}\n\t}\n\n\tsnOpt := getSnapshotterOpts(options.GOptions.Snapshotter)\n\tif unpackB {\n\t\tlog.G(ctx).Debugf(\"The image will be unpacked for platform %q, snapshotter %q.\", options.OCISpecPlatform[0], options.GOptions.Snapshotter)\n\t\timgcryptPayload := imgcrypt.Payload{}\n\t\timgcryptUnpackOpt := encryption.WithUnpackConfigApplyOpts(encryption.WithDecryptedUnpack(&imgcryptPayload))\n\t\tconfig.RemoteOpts = append(config.RemoteOpts,\n\t\t\tcontainerd.WithPullUnpack,\n\t\t\tcontainerd.WithUnpackOpts([]containerd.UnpackOpt{imgcryptUnpackOpt}))\n\n\t\t// different remote snapshotters will update pull.Config separately\n\t\tsnOpt.apply(config, ref, options.RFlags)\n\t} else {\n\t\tlog.G(ctx).Debugf(\"The image will not be unpacked. Platforms=%v.\", options.OCISpecPlatform)\n\t}\n\n\tcontainerdImage, err = pull.Pull(ctx, client, ref, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\timgConfig, err := getImageConfig(ctx, containerdImage)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres := &EnsuredImage{\n\t\tRef:         ref,\n\t\tImage:       containerdImage,\n\t\tImageConfig: *imgConfig,\n\t\tSnapshotter: options.GOptions.Snapshotter,\n\t\tRemote:      snOpt.isRemote(),\n\t}\n\treturn res, nil\n\n}\n\nfunc getImageConfig(ctx context.Context, image containerd.Image) (*ocispec.ImageConfig, error) {\n\tdesc, err := image.Config(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch desc.MediaType {\n\tcase ocispec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:\n\t\tvar ocispecImage ocispec.Image\n\t\tb, err := content.ReadBlob(ctx, image.ContentStore(), desc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err := json.Unmarshal(b, &ocispecImage); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err := addHealthCheckToImageConfig(b, &ocispecImage.Config); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Debug(\"failed to add health check config\")\n\t\t}\n\t\treturn &ocispecImage.Config, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown media type %q\", desc.MediaType)\n\t}\n}\n\n// ReadIndex returns image index, or nil for non-indexed image.\nfunc ReadIndex(ctx context.Context, img containerd.Image) (*ocispec.Index, *ocispec.Descriptor, error) {\n\tdesc := img.Target()\n\tif !images.IsIndexType(desc.MediaType) {\n\t\treturn nil, nil, nil\n\t}\n\tb, err := content.ReadBlob(ctx, img.ContentStore(), desc)\n\tif err != nil {\n\t\treturn nil, &desc, err\n\t}\n\tvar idx ocispec.Index\n\tif err := json.Unmarshal(b, &idx); err != nil {\n\t\treturn nil, &desc, err\n\t}\n\n\treturn &idx, &desc, nil\n}\n\n// ReadManifest returns the manifest for img.platform, or nil if no manifest was found.\nfunc ReadManifest(ctx context.Context, img containerd.Image) (*ocispec.Manifest, *ocispec.Descriptor, error) {\n\tcs := img.ContentStore()\n\ttargetDesc := img.Target()\n\tif images.IsManifestType(targetDesc.MediaType) {\n\t\tb, err := content.ReadBlob(ctx, img.ContentStore(), targetDesc)\n\t\tif err != nil {\n\t\t\treturn nil, &targetDesc, err\n\t\t}\n\t\tvar mani ocispec.Manifest\n\t\tif err := json.Unmarshal(b, &mani); err != nil {\n\t\t\treturn nil, &targetDesc, err\n\t\t}\n\t\treturn &mani, &targetDesc, nil\n\t}\n\tif images.IsIndexType(targetDesc.MediaType) {\n\t\tidx, _, err := ReadIndex(ctx, img)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tconfigDesc, err := img.Config(ctx) // aware of img.platform\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\t// We can't access the private `img.platform` variable.\n\t\t// So, we find the manifest object by comparing the config desc.\n\t\tfor _, maniDesc := range idx.Manifests {\n\t\t\tmaniDesc := maniDesc\n\t\t\t// ignore non-nil err\n\t\t\tif b, err := content.ReadBlob(ctx, cs, maniDesc); err == nil {\n\t\t\t\tvar mani ocispec.Manifest\n\t\t\t\tif err := json.Unmarshal(b, &mani); err != nil {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t\tif reflect.DeepEqual(configDesc, mani.Config) {\n\t\t\t\t\treturn &mani, &maniDesc, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// no manifest was found\n\treturn nil, nil, nil\n}\n\n// ReadImageConfig reads the config spec (`application/vnd.oci.image.config.v1+json`) for img.platform from content store.\nfunc ReadImageConfig(ctx context.Context, img containerd.Image) (ocispec.Image, ocispec.Descriptor, error) {\n\tvar config ocispec.Image\n\n\tconfigDesc, err := img.Config(ctx) // aware of img.platform\n\tif err != nil {\n\t\treturn config, configDesc, err\n\t}\n\tp, err := content.ReadBlob(ctx, img.ContentStore(), configDesc)\n\tif err != nil {\n\t\treturn config, configDesc, err\n\t}\n\tif err := json.Unmarshal(p, &config); err != nil {\n\t\treturn config, configDesc, err\n\t}\n\tif err := addHealthCheckToImageConfig(p, &config.Config); err != nil {\n\t\tlog.G(ctx).WithError(err).Debug(\"failed to add health check config\")\n\t}\n\treturn config, configDesc, nil\n}\n\n// ParseRepoTag parses raw `imgName` to repository and tag.\nfunc ParseRepoTag(imgName string) (string, string) {\n\tlog.L.Debugf(\"raw image name=%q\", imgName)\n\n\tparsedReference, err := referenceutil.Parse(imgName)\n\tif err != nil {\n\t\tlog.L.WithError(err).Debugf(\"unparsable image name %q\", imgName)\n\t\treturn \"\", \"\"\n\t}\n\n\treturn parsedReference.FamiliarName(), parsedReference.Tag\n}\n\n// ResourceUsage will return:\n// - the Usage value of the resource referenced by ID\n// - the cumulative Usage value of the resource, and all parents, recursively\n// Typically, for a running container, this will equal the size of the read-write layer, plus the sum of the size of all layers in the base image\nfunc ResourceUsage(ctx context.Context, snapshotter snapshots.Snapshotter, resourceID string) (snapshots.Usage, snapshots.Usage, error) {\n\tfirst := snapshots.Usage{}\n\ttotal := snapshots.Usage{}\n\tvar info snapshots.Info\n\tfor next := resourceID; next != \"\"; next = info.Parent {\n\t\t// Get the resource usage info\n\t\tusage, err := snapshotter.Usage(ctx, next)\n\t\tif err != nil {\n\t\t\treturn first, total, err\n\t\t}\n\t\t// In case that's the first one, store that\n\t\tif next == resourceID {\n\t\t\tfirst = usage\n\t\t}\n\t\t// And increment totals\n\t\ttotal.Size += usage.Size\n\t\ttotal.Inodes += usage.Inodes\n\n\t\t// Now, get the parent, if any and iterate\n\t\tinfo, err = snapshotter.Stat(ctx, next)\n\t\tif err != nil {\n\t\t\treturn first, total, err\n\t\t}\n\t}\n\n\treturn first, total, nil\n}\n\n// UnpackedImageSize is the size of the unpacked snapshots.\n// Does not contain the size of the blobs in the content store. (Corresponds to Docker).\nfunc UnpackedImageSize(ctx context.Context, s snapshots.Snapshotter, img containerd.Image) (int64, error) {\n\tdiffIDs, err := img.RootFS(ctx)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tchainID := identity.ChainID(diffIDs).String()\n\t_, total, err := ResourceUsage(ctx, s, chainID)\n\n\treturn total.Size, err\n}\n\n// GetUnusedImages returns the list of all images which are not referenced by a container.\nfunc GetUnusedImages(ctx context.Context, client *containerd.Client, filters ...Filter) ([]images.Image, error) {\n\tvar (\n\t\timageStore     = client.ImageService()\n\t\tcontainerStore = client.ContainerService()\n\t)\n\n\tcontainers, err := containerStore.List(ctx)\n\tif err != nil {\n\t\treturn []images.Image{}, err\n\t}\n\n\tusedImages := make(map[string]struct{})\n\tfor _, container := range containers {\n\t\tusedImages[container.Image] = struct{}{}\n\t}\n\n\tallImages, err := imageStore.List(ctx)\n\tif err != nil {\n\t\treturn []images.Image{}, err\n\t}\n\n\tunusedImages := make([]images.Image, 0, len(allImages))\n\tfor _, image := range allImages {\n\t\tif _, ok := usedImages[image.Name]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tunusedImages = append(unusedImages, image)\n\t}\n\n\treturn ApplyFilters(unusedImages, filters...)\n}\n\n// GetDanglingImages returns the list of all images which are not tagged.\nfunc GetDanglingImages(ctx context.Context, client *containerd.Client, filters ...Filter) ([]images.Image, error) {\n\tvar (\n\t\timageStore = client.ImageService()\n\t)\n\n\tallImages, err := imageStore.List(ctx)\n\tif err != nil {\n\t\treturn []images.Image{}, err\n\t}\n\n\tfilters = append([]Filter{FilterDanglingImages()}, filters...)\n\n\treturn ApplyFilters(allImages, filters...)\n}\n\n// addHealthCheckToImageConfig extracts health check information from the image content store and adds it to the labels\nfunc addHealthCheckToImageConfig(rawConfigContent []byte, config *ocispec.ImageConfig) error {\n\tvar imgConfig struct {\n\t\tConfig struct {\n\t\t\tHealthcheck *healthcheck.Healthcheck `json:\"Healthcheck,omitempty\"`\n\t\t} `json:\"config\"`\n\t}\n\n\tif err := json.Unmarshal(rawConfigContent, &imgConfig); err != nil {\n\t\treturn err\n\t}\n\n\tif imgConfig.Config.Healthcheck != nil {\n\t\thealthCheckJSON, err := json.Marshal(imgConfig.Config.Healthcheck)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif config.Labels == nil {\n\t\t\tconfig.Labels = make(map[string]string)\n\t\t}\n\t\tconfig.Labels[labels.HealthCheck] = string(healthCheckJSON)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/imgutil/imgutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestParseRepoTag(t *testing.T) {\n\ttype testCase struct {\n\t\timgName string\n\t\trepo    string\n\t\ttag     string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\timgName: \"127.0.0.1:5000/foo/bar:baz\",\n\t\t\trepo:    \"127.0.0.1:5000/foo/bar\",\n\t\t\ttag:     \"baz\",\n\t\t},\n\t\t{\n\t\t\timgName: \"docker.io/library/alpine:latest\",\n\t\t\trepo:    \"alpine\",\n\t\t\ttag:     \"latest\",\n\t\t},\n\t\t{\n\t\t\timgName: \"docker.io/foo/bar:baz\",\n\t\t\trepo:    \"foo/bar\",\n\t\t\ttag:     \"baz\",\n\t\t},\n\t\t{\n\t\t\t// created by BuildKit\n\t\t\timgName: \"overlayfs@sha256:da203733d47434b9e8b4d3f70e1c0c3ea59438353252fe600cb9eb1a1e808c4f\",\n\t\t\trepo:    \"overlayfs\",\n\t\t\ttag:     \"\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\trepo, tag := ParseRepoTag(tc.imgName)\n\t\tassert.Equal(t, tc.repo, repo)\n\t\tassert.Equal(t, tc.tag, tag)\n\t}\n}\n"
  },
  {
    "path": "pkg/imgutil/jobs/jobs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage jobs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/containerd/v2/pkg/progress\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n)\n\n// ShowProgress continuously updates the output with job progress\n// by checking status in the content store.\n//\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L219-L336\nfunc ShowProgress(ctx context.Context, ongoing *Jobs, cs content.Store, out io.Writer) {\n\tvar (\n\t\tticker   = time.NewTicker(100 * time.Millisecond)\n\t\tfw       = progress.NewWriter(out)\n\t\tstart    = time.Now()\n\t\tstatuses = map[string]StatusInfo{}\n\t\tdone     bool\n\t)\n\tdefer ticker.Stop()\n\nouter:\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tfw.Flush()\n\n\t\t\ttw := tabwriter.NewWriter(fw, 1, 8, 1, ' ', 0)\n\n\t\t\tresolved := StatusResolved\n\t\t\tif !ongoing.IsResolved() {\n\t\t\t\tresolved = StatusResolving\n\t\t\t}\n\t\t\tstatuses[ongoing.name] = StatusInfo{\n\t\t\t\tRef:    ongoing.name,\n\t\t\t\tStatus: resolved,\n\t\t\t}\n\t\t\tkeys := []string{ongoing.name}\n\n\t\t\tactiveSeen := map[string]struct{}{}\n\t\t\tif !done {\n\t\t\t\tactive, err := cs.ListStatuses(ctx, \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.G(ctx).WithError(err).Error(\"active check failed\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// update status of active entries!\n\t\t\t\tfor _, active := range active {\n\t\t\t\t\tstatuses[active.Ref] = StatusInfo{\n\t\t\t\t\t\tRef:       active.Ref,\n\t\t\t\t\t\tStatus:    StatusDownloading,\n\t\t\t\t\t\tOffset:    active.Offset,\n\t\t\t\t\t\tTotal:     active.Total,\n\t\t\t\t\t\tStartedAt: active.StartedAt,\n\t\t\t\t\t\tUpdatedAt: active.UpdatedAt,\n\t\t\t\t\t}\n\t\t\t\t\tactiveSeen[active.Ref] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// now, update the items in jobs that are not in active\n\t\t\tfor _, j := range ongoing.Jobs() {\n\t\t\t\tkey := remotes.MakeRefKey(ctx, j)\n\t\t\t\tkeys = append(keys, key)\n\t\t\t\tif _, ok := activeSeen[key]; ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tstatus, ok := statuses[key]\n\t\t\t\tif !done && (!ok || status.Status == StatusDownloading) {\n\t\t\t\t\tinfo, err := cs.Info(ctx, j.Digest)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tif !errdefs.IsNotFound(err) {\n\t\t\t\t\t\t\tlog.G(ctx).WithError(err).Error(\"failed to get content info\")\n\t\t\t\t\t\t\tcontinue outer\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstatuses[key] = StatusInfo{\n\t\t\t\t\t\t\tRef:    key,\n\t\t\t\t\t\t\tStatus: StatusWaiting,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if info.CreatedAt.After(start) {\n\t\t\t\t\t\tstatuses[key] = StatusInfo{\n\t\t\t\t\t\t\tRef:       key,\n\t\t\t\t\t\t\tStatus:    StatusDone,\n\t\t\t\t\t\t\tOffset:    info.Size,\n\t\t\t\t\t\t\tTotal:     info.Size,\n\t\t\t\t\t\t\tUpdatedAt: info.CreatedAt,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstatuses[key] = StatusInfo{\n\t\t\t\t\t\t\tRef:    key,\n\t\t\t\t\t\t\tStatus: StatusExists,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if done {\n\t\t\t\t\tif ok {\n\t\t\t\t\t\tif status.Status != StatusDone && status.Status != StatusExists {\n\t\t\t\t\t\t\tstatus.Status = StatusDone\n\t\t\t\t\t\t\tstatuses[key] = status\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstatuses[key] = StatusInfo{\n\t\t\t\t\t\t\tRef:    key,\n\t\t\t\t\t\t\tStatus: StatusDone,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar ordered []StatusInfo\n\t\t\tfor _, key := range keys {\n\t\t\t\tordered = append(ordered, statuses[key])\n\t\t\t}\n\n\t\t\tDisplay(tw, ordered, start)\n\t\t\ttw.Flush()\n\n\t\t\tif done {\n\t\t\t\tfw.Flush()\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\tdone = true // allow ui to update once more\n\t\t}\n\t}\n}\n\n// Jobs provides a way of identifying the download keys for a particular task\n// encountering during the pull walk.\n//\n// This is very minimal and will probably be replaced with something more\n// featured.\n//\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L338-L349\ntype Jobs struct {\n\tname     string\n\tadded    map[digest.Digest]struct{}\n\tdescs    []ocispec.Descriptor\n\tmu       sync.Mutex\n\tresolved bool\n}\n\n// New creates a new instance of the job status tracker.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L351-L357\nfunc New(name string) *Jobs {\n\treturn &Jobs{\n\t\tname:  name,\n\t\tadded: map[digest.Digest]struct{}{},\n\t}\n}\n\n// Add adds a descriptor to be tracked.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L359-L370\nfunc (j *Jobs) Add(desc ocispec.Descriptor) {\n\tj.mu.Lock()\n\tdefer j.mu.Unlock()\n\tj.resolved = true\n\n\tif _, ok := j.added[desc.Digest]; ok {\n\t\treturn\n\t}\n\tj.descs = append(j.descs, desc)\n\tj.added[desc.Digest] = struct{}{}\n}\n\n// Jobs returns a list of all tracked descriptors.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L372-L379\nfunc (j *Jobs) Jobs() []ocispec.Descriptor {\n\tj.mu.Lock()\n\tdefer j.mu.Unlock()\n\n\tvar descs []ocispec.Descriptor\n\treturn append(descs, j.descs...)\n}\n\n// IsResolved checks whether a descriptor has been resolved.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L381-L386\nfunc (j *Jobs) IsResolved() bool {\n\tj.mu.Lock()\n\tdefer j.mu.Unlock()\n\treturn j.resolved\n}\n\n// StatusInfoStatus describes status info for an upload or download.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L388-L400\ntype StatusInfoStatus string\n\nconst (\n\tStatusResolved    StatusInfoStatus = \"resolved\"\n\tStatusResolving   StatusInfoStatus = \"resolving\"\n\tStatusWaiting     StatusInfoStatus = \"waiting\"\n\tStatusCommitting  StatusInfoStatus = \"committing\"\n\tStatusDone        StatusInfoStatus = \"done\"\n\tStatusDownloading StatusInfoStatus = \"downloading\"\n\tStatusUploading   StatusInfoStatus = \"uploading\"\n\tStatusExists      StatusInfoStatus = \"exists\"\n)\n\n// StatusInfo holds the status info for an upload or download.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L402-L410\ntype StatusInfo struct {\n\tRef       string\n\tStatus    StatusInfoStatus\n\tOffset    int64\n\tTotal     int64\n\tStartedAt time.Time\n\tUpdatedAt time.Time\n}\n\n// Display pretty prints out the download or upload progress.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/content/fetch.go#L412-L452\nfunc Display(w io.Writer, statuses []StatusInfo, start time.Time) {\n\tvar total int64\n\tfor _, status := range statuses {\n\t\ttotal += status.Offset\n\t\tswitch status.Status {\n\t\tcase StatusDownloading, StatusUploading:\n\t\t\tvar bar progress.Bar\n\t\t\tif status.Total > 0.0 {\n\t\t\t\tbar = progress.Bar(float64(status.Offset) / float64(status.Total))\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"%s:\\t%s\\t%40r\\t%8.8s/%s\\t\\n\",\n\t\t\t\tstatus.Ref,\n\t\t\t\tstatus.Status,\n\t\t\t\tbar,\n\t\t\t\tprogress.Bytes(status.Offset), progress.Bytes(status.Total))\n\t\tcase StatusResolving, StatusWaiting:\n\t\t\tbar := progress.Bar(0.0)\n\t\t\tfmt.Fprintf(w, \"%s:\\t%s\\t%40r\\t\\n\",\n\t\t\t\tstatus.Ref,\n\t\t\t\tstatus.Status,\n\t\t\t\tbar)\n\t\tdefault:\n\t\t\tbar := progress.Bar(1.0)\n\t\t\tfmt.Fprintf(w, \"%s:\\t%s\\t%40r\\t\\n\",\n\t\t\t\tstatus.Ref,\n\t\t\t\tstatus.Status,\n\t\t\t\tbar)\n\t\t}\n\t}\n\n\tfmt.Fprintf(w, \"elapsed: %-4.1fs\\ttotal: %7.6v\\t(%v)\\t\\n\",\n\t\ttime.Since(start).Seconds(),\n\t\t// TODO(stevvooe): These calculations are actually way off.\n\t\t// Need to account for previously downloaded data. These\n\t\t// will basically be right for a download the first time\n\t\t// but will be skewed if restarting, as it includes the\n\t\t// data into the start time before.\n\t\tprogress.Bytes(total),\n\t\tprogress.NewBytesPerSecond(total, time.Since(start)))\n}\n"
  },
  {
    "path": "pkg/imgutil/load/load.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage load\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/transfer\"\n\ttarchive \"github.com/containerd/containerd/v2/core/transfer/archive\"\n\ttransferimage \"github.com/containerd/containerd/v2/core/transfer/image\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/transferutil\"\n)\n\n// FromArchive loads and unpacks the images from the tar archive specified in image load options.\nfunc FromArchive(ctx context.Context, client *containerd.Client, options types.ImageLoadOptions) ([]images.Image, error) {\n\tif options.Input != \"\" {\n\t\tf, err := os.Open(options.Input)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer f.Close()\n\t\toptions.Stdin = f\n\t} else {\n\t\t// check if stdin is empty.\n\t\tstdinStat, err := os.Stdin.Stat()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif stdinStat.Size() == 0 && (stdinStat.Mode()&os.ModeNamedPipe) == 0 {\n\t\t\treturn nil, errors.New(\"stdin is empty and input flag is not specified\")\n\t\t}\n\t}\n\n\tif _, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platform); err != nil {\n\t\treturn nil, err\n\t}\n\n\timageService := client.ImageService()\n\tbeforeImages, err := imageService.List(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbeforeSet := make(map[string]bool)\n\tfor _, img := range beforeImages {\n\t\tbeforeSet[img.Name] = true\n\t}\n\n\tvar storeOpts []transferimage.StoreOpt\n\tplatUnpack := platforms.DefaultSpec()\n\tif len(options.Platform) > 0 {\n\t\tp, err := platforms.Parse(options.Platform[0])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid platform %q: %w\", options.Platform[0], err)\n\t\t}\n\t\tplatUnpack = p\n\t\tstoreOpts = append(storeOpts, transferimage.WithPlatforms(p))\n\t} else if !options.AllPlatforms {\n\t\tstoreOpts = append(storeOpts, transferimage.WithPlatforms(platUnpack))\n\t}\n\tstoreOpts = append(storeOpts, transferimage.WithUnpack(platUnpack, options.GOptions.Snapshotter))\n\tstoreOpts = append(storeOpts, transferimage.WithDigestRef(\"import\", true, true))\n\n\tvar loadedImages []images.Image\n\tpf, done := transferutil.ProgressHandler(ctx, options.Stdout)\n\tdefer done()\n\n\terr = client.Transfer(ctx,\n\t\ttarchive.NewImageImportStream(options.Stdin, \"\"),\n\t\ttransferimage.NewStore(\"\", storeOpts...),\n\t\ttransfer.WithProgress(func(p transfer.Progress) {\n\t\t\tif p.Event == \"saved\" {\n\t\t\t\tif img, err := imageService.Get(ctx, p.Name); err == nil {\n\t\t\t\t\tif !beforeSet[img.Name] {\n\t\t\t\t\t\tloadedImages = append(loadedImages, img)\n\t\t\t\t\t\tif !options.Quiet {\n\t\t\t\t\t\t\tfmt.Fprintf(options.Stdout, \"Loaded image: %s\\n\", img.Name)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tpf(p)\n\t\t}),\n\t)\n\n\treturn loadedImages, err\n}\n\n// FromOCIArchive loads and unpacks the images from the OCI formatted archive at the provided file system path.\nfunc FromOCIArchive(ctx context.Context, client *containerd.Client, pathToOCIArchive string, options types.ImageLoadOptions) ([]images.Image, error) {\n\tconst ociArchivePrefix = \"oci-archive://\"\n\tpathToOCIArchive = strings.TrimPrefix(pathToOCIArchive, ociArchivePrefix)\n\n\tconst separator = \":\"\n\tif strings.Contains(pathToOCIArchive, separator) {\n\t\tsubs := strings.Split(pathToOCIArchive, separator)\n\t\tif len(subs) != 2 {\n\t\t\treturn nil, errors.New(\"too many seperators found in oci-archive path\")\n\t\t}\n\t\tpathToOCIArchive = subs[0]\n\t}\n\n\toptions.Input = pathToOCIArchive\n\n\treturn FromArchive(ctx, client, options)\n}\n"
  },
  {
    "path": "pkg/imgutil/pull/pull.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package pull forked from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/content/fetch.go\npackage pull\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/jobs\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n)\n\n// Config for content fetch\ntype Config struct {\n\t// Resolver\n\tResolver remotes.Resolver\n\t// ProgressOutput to display progress\n\tProgressOutput io.Writer\n\t// RemoteOpts, e.g. containerd.WithPullUnpack.\n\t//\n\t// Regardless to RemoteOpts, the following opts are always set:\n\t// WithResolver, WithImageHandler\n\t//\n\t// RemoteOpts related to unpacking can be set only when len(Platforms) is 1.\n\tRemoteOpts []containerd.RemoteOpt\n\tPlatforms  []ocispec.Platform // empty for all-platforms\n}\n\n// Pull loads all resources into the content store and returns the image\nfunc Pull(ctx context.Context, client *containerd.Client, ref string, config *Config) (containerd.Image, error) {\n\tongoing := jobs.New(ref)\n\n\tpctx, stopProgress := context.WithCancel(ctx)\n\tprogress := make(chan struct{})\n\n\tgo func() {\n\t\tif config.ProgressOutput != nil {\n\t\t\t// no progress bar, because it hides some debug logs\n\t\t\tjobs.ShowProgress(pctx, ongoing, client.ContentStore(), config.ProgressOutput)\n\t\t}\n\t\tclose(progress)\n\t}()\n\n\th := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\tif desc.MediaType != images.MediaTypeDockerSchema1Manifest {\n\t\t\tongoing.Add(desc)\n\t\t}\n\t\treturn nil, nil\n\t})\n\n\tlog.G(pctx).WithField(\"image\", ref).Debug(\"fetching\")\n\tplatformMC := platformutil.NewMatchComparerFromOCISpecPlatformSlice(config.Platforms)\n\topts := []containerd.RemoteOpt{\n\t\tcontainerd.WithResolver(config.Resolver),\n\t\tcontainerd.WithImageHandler(h),\n\t\tcontainerd.WithPlatformMatcher(platformMC),\n\t}\n\topts = append(opts, config.RemoteOpts...)\n\n\tvar (\n\t\timg containerd.Image\n\t\terr error\n\t)\n\tif len(config.Platforms) == 1 {\n\t\t// client.Pull is for single-platform (w/ unpacking)\n\t\timg, err = client.Pull(pctx, ref, opts...)\n\t} else {\n\t\t// client.Fetch is for multi-platform (w/o unpacking)\n\t\tvar imagesImg images.Image\n\t\timagesImg, err = client.Fetch(pctx, ref, opts...)\n\t\timg = containerd.NewImageWithPlatform(client, imagesImg, platformMC)\n\t}\n\tstopProgress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t<-progress\n\treturn img, nil\n}\n"
  },
  {
    "path": "pkg/imgutil/push/push.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package push derived from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/images/push.go\npackage push\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\t\"golang.org/x/sync/errgroup\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/containerd/v2/core/remotes/docker\"\n\t\"github.com/containerd/containerd/v2/pkg/progress\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/jobs\"\n)\n\n// Push pushes an image to a remote registry.\nfunc Push(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, pushTracker docker.StatusTracker, stdout io.Writer,\n\tlocalRef, remoteRef string, platform platforms.MatchComparer, allowNonDist, quiet bool) error {\n\timg, err := client.ImageService().Get(ctx, localRef)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to resolve image to manifest: %w\", err)\n\t}\n\tdesc := img.Target\n\n\tongoing := newPushJobs(pushTracker)\n\n\teg, ctx := errgroup.WithContext(ctx)\n\n\t// used to notify the progress writer\n\tdoneCh := make(chan struct{})\n\n\teg.Go(func() error {\n\t\tdefer close(doneCh)\n\n\t\tlog.G(ctx).WithField(\"image\", remoteRef).WithField(\"digest\", desc.Digest).Debug(\"pushing\")\n\n\t\tjobHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\t\tif allowNonDist || !images.IsNonDistributable(desc.MediaType) {\n\t\t\t\tongoing.add(remotes.MakeRefKey(ctx, desc))\n\t\t\t}\n\t\t\treturn nil, nil\n\t\t})\n\n\t\tif !allowNonDist {\n\t\t\tjobHandler = remotes.SkipNonDistributableBlobs(jobHandler)\n\t\t}\n\n\t\treturn client.Push(ctx, remoteRef, desc,\n\t\t\tcontainerd.WithResolver(resolver),\n\t\t\tcontainerd.WithImageHandler(jobHandler),\n\t\t\tcontainerd.WithPlatformMatcher(platform),\n\t\t)\n\t})\n\n\tif !quiet {\n\t\teg.Go(func() error {\n\t\t\tvar (\n\t\t\t\tticker = time.NewTicker(100 * time.Millisecond)\n\t\t\t\tfw     = progress.NewWriter(stdout)\n\t\t\t\tstart  = time.Now()\n\t\t\t\tdone   bool\n\t\t\t)\n\n\t\t\tdefer ticker.Stop()\n\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-ticker.C:\n\t\t\t\t\tfw.Flush()\n\n\t\t\t\t\ttw := tabwriter.NewWriter(fw, 1, 8, 1, ' ', 0)\n\n\t\t\t\t\tjobs.Display(tw, ongoing.status(), start)\n\t\t\t\t\ttw.Flush()\n\n\t\t\t\t\tif done {\n\t\t\t\t\t\tfw.Flush()\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\tcase <-doneCh:\n\t\t\t\t\tdone = true\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\tdone = true // allow ui to update once more\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\treturn eg.Wait()\n}\n\ntype pushjobs struct {\n\tjobs    map[string]struct{}\n\tordered []string\n\ttracker docker.StatusTracker\n\tmu      sync.Mutex\n}\n\nfunc newPushJobs(tracker docker.StatusTracker) *pushjobs {\n\treturn &pushjobs{\n\t\tjobs:    make(map[string]struct{}),\n\t\ttracker: tracker,\n\t}\n}\n\nfunc (j *pushjobs) add(ref string) {\n\tj.mu.Lock()\n\tdefer j.mu.Unlock()\n\n\tif _, ok := j.jobs[ref]; ok {\n\t\treturn\n\t}\n\tj.ordered = append(j.ordered, ref)\n\tj.jobs[ref] = struct{}{}\n}\n\nfunc (j *pushjobs) status() []jobs.StatusInfo {\n\tj.mu.Lock()\n\tdefer j.mu.Unlock()\n\n\tstatuses := make([]jobs.StatusInfo, 0, len(j.jobs))\n\tfor _, name := range j.ordered {\n\t\tsi := jobs.StatusInfo{\n\t\t\tRef: name,\n\t\t}\n\n\t\tstatus, err := j.tracker.GetStatus(name)\n\t\tif err != nil {\n\t\t\tsi.Status = \"waiting\"\n\t\t} else {\n\t\t\tsi.Offset = status.Offset\n\t\t\tsi.Total = status.Total\n\t\t\tsi.StartedAt = status.StartedAt\n\t\t\tsi.UpdatedAt = status.UpdatedAt\n\t\t\tif status.Offset >= status.Total {\n\t\t\t\tif status.UploadUUID == \"\" {\n\t\t\t\t\tsi.Status = \"done\"\n\t\t\t\t} else {\n\t\t\t\t\tsi.Status = \"committing\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsi.Status = \"uploading\"\n\t\t\t}\n\t\t}\n\t\tstatuses = append(statuses, si)\n\t}\n\n\treturn statuses\n}\n"
  },
  {
    "path": "pkg/imgutil/snapshotter.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"strings\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\tctdsnapshotters \"github.com/containerd/containerd/v2/pkg/snapshotters\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/stargz-snapshotter/fs/source\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/pull\"\n\t\"github.com/containerd/nerdctl/v2/pkg/snapshotterutil\"\n)\n\nconst (\n\tsnapshotterNameOverlaybd = \"overlaybd\"\n\tsnapshotterNameStargz    = \"stargz\"\n\tsnapshotterNameNydus     = \"nydus\"\n\tsnapshotterNameSoci      = \"soci\"\n\tsnapshotterNameCvmfs     = \"cvmfs-snapshotter\"\n\n\t// prefetch size for stargz\n\tprefetchSize = 10 * 1024 * 1024\n)\n\n// remote snapshotters explicitly handled by nerdctl\nvar builtinRemoteSnapshotterOpts = map[string]snapshotterOpts{\n\tsnapshotterNameOverlaybd: &remoteSnapshotterOpts{snapshotter: \"overlaybd\"},\n\tsnapshotterNameStargz:    &remoteSnapshotterOpts{snapshotter: \"stargz\", extraLabels: stargzExtraLabels},\n\tsnapshotterNameNydus:     &remoteSnapshotterOpts{snapshotter: \"nydus\"},\n\tsnapshotterNameSoci:      &remoteSnapshotterOpts{snapshotter: \"soci\", extraLabels: sociExtraLabels},\n\tsnapshotterNameCvmfs:     &remoteSnapshotterOpts{snapshotter: \"cvmfs-snapshotter\"},\n}\n\n// snapshotterOpts is used to update pull config\n// for different snapshotters\ntype snapshotterOpts interface {\n\tapply(config *pull.Config, ref string, rFlags types.RemoteSnapshotterFlags)\n\tisRemote() bool\n}\n\n// getSnapshotterOpts get snapshotter opts by fuzzy matching of the snapshotter name\nfunc getSnapshotterOpts(snapshotter string) snapshotterOpts {\n\tfor sn, sno := range builtinRemoteSnapshotterOpts {\n\t\tif strings.Contains(snapshotter, sn) {\n\t\t\tif snapshotter != sn {\n\t\t\t\tlog.L.Debugf(\"assuming %s to be a %s-compatible snapshotter\", snapshotter, sn)\n\t\t\t}\n\t\t\treturn sno\n\t\t}\n\t}\n\n\treturn &defaultSnapshotterOpts{snapshotter: snapshotter}\n}\n\n// remoteSnapshotterOpts is used as a remote snapshotter implementation for\n// interface `snapshotterOpts.isRemote()` function\ntype remoteSnapshotterOpts struct {\n\tsnapshotter string\n\textraLabels func(func(images.Handler) images.Handler, types.RemoteSnapshotterFlags) func(images.Handler) images.Handler\n}\n\nfunc (rs *remoteSnapshotterOpts) isRemote() bool {\n\treturn true\n}\n\nfunc (rs *remoteSnapshotterOpts) apply(config *pull.Config, ref string, rFlags types.RemoteSnapshotterFlags) {\n\th := ctdsnapshotters.AppendInfoHandlerWrapper(ref)\n\tif rs.extraLabels != nil {\n\t\th = rs.extraLabels(h, rFlags)\n\t}\n\tconfig.RemoteOpts = append(\n\t\tconfig.RemoteOpts,\n\t\tcontainerd.WithImageHandlerWrapper(h),\n\t\tcontainerd.WithPullSnapshotter(rs.snapshotter),\n\t)\n}\n\n// defaultSnapshotterOpts is for snapshotters that\n// not handled separately\ntype defaultSnapshotterOpts struct {\n\tsnapshotter string\n}\n\nfunc (dsn *defaultSnapshotterOpts) apply(config *pull.Config, _ref string, rFlags types.RemoteSnapshotterFlags) {\n\tconfig.RemoteOpts = append(\n\t\tconfig.RemoteOpts,\n\t\tcontainerd.WithPullSnapshotter(dsn.snapshotter))\n}\n\n// defaultSnapshotterOpts is not a remote snapshotter\nfunc (dsn *defaultSnapshotterOpts) isRemote() bool {\n\treturn false\n}\n\nfunc stargzExtraLabels(f func(images.Handler) images.Handler, rFlags types.RemoteSnapshotterFlags) func(images.Handler) images.Handler {\n\treturn source.AppendExtraLabelsHandler(prefetchSize, f)\n}\n\nfunc sociExtraLabels(f func(images.Handler) images.Handler, rFlags types.RemoteSnapshotterFlags) func(images.Handler) images.Handler {\n\treturn snapshotterutil.SociAppendDefaultLabelsHandlerWrapper(rFlags.SociIndexDigest, f)\n}\n"
  },
  {
    "path": "pkg/imgutil/snapshotter_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\tdigest \"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\t\"gotest.tools/v3/assert\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\tctdsnapshotters \"github.com/containerd/containerd/v2/pkg/snapshotters\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/pull\"\n)\n\nconst (\n\ttargetRefLabel = \"containerd.io/snapshot/remote/stargz.reference\"\n\ttestRef        = \"test:latest\"\n)\n\nfunc TestGetSnapshotterOpts(t *testing.T) {\n\ttype testCase struct {\n\t\tsns   []string\n\t\tcheck func(t *testing.T, o snapshotterOpts)\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tsns:   []string{\"overlayfs\"},\n\t\t\tcheck: sameOpts(&defaultSnapshotterOpts{snapshotter: \"overlayfs\"}),\n\t\t},\n\t\t{\n\t\t\tsns:   []string{\"overlayfs2\"},\n\t\t\tcheck: sameOpts(&defaultSnapshotterOpts{snapshotter: \"overlayfs2\"}),\n\t\t},\n\t\t{\n\t\t\tsns:   []string{\"stargz\", \"stargz-v1\"},\n\t\t\tcheck: remoteSnOpts(\"stargz\", true),\n\t\t},\n\t\t{\n\t\t\tsns:   []string{\"soci\"},\n\t\t\tcheck: remoteSnOpts(\"soci\", true),\n\t\t},\n\t\t{\n\t\t\tsns:   []string{\"overlaybd\", \"overlaybd-v2\"},\n\t\t\tcheck: sameOpts(&remoteSnapshotterOpts{snapshotter: \"overlaybd\"}),\n\t\t},\n\t\t{\n\t\t\tsns:   []string{\"nydus\", \"nydus-v3\"},\n\t\t\tcheck: sameOpts(&remoteSnapshotterOpts{snapshotter: \"nydus\"}),\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tfor i := range tc.sns {\n\t\t\tgot := getSnapshotterOpts(tc.sns[i])\n\t\t\ttc.check(t, got)\n\t\t}\n\t}\n}\n\nfunc remoteSnOpts(name string, withExtra bool) func(*testing.T, snapshotterOpts) {\n\treturn func(t *testing.T, got snapshotterOpts) {\n\t\topts, ok := got.(*remoteSnapshotterOpts)\n\t\tassert.Equal(t, ok, true)\n\t\tassert.Equal(t, opts.snapshotter, name)\n\t\tassert.Equal(t, opts.extraLabels != nil, withExtra)\n\t}\n}\n\nfunc sameOpts(want snapshotterOpts) func(*testing.T, snapshotterOpts) {\n\treturn func(t *testing.T, got snapshotterOpts) {\n\t\tif !reflect.DeepEqual(got, want) {\n\t\t\tt.Errorf(\"getSnapshotterOpts() got = %v, want %v\", got, want)\n\t\t}\n\t}\n}\n\nfunc getAndApplyRemoteOpts(t *testing.T, sn string) *containerd.RemoteContext {\n\tconfig := &pull.Config{}\n\tsnOpts := getSnapshotterOpts(sn)\n\trFlags := types.RemoteSnapshotterFlags{}\n\tsnOpts.apply(config, testRef, rFlags)\n\n\trc := &containerd.RemoteContext{}\n\tfor _, o := range config.RemoteOpts {\n\t\t// here passing a nil client is safe\n\t\t// because the remote opts will not use client\n\t\tif err := o(nil, rc); err != nil {\n\t\t\tt.Errorf(\"failed to apply remote opts: %s\", err)\n\t\t}\n\t}\n\n\treturn rc\n}\n\nfunc TestDefaultSnapshotterOpts(t *testing.T) {\n\trc := getAndApplyRemoteOpts(t, \"overlayfs\")\n\tassert.Equal(t, rc.Snapshotter, \"overlayfs\")\n}\n\n// dummyImageHandler will return a dummy layer\n// see https://github.com/containerd/containerd/blob/77d53d2d230c3bcd3f02e6f493019a72905c875b/images/mediatypes.go#L115\ntype dummyImageHandler struct{}\n\nfunc (dih *dummyImageHandler) Handle(_ctx context.Context, _desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {\n\treturn []ocispec.Descriptor{\n\t\t{\n\t\t\tMediaType: \"application/vnd.oci.image.layer.dummy\",\n\t\t\tDigest:    digest.FromString(\"dummy\"),\n\t\t},\n\t}, nil\n}\n\nfunc TestRemoteSnapshotterOpts(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tcheck []func(t *testing.T, a map[string]string)\n\t}{\n\t\t{\n\t\t\tname: \"stargz\",\n\t\t\tcheck: []func(t *testing.T, a map[string]string){\n\t\t\t\tcheckRemoteSnapshotterAnnotataions, checkStargzSnapshotterAnnotataions,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"soci\",\n\t\t\tcheck: []func(t *testing.T, a map[string]string){\n\t\t\t\tcheckRemoteSnapshotterAnnotataions, checkSociSnapshotterAnnotataions,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"nydus\",\n\t\t\tcheck: []func(t *testing.T, a map[string]string){checkRemoteSnapshotterAnnotataions},\n\t\t},\n\t\t{\n\t\t\tname:  \"overlaybd\",\n\t\t\tcheck: []func(t *testing.T, a map[string]string){checkRemoteSnapshotterAnnotataions},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tsn := tt.name\n\t\tt.Run(sn, func(t *testing.T) {\n\t\t\trc := getAndApplyRemoteOpts(t, sn)\n\t\t\tassert.Equal(t, rc.Snapshotter, sn)\n\n\t\t\tdesc := ocispec.Descriptor{\n\t\t\t\tMediaType: ocispec.MediaTypeImageManifest,\n\t\t\t}\n\n\t\t\th := &dummyImageHandler{}\n\t\t\tgot, err := rc.HandlerWrapper(h).Handle(context.Background(), desc)\n\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Check(t, len(got) == 1)\n\t\t\tfor _, f := range tt.check {\n\t\t\t\tf(t, got[0].Annotations)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkRemoteSnapshotterAnnotataions(t *testing.T, a map[string]string) {\n\tassert.Check(t, a != nil)\n\tassert.Equal(t, a[ctdsnapshotters.TargetRefLabel], testRef)\n}\n\nfunc checkStargzSnapshotterAnnotataions(t *testing.T, a map[string]string) {\n\tassert.Check(t, a != nil)\n\t_, ok := a[\"containerd.io/snapshot/remote/urls\"]\n\tassert.Equal(t, ok, true)\n}\n\n// using values from soci source to check for annotations (\n// see https://github.com/awslabs/soci-snapshotter/blob/b05ba712d246ecc5146469f87e5e9305702fd72b/fs/source/source.go#L80C1-L80C6\nfunc checkSociSnapshotterAnnotataions(t *testing.T, a map[string]string) {\n\tassert.Check(t, a != nil)\n\t_, ok := a[\"containerd.io/snapshot/remote/soci.size\"]\n\tassert.Equal(t, ok, true)\n\t_, ok = a[\"containerd.io/snapshot/remote/image.layers.size\"]\n\tassert.Equal(t, ok, true)\n\t_, ok = a[\"containerd.io/snapshot/remote/soci.index.digest\"]\n\tassert.Equal(t, ok, true)\n\n}\n"
  },
  {
    "path": "pkg/imgutil/transfer.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage imgutil\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/remotes/docker\"\n\t\"github.com/containerd/containerd/v2/core/transfer\"\n\ttransferimage \"github.com/containerd/containerd/v2/core/transfer/image\"\n\t\"github.com/containerd/containerd/v2/core/transfer/registry\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/errutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/transferutil\"\n)\n\nfunc prepareImageStore(ctx context.Context, parsedReference *referenceutil.ImageReference, options types.ImagePullOptions) (*transferimage.Store, error) {\n\tvar storeOpts []transferimage.StoreOpt\n\tif len(options.OCISpecPlatform) > 0 {\n\t\tstoreOpts = append(storeOpts, transferimage.WithPlatforms(options.OCISpecPlatform...))\n\t}\n\n\tunpackEnabled := len(options.OCISpecPlatform) == 1\n\tif options.Unpack != nil {\n\t\tunpackEnabled = *options.Unpack\n\t\tif unpackEnabled && len(options.OCISpecPlatform) != 1 {\n\t\t\treturn nil, fmt.Errorf(\"unpacking requires a single platform to be specified (e.g., --platform=amd64)\")\n\t\t}\n\t}\n\n\tif unpackEnabled {\n\t\tplatform := options.OCISpecPlatform[0]\n\t\tsnapshotter := options.GOptions.Snapshotter\n\t\tstoreOpts = append(storeOpts, transferimage.WithUnpack(platform, snapshotter))\n\t}\n\n\treturn transferimage.NewStore(parsedReference.String(), storeOpts...), nil\n}\n\nfunc createOCIRegistry(ctx context.Context, parsedReference *referenceutil.ImageReference, gOptions types.GlobalCommandOptions, plainHTTP bool) (*registry.OCIRegistry, func(), error) {\n\tch, err := dockerconfigresolver.NewCredentialHelper(parsedReference.Domain)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\topts := []registry.Opt{\n\t\tregistry.WithCredentials(ch),\n\t}\n\n\tvar tmpHostsDir string\n\tcleanup := func() {\n\t\tif tmpHostsDir != \"\" {\n\t\t\tos.RemoveAll(tmpHostsDir)\n\t\t}\n\t}\n\n\t// If insecure-registry is set, create a temporary hosts.toml with skip_verify\n\tif gOptions.InsecureRegistry {\n\t\ttmpHostsDir, err = dockerconfigresolver.CreateTmpHostsConfig(parsedReference.Domain, true)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to create temporary hosts.toml for %q, continuing without it\", parsedReference.Domain)\n\t\t} else if tmpHostsDir != \"\" {\n\t\t\topts = append(opts, registry.WithHostDir(tmpHostsDir))\n\t\t}\n\t} else if len(gOptions.HostsDir) > 0 {\n\t\topts = append(opts, registry.WithHostDir(gOptions.HostsDir[0]))\n\t}\n\n\tif isLocalHost, err := docker.MatchLocalhost(parsedReference.Domain); err != nil {\n\t\tcleanup()\n\t\treturn nil, nil, err\n\t} else if isLocalHost || plainHTTP {\n\t\topts = append(opts, registry.WithDefaultScheme(\"http\"))\n\t}\n\n\treg, err := registry.NewOCIRegistry(ctx, parsedReference.String(), opts...)\n\tif err != nil {\n\t\tcleanup()\n\t\treturn nil, nil, err\n\t}\n\n\treturn reg, cleanup, nil\n}\n\nfunc PullImageWithTransfer(ctx context.Context, client *containerd.Client, parsedReference *referenceutil.ImageReference, rawRef string, options types.ImagePullOptions) (*EnsuredImage, error) {\n\tstore, err := prepareImageStore(ctx, parsedReference, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprogressWriter := options.Stderr\n\tif options.ProgressOutputToStdout {\n\t\tprogressWriter = options.Stdout\n\t}\n\n\tfetcher, cleanup, err := createOCIRegistry(ctx, parsedReference, options.GOptions, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer cleanup()\n\n\ttransferErr := doTransfer(ctx, client, fetcher, store, options.Quiet, progressWriter)\n\n\tif transferErr != nil && (errors.Is(transferErr, http.ErrSchemeMismatch) || errutil.IsErrConnectionRefused(transferErr) || errutil.IsErrHTTPResponseToHTTPSClient(transferErr) || errutil.IsErrTLSHandshakeFailure(transferErr)) {\n\t\tif options.GOptions.InsecureRegistry {\n\t\t\tlog.G(ctx).WithError(transferErr).Warnf(\"server %q does not seem to support HTTPS, falling back to plain HTTP\", parsedReference.Domain)\n\t\t\tfetcher, cleanup2, err := createOCIRegistry(ctx, parsedReference, options.GOptions, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tdefer cleanup2()\n\t\t\ttransferErr = doTransfer(ctx, client, fetcher, store, options.Quiet, progressWriter)\n\t\t}\n\t}\n\n\tif transferErr != nil {\n\t\treturn nil, transferErr\n\t}\n\n\timageStore := client.ImageService()\n\tstored, err := store.Get(ctx, imageStore)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tplMatch := platformutil.NewMatchComparerFromOCISpecPlatformSlice(options.OCISpecPlatform)\n\tcontainerdImage := containerd.NewImageWithPlatform(client, stored, plMatch)\n\timgConfig, err := getImageConfig(ctx, containerdImage)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsnapshotter := options.GOptions.Snapshotter\n\tsnOpt := getSnapshotterOpts(snapshotter)\n\n\treturn &EnsuredImage{\n\t\tRef:         rawRef,\n\t\tImage:       containerdImage,\n\t\tImageConfig: *imgConfig,\n\t\tSnapshotter: snapshotter,\n\t\tRemote:      snOpt.isRemote(),\n\t}, nil\n}\n\nfunc preparePushStore(pushRef string, options types.ImagePushOptions) (*transferimage.Store, error) {\n\tplatformsSlice, err := platformutil.NewOCISpecPlatformSlice(options.AllPlatforms, options.Platforms)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstoreOpts := []transferimage.StoreOpt{}\n\tif len(platformsSlice) > 0 {\n\t\tstoreOpts = append(storeOpts, transferimage.WithPlatforms(platformsSlice...))\n\t}\n\n\treturn transferimage.NewStore(pushRef, storeOpts...), nil\n}\n\nfunc PushImageWithTransfer(ctx context.Context, client *containerd.Client, parsedReference *referenceutil.ImageReference, pushRef, rawRef string, options types.ImagePushOptions) error {\n\tsource, err := preparePushStore(pushRef, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprogressWriter := io.Discard\n\tif options.Stdout != nil {\n\t\tprogressWriter = options.Stdout\n\t}\n\n\tpusher, cleanup, err := createOCIRegistry(ctx, parsedReference, options.GOptions, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer cleanup()\n\n\ttransferErr := doTransfer(ctx, client, source, pusher, options.Quiet, progressWriter)\n\n\tif transferErr != nil && (errors.Is(transferErr, http.ErrSchemeMismatch) || errutil.IsErrConnectionRefused(transferErr) || errutil.IsErrHTTPResponseToHTTPSClient(transferErr) || errutil.IsErrTLSHandshakeFailure(transferErr)) {\n\t\tif options.GOptions.InsecureRegistry {\n\t\t\tlog.G(ctx).WithError(transferErr).Warnf(\"server %q does not seem to support HTTPS, falling back to plain HTTP\", parsedReference.Domain)\n\t\t\tpusher, cleanup2, err := createOCIRegistry(ctx, parsedReference, options.GOptions, true)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer cleanup2()\n\t\t\ttransferErr = doTransfer(ctx, client, source, pusher, options.Quiet, progressWriter)\n\t\t}\n\t}\n\n\tif transferErr != nil {\n\t\tlog.G(ctx).WithError(transferErr).Errorf(\"server %q does not seem to support HTTPS\", parsedReference.Domain)\n\t\tlog.G(ctx).Info(\"Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)\")\n\t\treturn transferErr\n\t}\n\n\treturn nil\n}\n\nfunc doTransfer(ctx context.Context, client *containerd.Client, src, dst interface{}, quiet bool, progressWriter io.Writer) error {\n\topts := make([]transfer.Opt, 0, 1)\n\tif !quiet {\n\t\tpf, done := transferutil.ProgressHandler(ctx, progressWriter)\n\t\tdefer done()\n\t\topts = append(opts, transfer.WithProgress(pf))\n\t}\n\treturn client.Transfer(ctx, src, dst, opts...)\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/docker/docker/pkg/sysinfo\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/introspection\"\n\tptypes \"github.com/containerd/containerd/v2/pkg/protobuf/types\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/version\"\n)\n\nfunc NativeDaemonInfo(ctx context.Context, client *containerd.Client) (*native.DaemonInfo, error) {\n\tintroService := client.IntrospectionService()\n\tplugins, err := introService.Plugins(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserver, err := introService.Server(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tversionService := client.VersionService()\n\tversion, err := versionService.Version(ctx, &ptypes.Empty{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdaemonInfo := &native.DaemonInfo{\n\t\tPlugins: plugins,\n\t\tServer:  server,\n\t\tVersion: version,\n\t}\n\treturn daemonInfo, nil\n}\n\nfunc Info(ctx context.Context, client *containerd.Client, snapshotter, cgroupManager string) (*dockercompat.Info, error) {\n\tdaemonVersion, err := client.Version(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tintroService := client.IntrospectionService()\n\tdaemonIntro, err := introService.Server(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsnapshotterPlugins, err := GetSnapshotterNames(ctx, introService)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar info dockercompat.Info\n\tinfo.ID = daemonIntro.UUID\n\t// Storage drivers and logging drivers are not really Server concept for nerdctl, but mimics `docker info` output\n\tinfo.Driver = snapshotter\n\tinfo.Plugins.Storage = snapshotterPlugins\n\tinfo.SystemTime = time.Now().Format(time.RFC3339Nano)\n\tinfo.LoggingDriver = \"json-file\" // hard-coded\n\tinfo.CgroupDriver = cgroupManager\n\tinfo.CgroupVersion = CgroupsVersion()\n\tinfo.KernelVersion = UnameR()\n\tinfo.OperatingSystem = DistroName()\n\tinfo.OSType = runtime.GOOS\n\tinfo.Architecture = UnameM()\n\tinfo.Name, err = os.Hostname()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo.ServerVersion = daemonVersion.Version\n\tfulfillPlatformInfo(&info)\n\treturn &info, nil\n}\n\nfunc GetSnapshotterNames(ctx context.Context, introService introspection.Service) ([]string, error) {\n\tvar names []string\n\tplugins, err := introService.Plugins(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, p := range plugins.Plugins {\n\t\tif strings.HasPrefix(p.Type, \"io.containerd.snapshotter.\") && p.InitErr == nil {\n\t\t\tnames = append(names, p.ID)\n\t\t}\n\t}\n\treturn names, nil\n}\n\nfunc ClientVersion() dockercompat.ClientVersion {\n\treturn dockercompat.ClientVersion{\n\t\tVersion:   version.GetVersion(),\n\t\tGitCommit: version.GetRevision(),\n\t\tGoVersion: runtime.Version(),\n\t\tOs:        runtime.GOOS,\n\t\tArch:      runtime.GOARCH,\n\t\tComponents: []dockercompat.ComponentVersion{\n\t\t\tbuildctlVersion(),\n\t\t},\n\t}\n}\n\nfunc ServerVersion(ctx context.Context, client *containerd.Client) (*dockercompat.ServerVersion, error) {\n\tdaemonVersion, err := client.Version(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv := &dockercompat.ServerVersion{\n\t\tComponents: []dockercompat.ComponentVersion{\n\t\t\t{\n\t\t\t\tName:    \"containerd\",\n\t\t\t\tVersion: daemonVersion.Version,\n\t\t\t\tDetails: map[string]string{\"GitCommit\": daemonVersion.Revision},\n\t\t\t},\n\t\t\truncVersion(),\n\t\t},\n\t}\n\treturn v, nil\n}\n\nfunc buildctlVersion() dockercompat.ComponentVersion {\n\tbuildctlBinary, err := buildkitutil.BuildctlBinary()\n\tif err != nil {\n\t\tlog.L.WithError(err).Warnf(\"unable to determine buildctl version\")\n\t\treturn dockercompat.ComponentVersion{Name: \"buildctl\"}\n\t}\n\n\tstdout, err := exec.Command(buildctlBinary, \"--version\").Output()\n\tif err != nil {\n\t\tlog.L.WithError(err).Warnf(\"unable to determine buildctl version\")\n\t\treturn dockercompat.ComponentVersion{Name: \"buildctl\"}\n\t}\n\n\tv, err := parseBuildctlVersion(stdout)\n\tif err != nil {\n\t\tlog.L.Warn(err)\n\t\treturn dockercompat.ComponentVersion{Name: \"buildctl\"}\n\t}\n\treturn *v\n}\n\nfunc parseBuildctlVersion(buildctlVersionStdout []byte) (*dockercompat.ComponentVersion, error) {\n\tfields := strings.Fields(strings.TrimSpace(string(buildctlVersionStdout)))\n\tvar v *dockercompat.ComponentVersion\n\tswitch len(fields) {\n\tcase 4:\n\t\tv = &dockercompat.ComponentVersion{\n\t\t\tName:    fields[0],\n\t\t\tVersion: fields[2],\n\t\t\tDetails: map[string]string{\"GitCommit\": fields[3]},\n\t\t}\n\tcase 3:\n\t\tv = &dockercompat.ComponentVersion{\n\t\t\tName:    fields[0],\n\t\t\tVersion: fields[2],\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unable to determine buildctl version, got %q\", string(buildctlVersionStdout))\n\t}\n\tif v.Name != \"buildctl\" {\n\t\treturn nil, fmt.Errorf(\"unable to determine buildctl version, got %q\", string(buildctlVersionStdout))\n\t}\n\treturn v, nil\n}\n\nfunc runcVersion() dockercompat.ComponentVersion {\n\tstdout, err := exec.Command(\"runc\", \"--version\").Output()\n\tif err != nil {\n\t\tlog.L.WithError(err).Warnf(\"unable to determine runc version\")\n\t\treturn dockercompat.ComponentVersion{Name: \"runc\"}\n\t}\n\tv, err := parseRuncVersion(stdout)\n\tif err != nil {\n\t\tlog.L.Warn(err)\n\t\treturn dockercompat.ComponentVersion{Name: \"runc\"}\n\t}\n\treturn *v\n}\n\nfunc parseRuncVersion(runcVersionStdout []byte) (*dockercompat.ComponentVersion, error) {\n\tvar versionList = strings.Split(strings.TrimSpace(string(runcVersionStdout)), \"\\n\")\n\tfirstLine := strings.Fields(versionList[0])\n\tif len(firstLine) != 3 || firstLine[0] != \"runc\" {\n\t\treturn nil, fmt.Errorf(\"unable to determine runc version, got: %s\", string(runcVersionStdout))\n\t}\n\tversion := firstLine[2]\n\n\tdetails := map[string]string{}\n\tfor _, detailsLine := range versionList[1:] {\n\t\tdetail := strings.SplitN(detailsLine, \":\", 2)\n\t\tif len(detail) != 2 {\n\t\t\tlog.L.Warnf(\"unable to determine one of runc details, got: %s, %d\", detail, len(detail))\n\t\t\tcontinue\n\t\t}\n\t\tswitch strings.TrimSpace(detail[0]) {\n\t\tcase \"commit\":\n\t\t\tdetails[\"GitCommit\"] = strings.TrimSpace(detail[1])\n\t\t}\n\t}\n\n\treturn &dockercompat.ComponentVersion{\n\t\tName:    \"runc\",\n\t\tVersion: version,\n\t\tDetails: details,\n\t}, nil\n}\n\n// getMobySysInfo returns the moby system info for the given cgroup manager\nfunc getMobySysInfo(cgroupManager string) *sysinfo.SysInfo {\n\tvar info dockercompat.Info\n\tinfo.CgroupVersion = CgroupsVersion()\n\tinfo.CgroupDriver = cgroupManager\n\treturn mobySysInfo(&info)\n}\n\n// BlockIOWeight returns whether Block IO weight is supported or not\nfunc BlockIOWeight(cgroupManager string) bool {\n\t// blkio weight is not available on cgroup v1 since kernel 5.0.\n\t// On cgroup v2, blkio weight is implemented using io.weight\n\treturn getMobySysInfo(cgroupManager).BlkioWeight\n}\n\n// BlockIOWeightDevice returns whether Block IO weight device is supported or not\nfunc BlockIOWeightDevice(cgroupManager string) bool {\n\treturn getMobySysInfo(cgroupManager).BlkioWeightDevice\n}\n\n// BlockIOReadBpsDevice returns whether Block IO read limit in bytes per second is supported or not\nfunc BlockIOReadBpsDevice(cgroupManager string) bool {\n\treturn getMobySysInfo(cgroupManager).BlkioReadBpsDevice\n}\n\n// BlockIOWriteBpsDevice returns whether Block IO write limit in bytes per second is supported or not\nfunc BlockIOWriteBpsDevice(cgroupManager string) bool {\n\treturn getMobySysInfo(cgroupManager).BlkioWriteBpsDevice\n}\n\n// BlockIOReadIOpsDevice returns whether Block IO read limit in IO per second is supported or not\nfunc BlockIOReadIOpsDevice(cgroupManager string) bool {\n\treturn getMobySysInfo(cgroupManager).BlkioReadIOpsDevice\n}\n\n// BlockIOWriteIOpsDevice returns whether Block IO write limit in IO per second is supported or not\nfunc BlockIOWriteIOpsDevice(cgroupManager string) bool {\n\treturn getMobySysInfo(cgroupManager).BlkioWriteIOpsDevice\n}\n\n// CPURealtime returns whether CPU realtime period is supported or not\nfunc CPURealtime(cgroupManager string) bool {\n\treturn getMobySysInfo(cgroupManager).CPURealtime\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"github.com/docker/docker/pkg/sysinfo\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n)\n\nconst UnameO = \"Darwin\"\n\nfunc CgroupsVersion() string {\n\treturn \"\"\n}\n\nfunc fulfillPlatformInfo(info *dockercompat.Info) {\n\t// unimplemented\n}\n\nfunc mobySysInfo(info *dockercompat.Info) *sysinfo.SysInfo {\n\tvar sysinfo sysinfo.SysInfo\n\treturn &sysinfo\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"github.com/docker/docker/pkg/sysinfo\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n)\n\nconst UnameO = \"FreeBSD\"\n\nfunc CgroupsVersion() string {\n\treturn \"\"\n}\n\nfunc fulfillPlatformInfo(info *dockercompat.Info) {\n\t// unimplemented\n}\n\nfunc mobySysInfo(info *dockercompat.Info) *sysinfo.SysInfo {\n\tvar sysinfo sysinfo.SysInfo\n\treturn &sysinfo\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/docker/docker/pkg/meminfo\"\n\t\"github.com/docker/docker/pkg/sysinfo\"\n\n\t\"github.com/containerd/cgroups/v3\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nconst UnameO = \"GNU/Linux\"\n\nfunc CgroupsVersion() string {\n\tif cgroups.Mode() == cgroups.Unified {\n\t\treturn \"2\"\n\t}\n\n\treturn \"1\"\n}\n\nfunc fulfillSecurityOptions(info *dockercompat.Info) {\n\tif apparmorutil.CanApplyExistingProfile() {\n\t\tinfo.SecurityOptions = append(info.SecurityOptions, \"name=apparmor\")\n\t\tif rootlessutil.IsRootless() && !apparmorutil.CanApplySpecificExistingProfile(defaults.AppArmorProfileName) {\n\t\t\tinfo.Warnings = append(info.Warnings, fmt.Sprintf(strings.TrimSpace(`\nWARNING: AppArmor profile %q is not loaded.\n         Use 'sudo nerdctl apparmor load' if you prefer to use AppArmor with rootless mode.\n         This warning is negligible if you do not intend to use AppArmor.`), defaults.AppArmorProfileName))\n\t\t}\n\t}\n\tinfo.SecurityOptions = append(info.SecurityOptions, \"name=seccomp,profile=\"+defaults.SeccompProfileName)\n\tif defaults.CgroupnsMode() == \"private\" {\n\t\tinfo.SecurityOptions = append(info.SecurityOptions, \"name=cgroupns\")\n\t}\n\tif rootlessutil.IsRootlessChild() {\n\t\tinfo.SecurityOptions = append(info.SecurityOptions, \"name=rootless\")\n\t}\n}\n\n// fulfillPlatformInfo fulfills cgroup and kernel info.\n//\n// fulfillPlatformInfo requires the following fields to be set:\n// SecurityOptions, CgroupDriver, CgroupVersion\nfunc fulfillPlatformInfo(info *dockercompat.Info) {\n\tfulfillSecurityOptions(info)\n\tmobySysInfo := mobySysInfo(info)\n\n\tif info.CgroupDriver == \"none\" {\n\t\tif info.CgroupVersion == \"2\" {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: Running in rootless-mode without cgroups. Systemd is required to enable cgroups in rootless-mode.\")\n\t\t} else {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: Running in rootless-mode without cgroups. To enable cgroups in rootless-mode, you need to boot the system in cgroup v2 mode.\")\n\t\t}\n\t} else {\n\t\tinfo.MemoryLimit = mobySysInfo.MemoryLimit\n\t\tif !info.MemoryLimit {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No memory limit support\")\n\t\t}\n\t\tinfo.SwapLimit = mobySysInfo.SwapLimit\n\t\tif !info.SwapLimit {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No swap limit support\")\n\t\t}\n\t\tinfo.CPUCfsPeriod = mobySysInfo.CPUCfs\n\t\tif !info.CPUCfsPeriod {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No cpu cfs period support\")\n\t\t}\n\t\tinfo.CPUCfsQuota = mobySysInfo.CPUCfs\n\t\tif !info.CPUCfsQuota {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No cpu cfs quota support\")\n\t\t}\n\t\tinfo.CPUShares = mobySysInfo.CPUShares\n\t\tif !info.CPUShares {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No cpu shares support\")\n\t\t}\n\t\tinfo.CPUSet = mobySysInfo.Cpuset\n\t\tif !info.CPUSet {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No cpuset support\")\n\t\t}\n\t\tinfo.PidsLimit = mobySysInfo.PidsLimit\n\t\tif !info.PidsLimit {\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No pids limit support\")\n\t\t}\n\t\tinfo.OomKillDisable = mobySysInfo.OomKillDisable\n\t\tif !info.OomKillDisable && info.CgroupVersion == \"1\" {\n\t\t\t// no warning for cgroup v2\n\t\t\tinfo.Warnings = append(info.Warnings, \"WARNING: No oom kill disable support\")\n\t\t}\n\t}\n\tinfo.IPv4Forwarding = !mobySysInfo.IPv4ForwardingDisabled\n\tif !info.IPv4Forwarding {\n\t\tinfo.Warnings = append(info.Warnings, \"WARNING: IPv4 forwarding is disabled\")\n\t}\n\t// FIXME: BridgeNFCallIP6TablesDisabled is deprecated in moby and always false now\n\t// Figure out what we want to do with this\n\tinfo.BridgeNfIptables = true // !mobySysInfo.BridgeNFCallIPTablesDisabled\n\tif !info.BridgeNfIptables {\n\t\tinfo.Warnings = append(info.Warnings, \"WARNING: bridge-nf-call-iptables is disabled\")\n\t}\n\t// FIXME: BridgeNFCallIP6TablesDisabled is deprecated in moby and always false now\n\t// Figure out what we want to do with this\n\tinfo.BridgeNfIP6tables = true // !mobySysInfo.BridgeNFCallIP6TablesDisabled\n\tif !info.BridgeNfIP6tables {\n\t\tinfo.Warnings = append(info.Warnings, \"WARNING: bridge-nf-call-ip6tables is disabled\")\n\t}\n\tinfo.NCPU = runtime.NumCPU()\n\tmemLimit, err := meminfo.Read()\n\tif err != nil {\n\t\tinfo.Warnings = append(info.Warnings, fmt.Sprintf(\"failed to read mem info: %v\", err))\n\t} else {\n\t\tinfo.MemTotal = memLimit.MemTotal\n\t}\n}\n\nfunc mobySysInfo(info *dockercompat.Info) *sysinfo.SysInfo {\n\tvar mobySysInfoOpts []sysinfo.Opt\n\tif info.CgroupDriver == \"systemd\" && info.CgroupVersion == \"2\" && rootlessutil.IsRootless() {\n\t\tg := fmt.Sprintf(\"/user.slice/user-%d.slice\", rootlessutil.ParentEUID())\n\t\tmobySysInfoOpts = append(mobySysInfoOpts, sysinfo.WithCgroup2GroupPath(g))\n\t}\n\tmobySysInfo := sysinfo.New(mobySysInfoOpts...)\n\treturn mobySysInfo\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n)\n\nfunc TestParseBuildctlVersion(t *testing.T) {\n\ttestCases := map[string]*dockercompat.ComponentVersion{\n\t\t\"buildctl github.com/moby/buildkit v0.10.3 c8d25d9a103b70dc300a4fd55e7e576472284e31\": {\n\t\t\tName:    \"buildctl\",\n\t\t\tVersion: \"v0.10.3\",\n\t\t\tDetails: map[string]string{\n\t\t\t\t\"GitCommit\": \"c8d25d9a103b70dc300a4fd55e7e576472284e31\",\n\t\t\t},\n\t\t},\n\t\t\"buildctl github.com/moby/buildkit v0.10.0-380-g874eef9b 874eef9b70dbaf4f074d2bc8f4dc64237f8e83a0\": {\n\t\t\tName:    \"buildctl\",\n\t\t\tVersion: \"v0.10.0-380-g874eef9b\",\n\t\t\tDetails: map[string]string{\n\t\t\t\t\"GitCommit\": \"874eef9b70dbaf4f074d2bc8f4dc64237f8e83a0\",\n\t\t\t},\n\t\t},\n\t\t\"buildctl github.com/moby/buildkit 0.0.0+unknown\": {\n\t\t\tName:    \"buildctl\",\n\t\t\tVersion: \"0.0.0+unknown\",\n\t\t},\n\t\t\"foo bar baz\": nil,\n\t}\n\n\tfor s, expected := range testCases {\n\t\tgot, err := parseBuildctlVersion([]byte(s))\n\t\tif expected != nil {\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.DeepEqual(t, expected, got)\n\t\t} else {\n\t\t\tassert.Assert(t, err != nil)\n\t\t}\n\t}\n}\n\nfunc TestParseRuncVersion(t *testing.T) {\n\ttestCases := map[string]*dockercompat.ComponentVersion{\n\t\t`runc version 1.1.2\ncommit: v1.1.2-0-ga916309f\nspec: 1.0.2-dev\ngo: go1.18.3\nlibseccomp: 2.5.1`: {\n\t\t\tName:    \"runc\",\n\t\t\tVersion: \"1.1.2\",\n\t\t\tDetails: map[string]string{\n\t\t\t\t\"GitCommit\": \"v1.1.2-0-ga916309f\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor s, expected := range testCases {\n\t\tgot, err := parseRuncVersion([]byte(s))\n\t\tif expected != nil {\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.DeepEqual(t, expected, got)\n\t\t} else {\n\t\t\tassert.Assert(t, err != nil)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// UnameR returns `uname -r`\nfunc UnameR() string {\n\tvar utsname unix.Utsname\n\tif err := unix.Uname(&utsname); err != nil {\n\t\t// error is unlikely to happen\n\t\treturn \"\"\n\t}\n\tvar s string\n\tfor _, f := range utsname.Release {\n\t\tif f == 0 {\n\t\t\tbreak\n\t\t}\n\t\ts += string(f)\n\t}\n\treturn s\n}\n\n// UnameM returns `uname -m`\nfunc UnameM() string {\n\tvar utsname unix.Utsname\n\tif err := unix.Uname(&utsname); err != nil {\n\t\t// error is unlikely to happen\n\t\treturn \"\"\n\t}\n\tvar s string\n\tfor _, f := range utsname.Machine {\n\t\tif f == 0 {\n\t\t\tbreak\n\t\t}\n\t\ts += string(f)\n\t}\n\treturn s\n}\n\nfunc DistroName() string {\n\tf, err := os.Open(\"/etc/os-release\")\n\tif err != nil {\n\t\treturn UnameO\n\t}\n\tdefer f.Close()\n\treturn distroName(f)\n}\n\nfunc distroName(r io.Reader) string {\n\tscanner := bufio.NewScanner(r)\n\tvar name, version string\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tk, v := getOSReleaseAttrib(line)\n\t\tswitch k {\n\t\tcase \"PRETTY_NAME\":\n\t\t\treturn v\n\t\tcase \"NAME\":\n\t\t\tname = v\n\t\tcase \"VERSION\":\n\t\t\tversion = v\n\t\t}\n\t}\n\tif name != \"\" {\n\t\tif version != \"\" {\n\t\t\treturn name + \" \" + version\n\t\t}\n\t\treturn name\n\t}\n\treturn UnameO\n}\n\nvar osReleaseAttribRegex = regexp.MustCompile(`([^\\s=]+)\\s*=\\s*(\"{0,1})([^\"]*)(\"{0,1})`)\n\nfunc getOSReleaseAttrib(line string) (string, string) {\n\tsplitBySlash := strings.SplitN(line, \"#\", 2)\n\tl := strings.TrimSpace(splitBySlash[0])\n\tx := osReleaseAttribRegex.FindAllStringSubmatch(l, -1)\n\tif len(x) >= 1 && len(x[0]) > 3 {\n\t\treturn x[0][1], x[0][3]\n\t}\n\treturn \"\", \"\"\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_unix_test.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestDistroNameAlpine(t *testing.T) {\n\tconst etcOSRelease = `NAME=\"Alpine Linux\"\nID=alpine\nVERSION_ID=3.13.2\nPRETTY_NAME=\"Alpine Linux v3.13\"\nHOME_URL=\"https://alpinelinux.org/\"\nBUG_REPORT_URL=\"https://bugs.alpinelinux.org/\"\n`\n\tr := strings.NewReader(etcOSRelease)\n\tassert.Equal(t, \"Alpine Linux v3.13\", distroName(r))\n}\n\nfunc TestDistroNameAlpineAltered(t *testing.T) {\n\tconst etcOSRelease = `NAME=\"Alpine Linux\"\nID=alpine\nVERSION_ID=3.13.2\nPRETTY_NAME=Alpine Linux v3.13 # some comment\nHOME_URL=\"https://alpinelinux.org/\"\nBUG_REPORT_URL=\"https://bugs.alpinelinux.org/\"\n`\n\tr := strings.NewReader(etcOSRelease)\n\tassert.Equal(t, \"Alpine Linux v3.13\", distroName(r))\n}\n\nfunc TestDistroNameUbuntu(t *testing.T) {\n\tconst etcOSRelease = `NAME=\"Ubuntu\"\nVERSION=\"20.10 (Groovy Gorilla)\"\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\"Ubuntu 20.10\"\nVERSION_ID=\"20.10\"\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\nPRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\nVERSION_CODENAME=groovy\nUBUNTU_CODENAME=groovy`\n\tr := strings.NewReader(etcOSRelease)\n\tassert.Equal(t, \"Ubuntu 20.10\", distroName(r))\n}\n\nfunc TestDistroNameCentOS(t *testing.T) {\n\tconst etcOSRelease = `NAME=\"CentOS Linux\"\nVERSION=\"7 (Core)\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"7\"\nPRETTY_NAME=\"CentOS Linux 7 (Core)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:centos:centos:7\"\nHOME_URL=\"https://www.centos.org/\"\nBUG_REPORT_URL=\"https://bugs.centos.org/\"\n\nCENTOS_MANTISBT_PROJECT=\"CentOS-7\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"7\"\nREDHAT_SUPPORT_PRODUCT=\"centos\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"7\"\n`\n\tr := strings.NewReader(etcOSRelease)\n\tassert.Equal(t, \"CentOS Linux 7 (Core)\", distroName(r))\n}\n\nfunc TestDistroNameEmpty(t *testing.T) {\n\tr := strings.NewReader(\"\")\n\tassert.Equal(t, UnameO, distroName(r))\n}\n\nfunc TestDistroNameNoPrettyName(t *testing.T) {\n\tconst etcOSRelease = `NAME=\"Foo\"\nVERSION = \"42.0\"\n`\n\tr := strings.NewReader(etcOSRelease)\n\tassert.Equal(t, \"Foo 42.0\", distroName(r))\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/docker/docker/pkg/meminfo\"\n\t\"github.com/docker/docker/pkg/sysinfo\"\n\t\"golang.org/x/sys/windows\"\n\t\"golang.org/x/sys/windows/registry\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n)\n\nconst UnameO = \"Microsoft Windows\"\n\n// MsiNTProductType is the product type of the operating system.\n// https://learn.microsoft.com/en-us/windows/win32/msi/msintproducttype\n// Ref: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa\nconst (\n\tverNTServer = 0x0000003\n)\n\ntype windowsInfoUtil interface {\n\tRtlGetVersion() *windows.OsVersionInfoEx\n\tGetRegistryStringValue(key registry.Key, path string, name string) (string, error)\n\tGetRegistryIntValue(key registry.Key, path string, name string) (int, error)\n}\n\ntype winInfoUtil struct{}\n\n// RtlGetVersion implements the RtlGetVersion method using the actual windows package\nfunc (sw *winInfoUtil) RtlGetVersion() *windows.OsVersionInfoEx {\n\treturn windows.RtlGetVersion()\n}\n\n// UnameR returns the Kernel version\nfunc UnameR() string {\n\tutil := &winInfoUtil{}\n\tversion, err := getKernelVersion(util)\n\tif err != nil {\n\t\tlog.L.Error(err.Error())\n\t}\n\n\treturn version\n}\n\n// UnameM returns the architecture of the system\nfunc UnameM() string {\n\tarch := runtime.GOARCH\n\n\tif strings.ToLower(arch) == \"amd64\" {\n\t\treturn \"x86_64\"\n\t}\n\n\t// \"386\": 32-bit Intel/AMD processors (x86 architecture)\n\tif strings.ToLower(arch) == \"386\" {\n\t\treturn \"x86\"\n\t}\n\n\t// arm, s390x, and so on\n\treturn arch\n}\n\n// DistroName returns version information about the currently running operating system\nfunc DistroName() string {\n\tutil := &winInfoUtil{}\n\tversion, err := distroName(util)\n\tif err != nil {\n\t\tlog.L.Error(err.Error())\n\t}\n\n\treturn version\n}\n\nfunc distroName(sw windowsInfoUtil) (string, error) {\n\t// Get the OS version information from the Windows registry\n\tregPath := `SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion`\n\n\t// Eg. 22631 (REG_SZ)\n\tcurrBuildNo, err := sw.GetRegistryStringValue(registry.LOCAL_MACHINE, regPath, \"CurrentBuildNumber\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get os version (build number) %v\", err)\n\t}\n\n\t// Eg. 23H2 (REG_SZ)\n\tdisplayVersion, err := sw.GetRegistryStringValue(registry.LOCAL_MACHINE, regPath, \"DisplayVersion\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get os version (display version) %v\", err)\n\t}\n\n\t// UBR: Update Build Revision. Eg. 3737 (REG_DWORD 32-bit Value)\n\tubr, err := sw.GetRegistryIntValue(registry.LOCAL_MACHINE, regPath, \"UBR\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get os version (ubr) %v\", err)\n\t}\n\n\tproductType := \"\"\n\tif isWindowsServer(sw) {\n\t\tproductType = \"Server\"\n\t}\n\n\t// Concatenate the reg.key values to get the OS version information\n\t// Example: \"Microsoft Windows Version 23H2 (OS Build 22631.3737)\"\n\tversionString := fmt.Sprintf(\"%s %s Version %s (OS Build %s.%d)\",\n\t\tUnameO,\n\t\tproductType,\n\t\tdisplayVersion,\n\t\tcurrBuildNo,\n\t\tubr,\n\t)\n\n\t// Replace double spaces with single spaces\n\tversionString = strings.ReplaceAll(versionString, \"  \", \" \")\n\n\treturn versionString, nil\n}\n\nfunc getKernelVersion(sw windowsInfoUtil) (string, error) {\n\t// Get BuildLabEx value from the Windows registry\n\t// [buiild number].[revision number].[architecture].[branch].[date]-[time]\n\t// Eg. \"BuildLabEx: 10240.16412.amd64fre.th1.150729-1800\"\n\tbuildLab, err := sw.GetRegistryStringValue(registry.LOCAL_MACHINE, `SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion`, \"BuildLabEx\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Get Version: Contains the major and minor version numbers of the operating system.\n\t// Eg. \"10.0\"\n\tosvi := sw.RtlGetVersion()\n\n\t// Concatenate the OS version and BuildLabEx values to get the Kernel version information\n\t// Example: \"10.0 22631 (10240.16412.amd64fre.th1.150729-1800)\"\n\tversion := fmt.Sprintf(\"%d.%d %d (%s)\", osvi.MajorVersion, osvi.MinorVersion, osvi.BuildNumber, buildLab)\n\treturn version, nil\n}\n\n// GetRegistryStringValue retrieves a string value from the Windows registry\nfunc (sw *winInfoUtil) GetRegistryStringValue(key registry.Key, path string, name string) (string, error) {\n\tk, err := registry.OpenKey(key, path, registry.QUERY_VALUE)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer k.Close()\n\n\tv, _, err := k.GetStringValue(name)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn v, nil\n}\n\n// GetRegistryIntValue retrieves an integer value from the Windows registry\nfunc (sw *winInfoUtil) GetRegistryIntValue(key registry.Key, path string, name string) (int, error) {\n\tk, err := registry.OpenKey(key, path, registry.QUERY_VALUE)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer k.Close()\n\n\tv, _, err := k.GetIntegerValue(name)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn int(v), nil\n}\n\nfunc isWindowsServer(sw windowsInfoUtil) bool {\n\tosvi := sw.RtlGetVersion()\n\treturn osvi.ProductType == verNTServer\n}\n\n// Cgroups not supported on Windows\nfunc CgroupsVersion() string {\n\treturn \"\"\n}\n\nfunc fulfillPlatformInfo(info *dockercompat.Info) {\n\tmobySysInfo := mobySysInfo(info)\n\n\t// NOTE: cgroup fields are not available on Windows\n\t// https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332\n\n\tinfo.IPv4Forwarding = !mobySysInfo.IPv4ForwardingDisabled\n\tif !info.IPv4Forwarding {\n\t\tinfo.Warnings = append(info.Warnings, \"WARNING: IPv4 forwarding is disabled\")\n\t}\n\t// FIXME: BridgeNFCallIP6TablesDisabled is deprecated in moby and always false now\n\t// Figure out what we want to do with this\n\tinfo.BridgeNfIptables = true // !mobySysInfo.BridgeNFCallIPTablesDisabled\n\tif !info.BridgeNfIptables {\n\t\tinfo.Warnings = append(info.Warnings, \"WARNING: bridge-nf-call-iptables is disabled\")\n\t}\n\t// FIXME: BridgeNFCallIP6TablesDisabled is deprecated in moby and always false now\n\t// Figure out what we want to do with this\n\tinfo.BridgeNfIP6tables = true // !mobySysInfo.BridgeNFCallIP6TablesDisabled\n\tif !info.BridgeNfIP6tables {\n\t\tinfo.Warnings = append(info.Warnings, \"WARNING: bridge-nf-call-ip6tables is disabled\")\n\t}\n\tinfo.NCPU = runtime.NumCPU()\n\tmemLimit, err := meminfo.Read()\n\tif err != nil {\n\t\tinfo.Warnings = append(info.Warnings, fmt.Sprintf(\"failed to read mem info: %v\", err))\n\t} else {\n\t\tinfo.MemTotal = memLimit.MemTotal\n\t}\n}\n\nfunc mobySysInfo(_ *dockercompat.Info) *sysinfo.SysInfo {\n\tvar sysinfo sysinfo.SysInfo\n\treturn &sysinfo\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutil_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutil\n\nimport (\n\t\"testing\"\n\n\t\"go.uber.org/mock/gomock\"\n\t\"golang.org/x/sys/windows\"\n\t\"golang.org/x/sys/windows/registry\"\n\t\"gotest.tools/v3/assert\"\n\n\tmocks \"github.com/containerd/nerdctl/v2/pkg/infoutil/infoutilmock\"\n)\n\nfunc setUpMocks(t *testing.T) *mocks.MockWindowsInfoUtil {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tmockInfoUtil := mocks.NewMockWindowsInfoUtil(ctrl)\n\n\t// Mock registry value: CurrentBuildNumber\n\tmockInfoUtil.\n\t\tEXPECT().\n\t\tGetRegistryStringValue(gomock.Any(), gomock.Any(), \"CurrentBuildNumber\").\n\t\tReturn(\"19041\", nil).\n\t\tAnyTimes()\n\n\t// Mock registry value: DisplayVersion\n\tmockInfoUtil.\n\t\tEXPECT().\n\t\tGetRegistryStringValue(gomock.Any(), gomock.Any(), \"DisplayVersion\").\n\t\tReturn(\"22H4\", nil).\n\t\tAnyTimes()\n\n\t// Mock registry value: UBR\n\tmockInfoUtil.\n\t\tEXPECT().\n\t\tGetRegistryIntValue(gomock.Any(), gomock.Any(), \"UBR\").\n\t\tReturn(558, nil).\n\t\tAnyTimes()\n\n\treturn mockInfoUtil\n}\n\nconst (\n\tverNTWorkStation      = 0x0000001\n\tverNTDomainController = 0x0000002\n)\n\nfunc TestDistroName(t *testing.T) {\n\tmockInfoUtil := setUpMocks(t)\n\n\tbaseVersion := windows.OsVersionInfoEx{\n\t\tMajorVersion: 10,\n\t\tMinorVersion: 0,\n\t\tBuildNumber:  19041,\n\t}\n\n\ttests := []struct {\n\t\tproductType byte\n\t\texpected    string\n\t}{\n\t\t{\n\t\t\tproductType: verNTWorkStation,\n\t\t\texpected:    \"Microsoft Windows Version 22H4 (OS Build 19041.558)\",\n\t\t},\n\t\t{\n\t\t\tproductType: verNTServer,\n\t\t\texpected:    \"Microsoft Windows Server Version 22H4 (OS Build 19041.558)\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\t// Mock sys/windows RtlGetVersion\n\t\tosvi := baseVersion\n\t\tosvi.ProductType = tt.productType\n\t\tmockInfoUtil.EXPECT().RtlGetVersion().Return(&osvi).Times(1)\n\n\t\tt.Run(tt.expected, func(t *testing.T) {\n\t\t\tactual, err := distroName(mockInfoUtil)\n\t\t\tassert.Equal(t, tt.expected, actual, \"distroName should return the name of the operating system\")\n\t\t\tassert.NilError(t, err)\n\t\t})\n\t}\n}\n\nfunc TestDistroNameError(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tmockInfoUtil := mocks.NewMockWindowsInfoUtil(ctrl)\n\n\tmockInfoUtil.EXPECT().RtlGetVersion().Return(nil).Times(0)\n\tmockInfoUtil.\n\t\tEXPECT().\n\t\tGetRegistryStringValue(gomock.Any(), gomock.Any(), gomock.Any()).\n\t\tReturn(\"19041\", registry.ErrNotExist).AnyTimes()\n\n\tactual, err := distroName(mockInfoUtil)\n\tassert.ErrorContains(t, err, registry.ErrNotExist.Error(), \"distroName should return an error on error\")\n\tassert.Equal(t, \"\", actual, \"distroname should return an empty string on error\")\n}\n\nfunc TestGetKernelVersion(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tmockInfoUtil := mocks.NewMockWindowsInfoUtil(ctrl)\n\n\t// Mock registry value: BuildLabEx\n\tmockInfoUtil.\n\t\tEXPECT().\n\t\tGetRegistryStringValue(gomock.Any(), gomock.Any(), \"BuildLabEx\").\n\t\tReturn(\"10240.16412.amd64fre.th1.150729-1800\", nil).\n\t\tTimes(1)\n\n\tbaseVersion := windows.OsVersionInfoEx{\n\t\tMajorVersion: 10,\n\t\tMinorVersion: 0,\n\t\tBuildNumber:  19041,\n\t}\n\n\texpected := \"10.0 19041 (10240.16412.amd64fre.th1.150729-1800)\"\n\n\t// Mock sys/windows RtlGetVersion\n\tosvi := baseVersion\n\tmockInfoUtil.EXPECT().RtlGetVersion().Return(&osvi).Times(1)\n\n\tactual, err := getKernelVersion(mockInfoUtil)\n\tassert.NilError(t, err)\n\tassert.Equal(t, expected, actual, \"getKernelVersion should return the kernel version\")\n}\n\nfunc TestGetKernelVersionError(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\tmockInfoUtil := mocks.NewMockWindowsInfoUtil(ctrl)\n\n\tmockInfoUtil.EXPECT().RtlGetVersion().Return(nil).Times(0)\n\tmockInfoUtil.\n\t\tEXPECT().\n\t\tGetRegistryStringValue(gomock.Any(), gomock.Any(), gomock.Any()).\n\t\tReturn(\"\", registry.ErrNotExist).Times(1)\n\n\tactual, err := getKernelVersion(mockInfoUtil)\n\tassert.ErrorContains(t, err, registry.ErrNotExist.Error(), \"getKernelVersion should return an error on error\")\n\tassert.Equal(t, \"\", actual, \"getKernelVersion should return an empty string on error\")\n}\n\nfunc TestIsWindowsServer(t *testing.T) {\n\tctrl := gomock.NewController(t)\n\tdefer ctrl.Finish()\n\n\ttests := []struct {\n\t\tproductType string\n\t\tosvi        windows.OsVersionInfoEx\n\t\texpected    bool\n\t}{\n\t\t{\n\t\t\tproductType: \"VER_NT_WORKSTATION\",\n\t\t\tosvi:        windows.OsVersionInfoEx{ProductType: verNTWorkStation},\n\t\t\texpected:    false,\n\t\t},\n\t\t{\n\t\t\tproductType: \"VER_NT_DOMAIN_CONTROLLER\",\n\t\t\tosvi:        windows.OsVersionInfoEx{ProductType: verNTDomainController},\n\t\t\texpected:    false,\n\t\t},\n\t\t{\n\t\t\tproductType: \"VER_NT_SERVER\",\n\t\t\tosvi:        windows.OsVersionInfoEx{ProductType: verNTServer},\n\t\t\texpected:    true,\n\t\t},\n\t}\n\n\tmockSysCall := mocks.NewMockWindowsInfoUtil(ctrl)\n\tfor _, tt := range tests {\n\t\tmockSysCall.EXPECT().RtlGetVersion().Return(&tt.osvi)\n\n\t\tt.Run(tt.productType, func(t *testing.T) {\n\t\t\tactual := isWindowsServer(mockSysCall)\n\t\t\tassert.Equal(t, tt.expected, actual, \"isWindowsServer should return true on Windows Server\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/infoutil/infoutilmock/infoutil_mock.go",
    "content": "//go:build windows\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage infoutilmock\n\nimport (\n\t\"reflect\"\n\n\t\"go.uber.org/mock/gomock\"\n\t\"golang.org/x/sys/windows\"\n\t\"golang.org/x/sys/windows/registry\"\n)\n\n// MockWindowsInfoUtil is a mock of windowsInfoUtil interface\ntype MockWindowsInfoUtil struct {\n\tctrl     *gomock.Controller\n\trecorder *MockWindowsInfoUtilMockRecorder\n}\n\n// MockWindowsInfoUtilMockRecorder is the mock recorder for MockWindowsInfoUtil\ntype MockWindowsInfoUtilMockRecorder struct {\n\tmock *MockWindowsInfoUtil\n}\n\n// NewMockWindowsInfoUtil creates a new mock instance\nfunc NewMockWindowsInfoUtil(ctrl *gomock.Controller) *MockWindowsInfoUtil {\n\tmock := &MockWindowsInfoUtil{ctrl: ctrl}\n\tmock.recorder = &MockWindowsInfoUtilMockRecorder{mock}\n\treturn mock\n}\n\n// EXPECT returns an object that allows the caller to indicate expected use\nfunc (m *MockWindowsInfoUtil) EXPECT() *MockWindowsInfoUtilMockRecorder {\n\treturn m.recorder\n}\n\n// Create mocks the RtlGetVersion method of windowsInfoUtil\nfunc (m *MockWindowsInfoUtil) RtlGetVersion() *windows.OsVersionInfoEx {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"RtlGetVersion\")\n\tret0, _ := ret[0].(*windows.OsVersionInfoEx)\n\treturn ret0\n}\n\n// Expected call of RtlGetVersion\nfunc (m *MockWindowsInfoUtilMockRecorder) RtlGetVersion() *gomock.Call {\n\tm.mock.ctrl.T.Helper()\n\treturn m.mock.ctrl.RecordCallWithMethodType(\n\t\tm.mock,\n\t\t\"RtlGetVersion\",\n\t\treflect.TypeOf((*MockWindowsInfoUtil)(nil).RtlGetVersion),\n\t)\n}\n\n// Create mocks the GetRegistryStringValue method of windowsInfoUtil\nfunc (m *MockWindowsInfoUtil) GetRegistryStringValue(key registry.Key, path string, name string) (string, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetRegistryStringValue\", key, path, name)\n\tret0, _ := ret[0].(string)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Expected call of GetRegistryStringValue\nfunc (m *MockWindowsInfoUtilMockRecorder) GetRegistryStringValue(key any, path any, name any) *gomock.Call {\n\tm.mock.ctrl.T.Helper()\n\treturn m.mock.ctrl.RecordCallWithMethodType(\n\t\tm.mock,\n\t\t\"GetRegistryStringValue\",\n\t\treflect.TypeOf((*MockWindowsInfoUtil)(nil).GetRegistryStringValue),\n\t\tkey, path, name,\n\t)\n}\n\n// Create mocks the GetRegistryIntValue method of windowsInfoUtil\nfunc (m *MockWindowsInfoUtil) GetRegistryIntValue(key registry.Key, path string, name string) (int, error) {\n\tm.ctrl.T.Helper()\n\tret := m.ctrl.Call(m, \"GetRegistryIntValue\", key, path, name)\n\tret0, _ := ret[0].(int)\n\tret1, _ := ret[1].(error)\n\treturn ret0, ret1\n}\n\n// Expected call of GetRegistryIntValue\nfunc (m *MockWindowsInfoUtilMockRecorder) GetRegistryIntValue(key any, path any, name any) *gomock.Call {\n\tm.mock.ctrl.T.Helper()\n\treturn m.mock.ctrl.RecordCallWithMethodType(\n\t\tm.mock,\n\t\t\"GetRegistryIntValue\",\n\t\treflect.TypeOf((*MockWindowsInfoUtil)(nil).GetRegistryIntValue),\n\t\tkey, path, name,\n\t)\n}\n"
  },
  {
    "path": "pkg/inspecttypes/dockercompat/blkio.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/moby/blob/v20.10.1/api/types/blkiodev/blkio.go\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.1/NOTICE\n*/\n\npackage dockercompat\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n)\n\ntype BlkioSettings struct {\n\tBlkioWeight          uint16 // Block IO weight (relative weight vs. other containers)\n\tBlkioWeightDevice    []*WeightDevice\n\tBlkioDeviceReadBps   []*ThrottleDevice\n\tBlkioDeviceWriteBps  []*ThrottleDevice\n\tBlkioDeviceReadIOps  []*ThrottleDevice\n\tBlkioDeviceWriteIOps []*ThrottleDevice\n}\n\n// From https://github.com/moby/moby/blob/v20.10.1/api/types/blkiodev/blkio.go\n// WeightDevice is a structure that holds device:weight pair\ntype WeightDevice struct {\n\tPath   string\n\tWeight uint16\n}\n\nfunc (w *WeightDevice) String() string {\n\treturn fmt.Sprintf(\"%s:%d\", w.Path, w.Weight)\n}\n\n// ThrottleDevice is a structure that holds device:rate_per_second pair\ntype ThrottleDevice struct {\n\tPath string\n\tRate uint64\n}\n\nfunc (t *ThrottleDevice) String() string {\n\treturn fmt.Sprintf(\"%s:%d\", t.Path, t.Rate)\n}\n\nfunc getBlkioSettingsFromSpec(spec *specs.Spec, hostConfig *HostConfig) error {\n\tif spec == nil {\n\t\treturn fmt.Errorf(\"spec cannot be nil\")\n\t}\n\tif hostConfig == nil {\n\t\treturn fmt.Errorf(\"hostConfig cannot be nil\")\n\t}\n\n\t// Initialize empty arrays by default\n\thostConfig.BlkioSettings = getDefaultBlkioSettings()\n\n\tif spec.Linux == nil || spec.Linux.Resources == nil || spec.Linux.Resources.BlockIO == nil {\n\t\treturn nil\n\t}\n\n\tblockIO := spec.Linux.Resources.BlockIO\n\n\t// Set block IO weight\n\tif blockIO.Weight != nil {\n\t\thostConfig.BlkioWeight = *blockIO.Weight\n\t}\n\n\t// Set weight devices\n\tif len(blockIO.WeightDevice) > 0 {\n\t\thostConfig.BlkioWeightDevice = make([]*WeightDevice, len(blockIO.WeightDevice))\n\t\tdockerCompatWeightDevices, err := toDockerCompatWeightDevices(blockIO.WeightDevice)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert weight devices to dockercompat format: %w\", err)\n\t\t}\n\t\tfor i, dev := range dockerCompatWeightDevices {\n\t\t\thostConfig.BlkioWeightDevice[i] = &dev\n\t\t}\n\t}\n\n\t// Set throttle devices for read BPS\n\tif len(blockIO.ThrottleReadBpsDevice) > 0 {\n\t\thostConfig.BlkioDeviceReadBps = make([]*ThrottleDevice, len(blockIO.ThrottleReadBpsDevice))\n\t\tdockerCompatThrottleDevices, err := toDockerCompatThrottleDevices(blockIO.ThrottleReadBpsDevice)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert throttle devices to dockercompat format: %w\", err)\n\t\t}\n\t\tfor i, dev := range dockerCompatThrottleDevices {\n\t\t\thostConfig.BlkioDeviceReadBps[i] = &dev\n\t\t}\n\t}\n\n\t// Set throttle devices for write BPS\n\tif len(blockIO.ThrottleWriteBpsDevice) > 0 {\n\t\thostConfig.BlkioDeviceWriteBps = make([]*ThrottleDevice, len(blockIO.ThrottleWriteBpsDevice))\n\t\tdockerCompatThrottleDevices, err := toDockerCompatThrottleDevices(blockIO.ThrottleWriteBpsDevice)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert throttle devices to dockercompat format: %w\", err)\n\t\t}\n\t\tfor i, dev := range dockerCompatThrottleDevices {\n\t\t\thostConfig.BlkioDeviceWriteBps[i] = &dev\n\t\t}\n\t}\n\n\t// Set throttle devices for read IOPs\n\tif len(blockIO.ThrottleReadIOPSDevice) > 0 {\n\t\thostConfig.BlkioDeviceReadIOps = make([]*ThrottleDevice, len(blockIO.ThrottleReadIOPSDevice))\n\t\tdockerCompatThrottleDevices, err := toDockerCompatThrottleDevices(blockIO.ThrottleReadIOPSDevice)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert throttle devices to dockercompat format: %w\", err)\n\t\t}\n\t\tfor i, dev := range dockerCompatThrottleDevices {\n\t\t\thostConfig.BlkioDeviceReadIOps[i] = &dev\n\t\t}\n\t}\n\n\t// Set throttle devices for write IOPs\n\tif len(blockIO.ThrottleWriteIOPSDevice) > 0 {\n\t\thostConfig.BlkioDeviceWriteIOps = make([]*ThrottleDevice, len(blockIO.ThrottleWriteIOPSDevice))\n\t\tdockerCompatThrottleDevices, err := toDockerCompatThrottleDevices(blockIO.ThrottleWriteIOPSDevice)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert throttle devices to dockercompat format: %w\", err)\n\t\t}\n\t\tfor i, dev := range dockerCompatThrottleDevices {\n\t\t\thostConfig.BlkioDeviceWriteIOps[i] = &dev\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getDefaultBlkioSettings() BlkioSettings {\n\treturn BlkioSettings{\n\t\tBlkioWeight:          0,\n\t\tBlkioWeightDevice:    make([]*WeightDevice, 0),\n\t\tBlkioDeviceReadBps:   make([]*ThrottleDevice, 0),\n\t\tBlkioDeviceWriteBps:  make([]*ThrottleDevice, 0),\n\t\tBlkioDeviceReadIOps:  make([]*ThrottleDevice, 0),\n\t\tBlkioDeviceWriteIOps: make([]*ThrottleDevice, 0),\n\t}\n}\n"
  },
  {
    "path": "pkg/inspecttypes/dockercompat/blkioutils_linux.go",
    "content": "//go:build linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockercompat\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc toDockerCompatWeightDevices(weightDevices []specs.LinuxWeightDevice) ([]WeightDevice, error) {\n\tmajorMinorToPathMap, err := getDeviceMajorMinorToPathMap()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query device paths from major/minor numbers: %w\", err)\n\t}\n\n\tdevices := []WeightDevice{}\n\tfor _, weightDevice := range weightDevices {\n\t\tkey := fmt.Sprintf(\"%d:%d\", weightDevice.Major, weightDevice.Minor)\n\t\tif _, ok := majorMinorToPathMap[key]; ok {\n\t\t\tdevices = append(devices, WeightDevice{\n\t\t\t\tPath:   majorMinorToPathMap[key],\n\t\t\t\tWeight: *weightDevice.Weight,\n\t\t\t})\n\t\t}\n\t}\n\treturn devices, nil\n}\n\nfunc toDockerCompatThrottleDevices(throttleDevices []specs.LinuxThrottleDevice) ([]ThrottleDevice, error) {\n\tmajorMinorToPathMap, err := getDeviceMajorMinorToPathMap()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query device paths from major/minor numbers: %w\", err)\n\t}\n\n\tdevices := []ThrottleDevice{}\n\tfor _, throttleDevice := range throttleDevices {\n\t\tkey := fmt.Sprintf(\"%d:%d\", throttleDevice.Major, throttleDevice.Minor)\n\t\tif _, ok := majorMinorToPathMap[key]; ok {\n\t\t\tdevices = append(devices, ThrottleDevice{\n\t\t\t\tPath: majorMinorToPathMap[key],\n\t\t\t\tRate: throttleDevice.Rate,\n\t\t\t})\n\t\t}\n\t}\n\treturn devices, nil\n}\n\nfunc getDeviceMajorMinorToPathMap() (map[string]string, error) {\n\tdevDir := \"/dev\"\n\tentries, err := os.ReadDir(devDir)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read %s: %w\", devDir, err)\n\t}\n\n\tmajorMinorToPathMap := make(map[string]string)\n\tfor _, ent := range entries {\n\t\tif ent.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tdevicePath := fmt.Sprintf(\"%s/%s\", devDir, ent.Name())\n\t\tosStat, err := os.Stat(devicePath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", devicePath, err)\n\t\t}\n\t\t// skip char devices\n\t\tif osStat.Mode()&os.ModeCharDevice != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tvar unixStat unix.Stat_t\n\t\tif err := unix.Stat(devicePath, &unixStat); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", devicePath, err)\n\t\t}\n\t\tmajor := int64(unix.Major(uint64(unixStat.Rdev))) //nolint: unconvert\n\t\tminor := int64(unix.Minor(uint64(unixStat.Rdev))) //nolint: unconvert\n\t\tkey := fmt.Sprintf(\"%d:%d\", major, minor)\n\t\tmajorMinorToPathMap[key] = devicePath\n\t}\n\treturn majorMinorToPathMap, nil\n}\n"
  },
  {
    "path": "pkg/inspecttypes/dockercompat/blkioutils_others.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockercompat\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n)\n\nfunc toDockerCompatWeightDevices(weightDevices []specs.LinuxWeightDevice) ([]WeightDevice, error) {\n\treturn nil, fmt.Errorf(\"block device weight controls are not supported on this platform\")\n}\n\nfunc toDockerCompatThrottleDevices(throttleDevices []specs.LinuxThrottleDevice) ([]ThrottleDevice, error) {\n\treturn nil, fmt.Errorf(\"block device throttling is not supported on this platform\")\n}\n"
  },
  {
    "path": "pkg/inspecttypes/dockercompat/dockercompat.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/moby/blob/v20.10.1/api/types/types.go\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.1/NOTICE\n*/\n\n// Package dockercompat mimics `docker inspect` objects.\npackage dockercompat\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/docker/go-connections/nat\"\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/runtime/restart\"\n\t\"github.com/containerd/go-cni\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ipcutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ocihook/state\"\n)\n\n// From https://github.com/moby/moby/blob/v26.1.2/api/types/types.go#L34-L140\ntype Image struct {\n\tID            string `json:\"Id\"`\n\tRepoTags      []string\n\tRepoDigests   []string\n\tParent        string\n\tComment       string\n\tCreated       string\n\tDockerVersion string\n\tAuthor        string\n\tConfig        *Config\n\tArchitecture  string\n\tVariant       string `json:\",omitempty\"`\n\tOs            string\n\n\t// TODO: OsVersion     string `json:\",omitempty\"`\n\n\tSize        int64 // Size is the unpacked size of the image\n\tVirtualSize int64 `json:\"VirtualSize,omitempty\"` // Deprecated\n\n\t// TODO: GraphDriver\tGraphDriverData\n\n\tRootFS   RootFS\n\tMetadata ImageMetadata\n\n\t// Deprecated: TODO: Container   string\n\t// Deprecated: TODO: ContainerConfig *container.Config\n}\n\n// From: https://github.com/moby/moby/blob/v26.1.2/api/types/graph_driver_data.go\ntype GraphDriverData struct {\n\tData map[string]string `json:\"Data\"`\n\tName string            `json:\"Name\"`\n}\n\ntype RootFS struct {\n\tType      string\n\tLayers    []string `json:\",omitempty\"`\n\tBaseLayer string   `json:\",omitempty\"`\n}\n\ntype ImageMetadata struct {\n\tLastTagTime time.Time `json:\",omitempty\"`\n}\n\ntype loggerLogConfig struct {\n\tDriver  string            `json:\"driver\"`\n\tOpts    map[string]string `json:\"opts,omitempty\"`\n\tLogURI  string            `json:\"-\"`\n\tAddress string            `json:\"address\"`\n}\n\n// Container mimics a `docker container inspect` object.\n// From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L340-L374\ntype Container struct {\n\tID             string `json:\"Id\"`\n\tCreated        string\n\tPath           string\n\tArgs           []string\n\tState          *ContainerState\n\tImage          string\n\tResolvConfPath string\n\tHostnamePath   string\n\tHostsPath      string\n\tLogPath        string\n\t// Unimplemented: Node            *ContainerNode `json:\",omitempty\"` // Node is only propagated by Docker Swarm standalone API\n\tName         string\n\tRestartCount int\n\tDriver       string\n\tPlatform     string\n\t// TODO: MountLabel      string\n\t// TODO: ProcessLabel    string\n\tAppArmorProfile string\n\t// TODO: ExecIDs         []string\n\tHostConfig *HostConfig\n\t// TODO: GraphDriver     GraphDriverData\n\tSizeRw     *int64 `json:\",omitempty\"`\n\tSizeRootFs *int64 `json:\",omitempty\"`\n\n\tMounts          []MountPoint\n\tConfig          *Config\n\tNetworkSettings *NetworkSettings\n}\n\n// From https://github.com/moby/moby/blob/8dbd90ec00daa26dc45d7da2431c965dec99e8b4/api/types/container/host_config.go#L391\n// HostConfig the non-portable Config structure of a container.\ntype HostConfig struct {\n\t// Binds           []string      // List of volume bindings for this container\n\tContainerIDFile string          // File (path) where the containerId is written\n\tLogConfig       loggerLogConfig // Configuration of the logs for this container\n\t// NetworkMode     NetworkMode   // Network mode to use for the container\n\tPortBindings nat.PortMap // Port mapping between the exposed port (container) and the host\n\t// RestartPolicy   RestartPolicy // Restart policy to be used for the container\n\t// AutoRemove      bool          // Automatically remove container when it exits\n\t// VolumeDriver    string        // Name of the volume driver used to mount volumes\n\t// VolumesFrom     []string      // List of volumes to take from other container\n\t// CapAdd          strslice.StrSlice // List of kernel capabilities to add to the container\n\t// CapDrop         strslice.StrSlice // List of kernel capabilities to remove from the container\n\n\tCgroupnsMode string   // Cgroup namespace mode to use for the container\n\tDNS          []string `json:\"Dns\"`        // List of DNS server to lookup\n\tDNSOptions   []string `json:\"DnsOptions\"` // List of DNSOption to look for\n\tDNSSearch    []string `json:\"DnsSearch\"`  // List of DNSSearch to look for\n\tExtraHosts   []string // List of extra hosts\n\tGroupAdd     []string // GroupAdd specifies additional groups to join\n\tIpcMode      string   `json:\"IpcMode\"` // IPC namespace to use for the container\n\t// Cgroup          CgroupSpec        // Cgroup to use for the container\n\tOomScoreAdj int    // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)\n\tPidMode     string // PID namespace to use for the container\n\t// Privileged      bool              // Is the container in privileged mode\n\t// PublishAllPorts bool              // Should docker publish all exposed port for the container\n\tReadonlyRootfs bool // Is the container root filesystem in read-only\n\t// SecurityOpt     []string          // List of string values to customize labels for MLS systems, such as SELinux.\n\tTmpfs   map[string]string `json:\"Tmpfs,omitempty\"` // List of tmpfs (mounts) used for the container\n\tUTSMode string            // UTS namespace to use for the container\n\t// UsernsMode      UsernsMode        // The user namespace to use for the container\n\tShmSize            int64             // Size of /dev/shm in bytes. The size must be greater than 0.\n\tSysctls            map[string]string // List of Namespaced sysctls used for the container\n\tRuntime            string            // Runtime to use with this container\n\tCPUSetMems         string            `json:\"CpusetMems\"`         // CpusetMems 0-2, 0,1\n\tCPUSetCPUs         string            `json:\"CpusetCpus\"`         // CpusetCpus 0-2, 0,1\n\tCPUQuota           int64             `json:\"CpuQuota\"`           // CPU CFS (Completely Fair Scheduler) quota\n\tCPUShares          uint64            `json:\"CpuShares\"`          // CPU shares (relative weight vs. other containers)\n\tCPUPeriod          uint64            `json:\"CpuPeriod\"`          // Limits the CPU CFS (Completely Fair Scheduler) period\n\tCPURealtimePeriod  uint64            `json:\"CpuRealtimePeriod\"`  // Limits the CPU real-time period in microseconds\n\tCPURealtimeRuntime int64             `json:\"CpuRealtimeRuntime\"` // Limits the CPU real-time runtime in microseconds\n\tMemory             int64             // Memory limit (in bytes)\n\tMemorySwap         int64             // Total memory usage (memory + swap); set `-1` to enable unlimited swap\n\tOomKillDisable     bool              // specifies whether to disable OOM Killer\n\tDevices            []DeviceMapping   // List of devices to map inside the container\n\tBlkioSettings\n}\n\n// From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L416-L427\n// MountPoint represents a mount point configuration inside the container.\n// This is used for reporting the mountpoints in use by a container.\ntype MountPoint struct {\n\tType        string `json:\",omitempty\"`\n\tName        string `json:\",omitempty\"`\n\tSource      string\n\tDestination string\n\tDriver      string `json:\",omitempty\"`\n\tMode        string\n\tRW          bool\n\tPropagation string\n}\n\n// config is from https://github.com/moby/moby/blob/8dbd90ec00daa26dc45d7da2431c965dec99e8b4/api/types/container/config.go#L37-L69\ntype Config struct {\n\tHostname    string `json:\",omitempty\"` // Hostname\n\tDomainname  string `json:\",omitempty\"` // Domainname\n\tUser        string `json:\",omitempty\"` // User that will run the command(s) inside the container, also support user:group\n\tAttachStdin bool   // Attach the standard input, makes possible user interaction\n\t// TODO: AttachStdout bool        // Attach the standard output\n\t// TODO: AttachStderr bool        // Attach the standard error\n\tExposedPorts nat.PortSet `json:\",omitempty\"` // List of exposed ports\n\t// TODO: Tty          bool        // Attach standard streams to a tty, including stdin if it is not closed.\n\t// TODO: OpenStdin    bool        // Open stdin\n\t// TODO: StdinOnce    bool        // If true, close stdin after the 1 attached client disconnects.\n\tEnv         []string                 `json:\",omitempty\"` // List of environment variable to set in the container\n\tCmd         []string                 `json:\",omitempty\"` // Command to run when starting the container\n\tHealthcheck *healthcheck.Healthcheck `json:\",omitempty\"` // Healthcheck describes how to check the container is healthy\n\t// TODO: ArgsEscaped     bool                `json:\",omitempty\"` // True if command is already escaped (meaning treat as a command line) (Windows specific).\n\tImage      string              `json:\",omitempty\"` // Name of the image as it was passed by the operator (e.g. could be symbolic)\n\tVolumes    map[string]struct{} `json:\",omitempty\"` // List of volumes (mounts) used for the container\n\tWorkingDir string              `json:\",omitempty\"` // Current directory (PWD) in the command will be launched\n\tEntrypoint []string            `json:\",omitempty\"` // Entrypoint to run when starting the container\n\t// TODO: NetworkDisabled bool                `json:\",omitempty\"` // Is network disabled\n\t// TODO: MacAddress      string              `json:\",omitempty\"` // Mac Address of the container\n\t// TODO: OnBuild         []string            // ONBUILD metadata that were defined on the image Dockerfile\n\tLabels map[string]string `json:\",omitempty\"` // List of labels set to this container\n\t// TODO: StopSignal      string              `json:\",omitempty\"` // Signal to stop a container\n\t// TODO: StopTimeout     *int                `json:\",omitempty\"` // Timeout (in seconds) to stop a container\n\t// TODO: Shell           []string            `json:\",omitempty\"` // Shell for shell-form of RUN, CMD, ENTRYPOINT\n}\n\n// ContainerState is from https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L313-L326\ntype ContainerState struct {\n\tStatus     string // String representation of the container state. Can be one of \"created\", \"running\", \"paused\", \"restarting\", \"removing\", \"exited\", or \"dead\"\n\tRunning    bool\n\tPaused     bool\n\tRestarting bool\n\t// TODO: OOMKilled  bool\n\t// TODO:\tDead       bool\n\tPid        int\n\tExitCode   int\n\tError      string\n\tStartedAt  string\n\tFinishedAt string\n\tHealth     *healthcheck.Health `json:\",omitempty\"`\n}\n\ntype NetworkSettings struct {\n\tPorts *nat.PortMap\n\tDefaultNetworkSettings\n\tNetworks map[string]*NetworkEndpointSettings\n}\n\ntype DNSSettings struct {\n\tDNSServers           []string\n\tDNSResolvConfOptions []string\n\tDNSSearchDomains     []string\n}\n\ntype HostConfigLabel struct {\n\tBlkioWeight uint16\n\tCidFile     string\n\tDevices     []DeviceMapping\n}\n\ntype DeviceMapping struct {\n\tPathOnHost        string\n\tPathInContainer   string\n\tCgroupPermissions string\n}\n\ntype CPUSettings struct {\n\tCPUSetCpus         string\n\tCPUSetMems         string\n\tCPUShares          uint64\n\tCPUQuota           int64\n\tCPUPeriod          uint64\n\tCPURealtimePeriod  uint64\n\tCPURealtimeRuntime int64\n}\n\n// DefaultNetworkSettings is from https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L405-L414\ntype DefaultNetworkSettings struct {\n\t// TODO EndpointID          string // EndpointID uniquely represents a service endpoint in a Sandbox\n\t// TODO Gateway             string // Gateway holds the gateway address for the network\n\tGlobalIPv6Address   string // GlobalIPv6Address holds network's global IPv6 address\n\tGlobalIPv6PrefixLen int    // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address\n\tIPAddress           string // IPAddress holds the IPv4 address for the network\n\tIPPrefixLen         int    // IPPrefixLen represents mask length of network's IPv4 address\n\t// TODO IPv6Gateway         string // IPv6Gateway holds gateway address specific for IPv6\n\tMacAddress string // MacAddress holds the MAC address for the network\n}\n\n// NetworkEndpointSettings is from https://github.com/moby/moby/blob/v20.10.1/api/types/network/network.go#L49-L65\ntype NetworkEndpointSettings struct {\n\t// Configurations\n\t// TODO IPAMConfig *EndpointIPAMConfig\n\t// TODO Links      []string\n\t// TODO Aliases    []string\n\t// Operational data\n\t// TODO NetworkID           string\n\t// TODO EndpointID          string\n\t// TODO Gateway             string\n\tIPAddress   string\n\tIPPrefixLen int\n\t// TODO IPv6Gateway         string\n\tGlobalIPv6Address   string\n\tGlobalIPv6PrefixLen int\n\tMacAddress          string\n\t// TODO DriverOpts          map[string]string\n}\n\n// ContainerFromNative instantiates a Docker-compatible Container from containerd-native Container.\nfunc ContainerFromNative(n *native.Container) (*Container, error) {\n\tvar hostname string\n\tc := &Container{\n\t\tID:      n.ID,\n\t\tCreated: n.CreatedAt.Format(time.RFC3339Nano),\n\t\tImage:   n.Image,\n\t\tName:    n.Labels[labels.Name],\n\t\tDriver:  n.Snapshotter,\n\t\t// XXX is this always right? what if the container OS is NOT the same as the host OS?\n\t\tPlatform: runtime.GOOS, // for Docker compatibility, this Platform string does NOT contain arch like \"/amd64\"\n\t}\n\tc.HostConfig = new(HostConfig)\n\tif n.Labels[restart.StatusLabel] == string(containerd.Running) {\n\t\tc.RestartCount, _ = strconv.Atoi(n.Labels[restart.CountLabel])\n\t}\n\tcontainerAnnotations := make(map[string]string)\n\tif sp, ok := n.Spec.(*specs.Spec); ok {\n\t\tcontainerAnnotations = sp.Annotations\n\t\tif p := sp.Process; p != nil {\n\t\t\tif len(p.Args) > 0 {\n\t\t\t\tc.Path = p.Args[0]\n\t\t\t\tif len(p.Args) > 1 {\n\t\t\t\t\tc.Args = p.Args[1:]\n\t\t\t\t}\n\t\t\t}\n\t\t\tc.AppArmorProfile = p.ApparmorProfile\n\t\t}\n\t\tc.Mounts = mountsFromNative(sp.Mounts)\n\t\tfor _, mount := range c.Mounts {\n\t\t\tif mount.Destination == \"/etc/resolv.conf\" {\n\t\t\t\tc.ResolvConfPath = mount.Source\n\t\t\t} else if mount.Destination == \"/etc/hostname\" {\n\t\t\t\tc.HostnamePath = mount.Source\n\t\t\t} else if mount.Destination == \"/etc/hosts\" {\n\t\t\t\tc.HostsPath = mount.Source\n\t\t\t}\n\t\t}\n\t\thostname = sp.Hostname\n\t}\n\tif nerdctlStateDir := n.Labels[labels.StateDir]; nerdctlStateDir != \"\" {\n\t\tresolvConfPath := filepath.Join(nerdctlStateDir, \"resolv.conf\")\n\t\tif _, err := os.Stat(resolvConfPath); err == nil {\n\t\t\tc.ResolvConfPath = resolvConfPath\n\t\t}\n\t\thostnamePath := filepath.Join(nerdctlStateDir, \"hostname\")\n\t\tif _, err := os.Stat(hostnamePath); err == nil {\n\t\t\tc.HostnamePath = hostnamePath\n\t\t}\n\t\tc.LogPath = filepath.Join(nerdctlStateDir, n.ID+\"-json.log\")\n\t\tif _, err := os.Stat(c.LogPath); err != nil {\n\t\t\tc.LogPath = \"\"\n\t\t}\n\t\thostsPath := filepath.Join(nerdctlStateDir, \"hosts\")\n\t\tif _, err := os.Stat(hostsPath); err == nil {\n\t\t\tc.HostsPath = hostsPath\n\t\t}\n\t}\n\n\tc.HostConfig.Tmpfs = make(map[string]string)\n\tif nerdctlMounts := n.Labels[labels.Mounts]; nerdctlMounts != \"\" {\n\t\tmounts, err := parseMounts(nerdctlMounts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.Mounts = mounts\n\t\tfor _, mount := range mounts {\n\t\t\tif mount.Type == \"tmpfs\" {\n\t\t\t\tc.HostConfig.Tmpfs[mount.Destination] = mount.Mode\n\t\t\t}\n\t\t}\n\t}\n\n\tif nedctlExtraHosts := n.Labels[labels.ExtraHosts]; nedctlExtraHosts != \"\" {\n\t\tc.HostConfig.ExtraHosts = parseExtraHosts(nedctlExtraHosts)\n\t}\n\n\tif nerdctlLoguri := n.Labels[labels.LogURI]; nerdctlLoguri != \"\" {\n\t\tc.HostConfig.LogConfig.LogURI = nerdctlLoguri\n\t}\n\tif logConfigJSON, ok := n.Labels[labels.LogConfig]; ok {\n\t\tvar logConfig loggerLogConfig\n\t\terr := json.Unmarshal([]byte(logConfigJSON), &logConfig)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to unmarshal log config: %v\", err)\n\t\t}\n\n\t\t// Assign the parsed LogConfig to c.HostConfig.LogConfig\n\t\tc.HostConfig.LogConfig = logConfig\n\t} else {\n\t\t// If LogConfig label is not present, set default values\n\t\tc.HostConfig.LogConfig = loggerLogConfig{\n\t\t\tDriver: \"json-file\",\n\t\t\tOpts:   make(map[string]string),\n\t\t}\n\t}\n\n\thostConfigLabel, err := getHostConfigLabelFromNative(n.Labels)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch HostConfigLabel: %v\", err)\n\t}\n\n\tc.HostConfig.BlkioWeight = hostConfigLabel.BlkioWeight\n\tc.HostConfig.ContainerIDFile = hostConfigLabel.CidFile\n\n\tgroupAdd, err := groupAddFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to groupAdd from native spec: %v\", err)\n\t}\n\n\tc.HostConfig.GroupAdd = groupAdd\n\tc.HostConfig.ShmSize = 0\n\n\tif ipcMode := n.Labels[labels.IPC]; ipcMode != \"\" {\n\t\tipc, err := ipcutil.DecodeIPCLabel(ipcMode)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to Decode IPC Label: %v\", err)\n\t\t}\n\t\tc.HostConfig.IpcMode = string(ipc.Mode)\n\t\tif ipc.ShmSize != \"\" {\n\t\t\tshmSize, err := units.RAMInBytes(ipc.ShmSize)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to parse ShmSize: %v\", err)\n\t\t\t}\n\t\t\tc.HostConfig.ShmSize = shmSize\n\t\t}\n\t}\n\n\tcs := new(ContainerState)\n\tcs.Restarting = n.Labels[restart.StatusLabel] == string(containerd.Running)\n\tcs.Error = n.Labels[labels.Error]\n\tif n.Process != nil {\n\t\tcs.Status = statusFromNative(n.Process.Status, n.Labels)\n\t\tcs.Running = n.Process.Status.Status == containerd.Running\n\t\tcs.Paused = n.Process.Status.Status == containerd.Paused\n\t\tcs.Pid = n.Process.Pid\n\t\tcs.ExitCode = int(n.Process.Status.ExitStatus)\n\t\tif containerAnnotations[labels.StateDir] != \"\" {\n\t\t\tif lf, err := state.New(containerAnnotations[labels.StateDir]); err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed retrieving state\")\n\t\t\t} else if err = lf.Load(); err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed retrieving StartedAt from state\")\n\t\t\t} else if !time.Time.IsZero(lf.StartedAt) {\n\t\t\t\tcs.StartedAt = lf.StartedAt.UTC().Format(time.RFC3339Nano)\n\t\t\t}\n\t\t}\n\t\tif !n.Process.Status.ExitTime.IsZero() {\n\t\t\tcs.FinishedAt = n.Process.Status.ExitTime.Format(time.RFC3339Nano)\n\t\t}\n\t\tnSettings, err := networkSettingsFromNative(n.Process.NetNS, n.Spec.(*specs.Spec))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.NetworkSettings = nSettings\n\t\tc.HostConfig.PortBindings = *nSettings.Ports\n\t} else {\n\t\t// n.process is not set if the container is not started, making the networkSetting null\n\t\t// we should send an empty object even in this case inorder for it to be compatible with docker inspect response\n\t\tnSettings, err := networkSettingsFromNative(nil, n.Spec.(*specs.Spec))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.NetworkSettings = nSettings\n\t}\n\n\tcpuSetting, err := cpuSettingsFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to Decode cpuSetting: %v\", err)\n\t}\n\tc.HostConfig.CPUSetCPUs = cpuSetting.CPUSetCpus\n\tc.HostConfig.CPUSetMems = cpuSetting.CPUSetMems\n\tc.HostConfig.CPUQuota = cpuSetting.CPUQuota\n\tc.HostConfig.CPUShares = cpuSetting.CPUShares\n\tc.HostConfig.CPUPeriod = cpuSetting.CPUPeriod\n\tc.HostConfig.CPURealtimePeriod = cpuSetting.CPURealtimePeriod\n\tc.HostConfig.CPURealtimeRuntime = cpuSetting.CPURealtimeRuntime\n\n\tcgroupNamespace, err := getCgroupnsFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to Decode cgroupNamespace: %v\", err)\n\t}\n\tc.HostConfig.CgroupnsMode = cgroupNamespace\n\n\tmemorySettings, err := getMemorySettingsFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to Decode memory Settings: %v\", err)\n\t}\n\n\tc.HostConfig.OomKillDisable = memorySettings.DisableOOMKiller\n\tc.HostConfig.Memory = memorySettings.Limit\n\tc.HostConfig.MemorySwap = memorySettings.Swap\n\n\tdnsSettings, err := getDNSFromNative(n.Labels)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to Decode dns Settings: %v\", err)\n\t}\n\n\tc.HostConfig.DNS = dnsSettings.DNSServers\n\tc.HostConfig.DNSOptions = dnsSettings.DNSResolvConfOptions\n\tc.HostConfig.DNSSearch = dnsSettings.DNSSearchDomains\n\n\toomScoreAdj, err := getOomScoreAdjFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get OomScoreAdj value: %v\", err)\n\t}\n\tc.HostConfig.OomScoreAdj = oomScoreAdj\n\n\tc.HostConfig.ReadonlyRootfs = false\n\tif n.Spec.(*specs.Spec).Root != nil && n.Spec.(*specs.Spec).Root.Readonly {\n\t\tc.HostConfig.ReadonlyRootfs = n.Spec.(*specs.Spec).Root.Readonly\n\t}\n\n\tutsMode, err := getUtsModeFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get UtsMode value: %v\", err)\n\t}\n\tc.HostConfig.UTSMode = utsMode\n\n\tsysctls, err := getSysctlFromNative(n.Spec.(*specs.Spec))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get UtsMode value: %v\", err)\n\t}\n\tc.HostConfig.Sysctls = sysctls\n\n\tif n.Runtime.Name != \"\" {\n\t\tc.HostConfig.Runtime = n.Runtime.Name\n\t}\n\n\tc.State = cs\n\tc.Config = &Config{\n\t\tLabels: n.Labels,\n\t\tImage:  c.Image,\n\t}\n\tif n.Labels[labels.Hostname] != \"\" {\n\t\thostname = n.Labels[labels.Hostname]\n\t}\n\tc.Config.Hostname = hostname\n\n\tif n.Labels[labels.Domainname] != \"\" {\n\t\tc.Config.Domainname = n.Labels[labels.Domainname]\n\t}\n\n\tc.HostConfig.Devices = hostConfigLabel.Devices\n\n\tvar pidMode string\n\tif n.Labels[labels.PIDContainer] != \"\" {\n\t\tpidMode = n.Labels[labels.PIDContainer]\n\t}\n\tc.HostConfig.PidMode = pidMode\n\n\tif err := getBlkioSettingsFromSpec(n.Spec.(*specs.Spec), c.HostConfig); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get blkio settings: %w\", err)\n\t}\n\n\tif n.Spec != nil {\n\t\tif spec, ok := n.Spec.(*specs.Spec); ok && spec.Process != nil {\n\t\t\tc.Config.Env = spec.Process.Env\n\t\t}\n\t}\n\n\tif n.Labels[labels.User] != \"\" {\n\t\tc.Config.User = n.Labels[labels.User]\n\t}\n\n\t// Add health check config if present in labels\n\tif hConfig, ok := n.Labels[labels.HealthCheck]; ok && hConfig != \"\" {\n\t\thealthCheckConfig, err := healthcheck.HealthCheckFromJSON(hConfig)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse healthcheck label: %w\", err)\n\t\t}\n\t\tc.Config.Healthcheck = healthCheckConfig\n\t}\n\n\t// Add health status to container state.\n\tif healthState, ok := n.Labels[labels.HealthState]; ok && healthState != \"\" {\n\t\thealthStatus, err := healthcheck.ReadHealthStatusForInspect(n.Labels[labels.StateDir], n.Labels[labels.HealthState])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get health status for inspect: %w\", err)\n\t\t}\n\t\tif healthStatus != nil {\n\t\t\tc.State.Health = healthStatus\n\t\t}\n\t}\n\n\treturn c, nil\n}\n\nfunc ImageFromNative(nativeImage *native.Image) (*Image, error) {\n\timgOCI := nativeImage.ImageConfig\n\trepository, tag := imgutil.ParseRepoTag(nativeImage.Image.Name)\n\n\timage := &Image{\n\t\t// Docker ID (digest of platform-specific config), not containerd ID (digest of multi-platform index or manifest)\n\t\tID:           nativeImage.ImageConfigDesc.Digest.String(),\n\t\tParent:       nativeImage.Image.Labels[\"org.mobyproject.image.parent\"],\n\t\tArchitecture: imgOCI.Architecture,\n\t\tVariant:      imgOCI.Platform.Variant,\n\t\tOs:           imgOCI.OS,\n\t\tSize:         nativeImage.Size,\n\t\tVirtualSize:  nativeImage.Size,\n\t\tRepoTags:     []string{fmt.Sprintf(\"%s:%s\", repository, tag)},\n\t\tRepoDigests:  []string{fmt.Sprintf(\"%s@%s\", repository, nativeImage.Image.Target.Digest.String())},\n\t}\n\n\tif len(imgOCI.History) > 0 {\n\t\timage.Comment = imgOCI.History[len(imgOCI.History)-1].Comment\n\t\tif !imgOCI.History[len(imgOCI.History)-1].Created.IsZero() {\n\t\t\timage.Created = imgOCI.History[len(imgOCI.History)-1].Created.Format(time.RFC3339Nano)\n\t\t}\n\t\timage.Author = imgOCI.History[len(imgOCI.History)-1].Author\n\t}\n\n\timage.RootFS.Type = imgOCI.RootFS.Type\n\tfor _, d := range imgOCI.RootFS.DiffIDs {\n\t\timage.RootFS.Layers = append(image.RootFS.Layers, d.String())\n\t}\n\n\tportSet := make(nat.PortSet)\n\tfor k := range imgOCI.Config.ExposedPorts {\n\t\tportSet[nat.Port(k)] = struct{}{}\n\t}\n\n\timage.Config = &Config{\n\t\tCmd:          imgOCI.Config.Cmd,\n\t\tVolumes:      imgOCI.Config.Volumes,\n\t\tEnv:          imgOCI.Config.Env,\n\t\tUser:         imgOCI.Config.User,\n\t\tWorkingDir:   imgOCI.Config.WorkingDir,\n\t\tEntrypoint:   imgOCI.Config.Entrypoint,\n\t\tLabels:       imgOCI.Config.Labels,\n\t\tExposedPorts: portSet,\n\t\tImage:        nativeImage.Image.Name,\n\t}\n\n\t// Add health check if present in labels\n\tif healthStr, ok := imgOCI.Config.Labels[labels.HealthCheck]; ok && healthStr != \"\" {\n\t\thealthCheckConfig, err := healthcheck.HealthCheckFromJSON(healthStr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse healthcheck label: %w\", err)\n\t\t}\n\t\timage.Config.Healthcheck = healthCheckConfig\n\t}\n\n\treturn image, nil\n}\n\n// mountsFromNative only filters bind mount to transform from native container.\n// Because native container shows all types of mounts, such as tmpfs, proc, sysfs.\nfunc mountsFromNative(spMounts []specs.Mount) []MountPoint {\n\tmountpoints := make([]MountPoint, 0, len(spMounts))\n\tfor _, m := range spMounts {\n\t\tvar mp MountPoint\n\t\tif m.Type != \"bind\" {\n\t\t\tcontinue\n\t\t}\n\t\tmp.Type = m.Type\n\t\tmp.Source = m.Source\n\t\tmp.Destination = m.Destination\n\t\tmp.Mode = strings.Join(m.Options, \",\")\n\t\tmp.RW, mp.Propagation = ParseMountProperties(m.Options)\n\t\tmountpoints = append(mountpoints, mp)\n\t}\n\n\treturn mountpoints\n}\n\nfunc statusFromNative(x containerd.Status, labels map[string]string) string {\n\tswitch s := x.Status; s {\n\tcase containerd.Stopped:\n\t\tif labels[restart.StatusLabel] == string(containerd.Running) && restart.Reconcile(x, labels) {\n\t\t\treturn \"restarting\"\n\t\t}\n\t\treturn \"exited\"\n\tdefault:\n\t\treturn string(s)\n\t}\n}\n\nfunc networkSettingsFromNative(n *native.NetNS, _ *specs.Spec) (*NetworkSettings, error) {\n\tres := &NetworkSettings{\n\t\tNetworks: make(map[string]*NetworkEndpointSettings),\n\t}\n\tresPortMap := make(nat.PortMap)\n\tres.Ports = &resPortMap\n\tif n == nil {\n\t\treturn res, nil\n\t}\n\n\tvar primary *NetworkEndpointSettings\n\tfor _, x := range n.Interfaces {\n\t\tif x.Interface.Flags&net.FlagLoopback != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif x.Interface.Flags&net.FlagUp == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tnes := &NetworkEndpointSettings{}\n\t\tnes.MacAddress = x.HardwareAddr\n\n\t\tfor _, a := range x.Addrs {\n\t\t\tip, ipnet, err := net.ParseCIDR(a)\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).WithField(\"name\", x.Name).Warnf(\"failed to parse %q\", a)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif ip.IsLoopback() || ip.IsLinkLocalUnicast() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tones, _ := ipnet.Mask.Size()\n\t\t\tif ip4 := ip.To4(); ip4 != nil {\n\t\t\t\tnes.IPAddress = ip4.String()\n\t\t\t\tnes.IPPrefixLen = ones\n\t\t\t} else if ip16 := ip.To16(); ip16 != nil {\n\t\t\t\tnes.GlobalIPv6Address = ip16.String()\n\t\t\t\tnes.GlobalIPv6PrefixLen = ones\n\t\t\t}\n\t\t}\n\t\t// TODO: set CNI name when possible\n\t\tfakeDockerNetworkName := fmt.Sprintf(\"unknown-%s\", x.Name)\n\t\tres.Networks[fakeDockerNetworkName] = nes\n\n\t\tnports, err := convertToNatPort(n.PortMappings)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor portLabel, portBindings := range *nports {\n\t\t\tresPortMap[portLabel] = portBindings\n\t\t}\n\n\t\tif x.Index == n.PrimaryInterface {\n\t\t\tprimary = nes\n\t\t}\n\n\t}\n\tif primary != nil {\n\t\tres.DefaultNetworkSettings.MacAddress = primary.MacAddress\n\t\tres.DefaultNetworkSettings.IPAddress = primary.IPAddress\n\t\tres.DefaultNetworkSettings.IPPrefixLen = primary.IPPrefixLen\n\t\tres.DefaultNetworkSettings.GlobalIPv6Address = primary.GlobalIPv6Address\n\t\tres.DefaultNetworkSettings.GlobalIPv6PrefixLen = primary.GlobalIPv6PrefixLen\n\t}\n\treturn res, nil\n}\n\nfunc cpuSettingsFromNative(sp *specs.Spec) (*CPUSettings, error) {\n\tres := &CPUSettings{}\n\tif sp.Linux != nil && sp.Linux.Resources != nil && sp.Linux.Resources.CPU != nil {\n\t\tif sp.Linux.Resources.CPU.Cpus != \"\" {\n\t\t\tres.CPUSetCpus = sp.Linux.Resources.CPU.Cpus\n\t\t}\n\n\t\tif sp.Linux.Resources.CPU.Mems != \"\" {\n\t\t\tres.CPUSetMems = sp.Linux.Resources.CPU.Mems\n\t\t}\n\n\t\tif sp.Linux.Resources.CPU.Shares != nil && *sp.Linux.Resources.CPU.Shares > 0 {\n\t\t\tres.CPUShares = *sp.Linux.Resources.CPU.Shares\n\t\t}\n\n\t\tif sp.Linux.Resources.CPU.Quota != nil && *sp.Linux.Resources.CPU.Quota > 0 {\n\t\t\tres.CPUQuota = *sp.Linux.Resources.CPU.Quota\n\t\t}\n\t\tif sp.Linux.Resources.CPU.Period != nil && *sp.Linux.Resources.CPU.Period > 0 {\n\t\t\tres.CPUPeriod = *sp.Linux.Resources.CPU.Period\n\t\t}\n\t\tif sp.Linux.Resources.CPU.RealtimePeriod != nil && *sp.Linux.Resources.CPU.RealtimePeriod > 0 {\n\t\t\tres.CPURealtimePeriod = *sp.Linux.Resources.CPU.RealtimePeriod\n\t\t}\n\t\tif sp.Linux.Resources.CPU.RealtimeRuntime != nil && *sp.Linux.Resources.CPU.RealtimeRuntime > 0 {\n\t\t\tres.CPURealtimeRuntime = *sp.Linux.Resources.CPU.RealtimeRuntime\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\nfunc getCgroupnsFromNative(sp *specs.Spec) (string, error) {\n\tres := \"\"\n\tif sp.Linux != nil && len(sp.Linux.Namespaces) != 0 {\n\t\tfor _, ns := range sp.Linux.Namespaces {\n\t\t\tif ns.Type == \"cgroup\" {\n\t\t\t\tres = \"private\"\n\t\t\t}\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc groupAddFromNative(sp *specs.Spec) ([]string, error) {\n\tres := []string{}\n\tif sp.Process != nil && sp.Process.User.AdditionalGids != nil {\n\t\tfor _, gid := range sp.Process.User.AdditionalGids {\n\t\t\tif gid != 0 {\n\t\t\t\tres = append(res, strconv.FormatUint(uint64(gid), 10))\n\t\t\t}\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc convertToNatPort(portMappings []cni.PortMapping) (*nat.PortMap, error) {\n\tportMap := make(nat.PortMap)\n\tfor _, portMapping := range portMappings {\n\t\tports := []nat.PortBinding{}\n\t\tp := nat.PortBinding{\n\t\t\tHostIP:   portMapping.HostIP,\n\t\t\tHostPort: strconv.FormatInt(int64(portMapping.HostPort), 10),\n\t\t}\n\t\tnewP, err := nat.NewPort(portMapping.Protocol, strconv.FormatInt(int64(portMapping.ContainerPort), 10))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tports = append(ports, p)\n\t\tportMap[newP] = ports\n\t}\n\treturn &portMap, nil\n}\n\nfunc parseExtraHosts(extraHostsJSON string) []string {\n\tvar extraHosts []string\n\tif err := json.Unmarshal([]byte(extraHostsJSON), &extraHosts); err != nil {\n\t\t// Handle error or return empty slice\n\t\treturn []string{}\n\t}\n\treturn extraHosts\n}\n\nfunc getMemorySettingsFromNative(sp *specs.Spec) (*MemorySetting, error) {\n\tres := &MemorySetting{}\n\tif sp.Linux != nil && sp.Linux.Resources != nil && sp.Linux.Resources.Memory != nil {\n\t\tif sp.Linux.Resources.Memory.DisableOOMKiller != nil {\n\t\t\tres.DisableOOMKiller = *sp.Linux.Resources.Memory.DisableOOMKiller\n\t\t}\n\n\t\tif sp.Linux.Resources.Memory.Limit != nil {\n\t\t\tres.Limit = *sp.Linux.Resources.Memory.Limit\n\t\t}\n\n\t\tif sp.Linux.Resources.Memory.Swap != nil {\n\t\t\tres.Swap = *sp.Linux.Resources.Memory.Swap\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc getDNSFromNative(lbls map[string]string) (*DNSSettings, error) {\n\tres := &DNSSettings{}\n\n\tif dnsSettingJSON, ok := lbls[labels.DNSSetting]; ok {\n\t\tif err := json.Unmarshal([]byte(dnsSettingJSON), &res); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse DNS settings: %v\", err)\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\nfunc getHostConfigLabelFromNative(lbls map[string]string) (*HostConfigLabel, error) {\n\tres := &HostConfigLabel{}\n\n\tif hostConfigLabelJSON, ok := lbls[labels.HostConfigLabel]; ok {\n\t\tif err := json.Unmarshal([]byte(hostConfigLabelJSON), &res); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse DNS servers: %v\", err)\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc getOomScoreAdjFromNative(sp *specs.Spec) (int, error) {\n\tvar res int\n\tif sp.Process != nil && sp.Process.OOMScoreAdj != nil {\n\t\tres = *sp.Process.OOMScoreAdj\n\t}\n\treturn res, nil\n}\n\nfunc getUtsModeFromNative(sp *specs.Spec) (string, error) {\n\tif sp.Linux != nil && len(sp.Linux.Namespaces) > 0 {\n\t\tfor _, ns := range sp.Linux.Namespaces {\n\t\t\tif ns.Type == \"uts\" {\n\t\t\t\treturn \"\", nil\n\t\t\t}\n\t\t}\n\t}\n\treturn \"host\", nil\n}\n\nfunc getSysctlFromNative(sp *specs.Spec) (map[string]string, error) {\n\tvar res map[string]string\n\tif sp.Linux != nil && sp.Linux.Sysctl != nil {\n\t\tres = sp.Linux.Sysctl\n\t}\n\treturn res, nil\n}\n\ntype IPAMConfig struct {\n\tSubnet  string `json:\"Subnet,omitempty\"`\n\tGateway string `json:\"Gateway,omitempty\"`\n\tIPRange string `json:\"IPRange,omitempty\"`\n}\n\ntype IPAM struct {\n\t// Driver is omitted\n\tConfig []IPAMConfig `json:\"Config,omitempty\"`\n}\n\n// Network mimics a `docker network inspect` object.\n// From https://github.com/moby/moby/blob/v20.10.7/api/types/types.go#L430-L448\ntype Network struct {\n\tName       string                      `json:\"Name\"`\n\tID         string                      `json:\"Id,omitempty\"` // optional in nerdctl\n\tIPAM       IPAM                        `json:\"IPAM,omitempty\"`\n\tLabels     map[string]string           `json:\"Labels\"`\n\tContainers map[string]EndpointResource `json:\"Containers\"` // Containers contains endpoints belonging to the network\n\t// Scope, Driver, etc. are omitted\n}\n\ntype EndpointResource struct {\n\tName string `json:\"Name\"`\n\t// EndpointID  string `json:\"EndpointID\"`\n\t// MacAddress  string `json:\"MacAddress\"`\n\t// IPv4Address string `json:\"IPv4Address\"`\n\t// IPv6Address string `json:\"IPv6Address\"`\n}\n\ntype structuredCNI struct {\n\tName    string `json:\"name\"`\n\tPlugins []struct {\n\t\tIpam struct {\n\t\t\tRanges [][]IPAMConfig `json:\"ranges\"`\n\t\t} `json:\"ipam\"`\n\t} `json:\"plugins\"`\n}\n\ntype MemorySetting struct {\n\tLimit            int64 `json:\"limit\"`\n\tSwap             int64 `json:\"swap\"`\n\tDisableOOMKiller bool  `json:\"disableOOMKiller\"`\n}\n\nfunc NetworkFromNative(n *native.Network) (*Network, error) {\n\tvar res Network\n\n\tsCNI := &structuredCNI{}\n\terr := json.Unmarshal(n.CNI, sCNI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres.Name = sCNI.Name\n\tfor _, plugin := range sCNI.Plugins {\n\t\tfor _, ranges := range plugin.Ipam.Ranges {\n\t\t\tres.IPAM.Config = append(res.IPAM.Config, ranges...)\n\t\t}\n\t}\n\n\tif n.NerdctlID != nil {\n\t\tres.ID = *n.NerdctlID\n\t}\n\n\tif n.NerdctlLabels != nil {\n\t\tres.Labels = *n.NerdctlLabels\n\t}\n\n\tres.Containers = make(map[string]EndpointResource)\n\tfor _, container := range n.Containers {\n\t\tres.Containers[container.ID] = EndpointResource{\n\t\t\tName: container.Labels[labels.Name],\n\t\t\t// EndpointID:  container.EndpointID,\n\t\t\t// MacAddress:  container.MacAddress,\n\t\t\t// IPv4Address: container.IPv4Address,\n\t\t\t// IPv6Address: container.IPv6Address,\n\t\t}\n\t}\n\n\treturn &res, nil\n}\n\nfunc parseMounts(nerdctlMounts string) ([]MountPoint, error) {\n\tvar mounts []MountPoint\n\terr := json.Unmarshal([]byte(nerdctlMounts), &mounts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn mounts, nil\n}\n\nfunc ParseMountProperties(option []string) (rw bool, propagation string) {\n\trw = true\n\tfor _, opt := range option {\n\t\tswitch opt {\n\t\tcase \"ro\", \"rro\":\n\t\t\trw = false\n\t\tcase \"private\", \"rprivate\", \"shared\", \"rshared\", \"slave\", \"rslave\":\n\t\t\tpropagation = opt\n\t\tdefault:\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/inspecttypes/dockercompat/dockercompat_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage dockercompat\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/docker/go-connections/nat\"\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"gotest.tools/v3/assert\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/healthcheck\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\nfunc TestContainerFromNative(t *testing.T) {\n\ttempStateDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfilesystem.WriteFile(filepath.Join(tempStateDir, \"resolv.conf\"), []byte(\"\"), 0644)\n\tdefer os.RemoveAll(tempStateDir)\n\n\thc := &healthcheck.Healthcheck{\n\t\tTest:        []string{\"CMD-SHELL\", \"curl -f http://localhost || exit 1\"},\n\t\tInterval:    time.Second * 30,\n\t\tTimeout:     time.Second * 5,\n\t\tRetries:     3,\n\t\tStartPeriod: time.Second * 10,\n\t}\n\thcJSON, err := hc.ToJSONString()\n\tassert.NilError(t, err)\n\n\ttestcase := []struct {\n\t\tname     string\n\t\tn        *native.Container\n\t\texpected *Container\n\t}{\n\t\t// nerdctl container, mount /mnt/foo:/mnt/foo:rw,rslave; ResolvConfPath; hostname\n\t\t{\n\t\t\tname: \"container from nerdctl\",\n\t\t\tn: &native.Container{\n\t\t\t\tContainer: containers.Container{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"nerdctl/mounts\":    \"[{\\\"Type\\\":\\\"bind\\\",\\\"Source\\\":\\\"/mnt/foo\\\",\\\"Destination\\\":\\\"/mnt/foo\\\",\\\"Mode\\\":\\\"rshared,rw\\\",\\\"RW\\\":true,\\\"Propagation\\\":\\\"rshared\\\"}]\",\n\t\t\t\t\t\t\"nerdctl/state-dir\": tempStateDir,\n\t\t\t\t\t\t\"nerdctl/hostname\":  \"host1\",\n\t\t\t\t\t\t\"nerdctl/user\":      \"test-user\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: &specs.Spec{\n\t\t\t\t\tProcess: &specs.Process{\n\t\t\t\t\t\tEnv: []string{\"/some/path\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tProcess: &native.Process{\n\t\t\t\t\tPid: 10000,\n\t\t\t\t\tStatus: containerd.Status{\n\t\t\t\t\t\tStatus: \"running\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &Container{\n\t\t\t\tCreated:        \"0001-01-01T00:00:00Z\",\n\t\t\t\tPlatform:       runtime.GOOS,\n\t\t\t\tResolvConfPath: filepath.Join(tempStateDir, \"resolv.conf\"),\n\t\t\t\tState: &ContainerState{\n\t\t\t\t\tStatus:     \"running\",\n\t\t\t\t\tRunning:    true,\n\t\t\t\t\tPid:        10000,\n\t\t\t\t\tFinishedAt: \"\",\n\t\t\t\t},\n\t\t\t\tHostConfig: &HostConfig{\n\t\t\t\t\tPortBindings: nat.PortMap{},\n\t\t\t\t\tGroupAdd:     []string{},\n\t\t\t\t\tLogConfig: loggerLogConfig{\n\t\t\t\t\t\tDriver: \"json-file\",\n\t\t\t\t\t\tOpts:   map[string]string{},\n\t\t\t\t\t},\n\t\t\t\t\tUTSMode:       \"host\",\n\t\t\t\t\tTmpfs:         map[string]string{},\n\t\t\t\t\tBlkioSettings: getDefaultBlkioSettings(),\n\t\t\t\t},\n\t\t\t\tMounts: []MountPoint{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\tSource:      \"/mnt/foo\",\n\t\t\t\t\t\tDestination: \"/mnt/foo\",\n\t\t\t\t\t\tMode:        \"rshared,rw\",\n\t\t\t\t\t\tRW:          true,\n\t\t\t\t\t\tPropagation: \"rshared\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tConfig: &Config{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"nerdctl/mounts\":    \"[{\\\"Type\\\":\\\"bind\\\",\\\"Source\\\":\\\"/mnt/foo\\\",\\\"Destination\\\":\\\"/mnt/foo\\\",\\\"Mode\\\":\\\"rshared,rw\\\",\\\"RW\\\":true,\\\"Propagation\\\":\\\"rshared\\\"}]\",\n\t\t\t\t\t\t\"nerdctl/state-dir\": tempStateDir,\n\t\t\t\t\t\t\"nerdctl/hostname\":  \"host1\",\n\t\t\t\t\t\t\"nerdctl/user\":      \"test-user\",\n\t\t\t\t\t},\n\t\t\t\t\tHostname: \"host1\",\n\t\t\t\t\tEnv:      []string{\"/some/path\"},\n\t\t\t\t\tUser:     \"test-user\",\n\t\t\t\t},\n\t\t\t\tNetworkSettings: &NetworkSettings{\n\t\t\t\t\tPorts:    &nat.PortMap{},\n\t\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t// cri container, mount /mnt/foo:/mnt/foo:rw,rslave; mount resolv.conf and hostname; internal sysfs mount\n\t\t{\n\t\t\tname: \"container from cri\",\n\t\t\tn: &native.Container{\n\t\t\t\tContainer: containers.Container{},\n\t\t\t\tSpec: &specs.Spec{\n\t\t\t\t\tMounts: []specs.Mount{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/etc/resolv.conf\",\n\t\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\t\tSource:      \"/mock-sandbox-dir/resolv.conf\",\n\t\t\t\t\t\t\tOptions:     []string{\"rbind\", \"rprivate\", \"rw\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/etc/hostname\",\n\t\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\t\tSource:      \"/mock-sandbox-dir/hostname\",\n\t\t\t\t\t\t\tOptions:     []string{\"rbind\", \"rprivate\", \"rw\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/mnt/foo\",\n\t\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\t\tSource:      \"/mnt/foo\",\n\t\t\t\t\t\t\tOptions:     []string{\"rbind\", \"rslave\", \"rw\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/sys\",\n\t\t\t\t\t\t\tType:        \"sysfs\",\n\t\t\t\t\t\t\tSource:      \"sysfs\",\n\t\t\t\t\t\t\tOptions:     []string{\"nosuid\", \"noexec\", \"nodev\", \"ro\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/etc/hosts\",\n\t\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\t\tSource:      \"/mock-sandbox-dir/hosts\",\n\t\t\t\t\t\t\tOptions:     []string{\"bind\", \"rprivate\", \"rw\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tProcess: &native.Process{\n\t\t\t\t\tPid: 10000,\n\t\t\t\t\tStatus: containerd.Status{\n\t\t\t\t\t\tStatus: \"running\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &Container{\n\t\t\t\tCreated:        \"0001-01-01T00:00:00Z\",\n\t\t\t\tPlatform:       runtime.GOOS,\n\t\t\t\tResolvConfPath: \"/mock-sandbox-dir/resolv.conf\",\n\t\t\t\tHostnamePath:   \"/mock-sandbox-dir/hostname\",\n\t\t\t\tHostsPath:      \"/mock-sandbox-dir/hosts\",\n\t\t\t\tState: &ContainerState{\n\t\t\t\t\tStatus:     \"running\",\n\t\t\t\t\tRunning:    true,\n\t\t\t\t\tPid:        10000,\n\t\t\t\t\tFinishedAt: \"\",\n\t\t\t\t},\n\t\t\t\tHostConfig: &HostConfig{\n\t\t\t\t\tPortBindings: nat.PortMap{},\n\t\t\t\t\tGroupAdd:     []string{},\n\t\t\t\t\tLogConfig: loggerLogConfig{\n\t\t\t\t\t\tDriver: \"json-file\",\n\t\t\t\t\t\tOpts:   map[string]string{},\n\t\t\t\t\t},\n\t\t\t\t\tUTSMode:       \"host\",\n\t\t\t\t\tTmpfs:         map[string]string{},\n\t\t\t\t\tBlkioSettings: getDefaultBlkioSettings(),\n\t\t\t\t},\n\t\t\t\tMounts: []MountPoint{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\tSource:      \"/mock-sandbox-dir/resolv.conf\",\n\t\t\t\t\t\tDestination: \"/etc/resolv.conf\",\n\t\t\t\t\t\tMode:        \"rbind,rprivate,rw\",\n\t\t\t\t\t\tRW:          true,\n\t\t\t\t\t\tPropagation: \"rprivate\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\tSource:      \"/mock-sandbox-dir/hostname\",\n\t\t\t\t\t\tDestination: \"/etc/hostname\",\n\t\t\t\t\t\tMode:        \"rbind,rprivate,rw\",\n\t\t\t\t\t\tRW:          true,\n\t\t\t\t\t\tPropagation: \"rprivate\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\tSource:      \"/mnt/foo\",\n\t\t\t\t\t\tDestination: \"/mnt/foo\",\n\t\t\t\t\t\tMode:        \"rbind,rslave,rw\",\n\t\t\t\t\t\tRW:          true,\n\t\t\t\t\t\tPropagation: \"rslave\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\tSource:      \"/mock-sandbox-dir/hosts\",\n\t\t\t\t\t\tDestination: \"/etc/hosts\",\n\t\t\t\t\t\tMode:        \"bind,rprivate,rw\",\n\t\t\t\t\t\tRW:          true,\n\t\t\t\t\t\tPropagation: \"rprivate\",\n\t\t\t\t\t},\n\t\t\t\t\t// ignore sysfs mountpoint\n\t\t\t\t},\n\t\t\t\tConfig: &Config{},\n\t\t\t\tNetworkSettings: &NetworkSettings{\n\t\t\t\t\tPorts:    &nat.PortMap{},\n\t\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t// ctr container, mount /mnt/foo:/mnt/foo:rw,rslave; internal sysfs mount; hostname\n\t\t{\n\t\t\tname: \"container from ctr\",\n\t\t\tn: &native.Container{\n\t\t\t\tContainer: containers.Container{},\n\t\t\t\tSpec: &specs.Spec{\n\t\t\t\t\tHostname: \"host1\",\n\t\t\t\t\tMounts: []specs.Mount{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/mnt/foo\",\n\t\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\t\tSource:      \"/mnt/foo\",\n\t\t\t\t\t\t\tOptions:     []string{\"rbind\", \"rslave\", \"rw\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tDestination: \"/sys\",\n\t\t\t\t\t\t\tType:        \"sysfs\",\n\t\t\t\t\t\t\tSource:      \"sysfs\",\n\t\t\t\t\t\t\tOptions:     []string{\"nosuid\", \"noexec\", \"nodev\", \"ro\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tProcess: &native.Process{\n\t\t\t\t\tPid: 10000,\n\t\t\t\t\tStatus: containerd.Status{\n\t\t\t\t\t\tStatus: \"running\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &Container{\n\t\t\t\tCreated:  \"0001-01-01T00:00:00Z\",\n\t\t\t\tPlatform: runtime.GOOS,\n\t\t\t\tState: &ContainerState{\n\t\t\t\t\tStatus:     \"running\",\n\t\t\t\t\tRunning:    true,\n\t\t\t\t\tPid:        10000,\n\t\t\t\t\tFinishedAt: \"\",\n\t\t\t\t},\n\t\t\t\tHostConfig: &HostConfig{\n\t\t\t\t\tPortBindings: nat.PortMap{},\n\t\t\t\t\tGroupAdd:     []string{},\n\t\t\t\t\tLogConfig: loggerLogConfig{\n\t\t\t\t\t\tDriver: \"json-file\",\n\t\t\t\t\t\tOpts:   map[string]string{},\n\t\t\t\t\t},\n\t\t\t\t\tUTSMode:       \"host\",\n\t\t\t\t\tTmpfs:         map[string]string{},\n\t\t\t\t\tBlkioSettings: getDefaultBlkioSettings(),\n\t\t\t\t},\n\t\t\t\tMounts: []MountPoint{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\t\tSource:      \"/mnt/foo\",\n\t\t\t\t\t\tDestination: \"/mnt/foo\",\n\t\t\t\t\t\tMode:        \"rbind,rslave,rw\",\n\t\t\t\t\t\tRW:          true,\n\t\t\t\t\t\tPropagation: \"rslave\",\n\t\t\t\t\t},\n\t\t\t\t\t// ignore sysfs mountpoint\n\t\t\t\t},\n\t\t\t\tConfig: &Config{\n\t\t\t\t\tHostname: \"host1\",\n\t\t\t\t},\n\t\t\t\tNetworkSettings: &NetworkSettings{\n\t\t\t\t\tPorts:    &nat.PortMap{},\n\t\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"container with healthcheck label\",\n\t\t\tn: &native.Container{\n\t\t\t\tContainer: containers.Container{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\tlabels.HealthCheck: hcJSON,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: &specs.Spec{},\n\t\t\t\tProcess: &native.Process{\n\t\t\t\t\tStatus: containerd.Status{\n\t\t\t\t\t\tStatus: \"running\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &Container{\n\t\t\t\tCreated:  \"0001-01-01T00:00:00Z\",\n\t\t\t\tPlatform: runtime.GOOS,\n\t\t\t\tMounts:   []MountPoint{},\n\t\t\t\tState: &ContainerState{\n\t\t\t\t\tStatus:     \"running\",\n\t\t\t\t\tRunning:    true,\n\t\t\t\t\tPid:        0,\n\t\t\t\t\tFinishedAt: \"\",\n\t\t\t\t},\n\t\t\t\tHostConfig: &HostConfig{\n\t\t\t\t\tLogConfig:     loggerLogConfig{Driver: \"json-file\", Opts: map[string]string{}},\n\t\t\t\t\tPortBindings:  nat.PortMap{},\n\t\t\t\t\tGroupAdd:      []string{},\n\t\t\t\t\tTmpfs:         map[string]string{},\n\t\t\t\t\tUTSMode:       \"host\",\n\t\t\t\t\tBlkioSettings: getDefaultBlkioSettings(),\n\t\t\t\t},\n\t\t\t\tNetworkSettings: &NetworkSettings{\n\t\t\t\t\tPorts:    &nat.PortMap{},\n\t\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{},\n\t\t\t\t},\n\t\t\t\tConfig: &Config{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\tlabels.HealthCheck: hcJSON,\n\t\t\t\t\t},\n\t\t\t\t\tHealthcheck: hc,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcase {\n\t\tt.Run(tc.name, func(tt *testing.T) {\n\t\t\td, _ := ContainerFromNative(tc.n)\n\t\t\tassert.DeepEqual(tt, d, tc.expected)\n\t\t})\n\t}\n}\n\nfunc TestNetworkSettingsFromNative(t *testing.T) {\n\ttempStateDir, err := os.MkdirTemp(t.TempDir(), \"rw\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfilesystem.WriteFile(filepath.Join(tempStateDir, \"resolv.conf\"), []byte(\"\"), 0644)\n\tdefer os.RemoveAll(tempStateDir)\n\n\ttestcase := []struct {\n\t\tname     string\n\t\tn        *native.NetNS\n\t\ts        *specs.Spec\n\t\texpected *NetworkSettings\n\t}{\n\t\t// Given null native.NetNS, Return initialized NetworkSettings\n\t\t//    UseCase: Inspect a Stopped Container\n\t\t{\n\t\t\tname: \"Given Null NetNS, Return initialized NetworkSettings\",\n\t\t\tn:    nil,\n\t\t\ts:    &specs.Spec{},\n\t\t\texpected: &NetworkSettings{\n\t\t\t\tPorts:    &nat.PortMap{},\n\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{},\n\t\t\t},\n\t\t},\n\t\t// Given native.NetNS with single Interface with Port Annotations, Return populated NetworkSettings\n\t\t//   UseCase: Inspect a Running Container with published ports\n\t\t{\n\t\t\tname: \"Given NetNS with single Interface with Port Annotation, Return populated NetworkSettings\",\n\t\t\tn: &native.NetNS{\n\t\t\t\tInterfaces: []native.NetInterface{\n\t\t\t\t\t{\n\t\t\t\t\t\tInterface: net.Interface{\n\t\t\t\t\t\t\tIndex: 1,\n\t\t\t\t\t\t\tMTU:   1500,\n\t\t\t\t\t\t\tName:  \"eth0.100\",\n\t\t\t\t\t\t\tFlags: net.FlagUp,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tHardwareAddr: \"xx:xx:xx:xx:xx:xx\",\n\t\t\t\t\t\tFlags:        []string{},\n\t\t\t\t\t\tAddrs:        []string{\"10.0.4.30/24\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPortMappings: []cni.PortMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tHostPort:      8075,\n\t\t\t\t\t\tContainerPort: 77,\n\t\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ts: &specs.Spec{\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\texpected: &NetworkSettings{\n\t\t\t\tPorts: &nat.PortMap{\n\t\t\t\t\tnat.Port(\"77/tcp\"): []nat.PortBinding{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tHostIP:   \"127.0.0.1\",\n\t\t\t\t\t\t\tHostPort: \"8075\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{\n\t\t\t\t\t\"unknown-eth0.100\": {\n\t\t\t\t\t\tIPAddress:   \"10.0.4.30\",\n\t\t\t\t\t\tIPPrefixLen: 24,\n\t\t\t\t\t\tMacAddress:  \"xx:xx:xx:xx:xx:xx\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t// Given native.NetNS with single Interface without Port Annotations, Return valid NetworkSettings w/ empty Ports\n\t\t//   UseCase: Inspect a Running Container without published ports\n\t\t{\n\t\t\tname: \"Given NetNS with single Interface without Port Annotations, Return valid NetworkSettings w/ empty Ports\",\n\t\t\tn: &native.NetNS{\n\t\t\t\tInterfaces: []native.NetInterface{\n\t\t\t\t\t{\n\t\t\t\t\t\tInterface: net.Interface{\n\t\t\t\t\t\t\tIndex: 1,\n\t\t\t\t\t\t\tMTU:   1500,\n\t\t\t\t\t\t\tName:  \"eth0.100\",\n\t\t\t\t\t\t\tFlags: net.FlagUp,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tHardwareAddr: \"xx:xx:xx:xx:xx:xx\",\n\t\t\t\t\t\tFlags:        []string{},\n\t\t\t\t\t\tAddrs:        []string{\"10.0.4.30/24\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ts: &specs.Spec{\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\texpected: &NetworkSettings{\n\t\t\t\tPorts: &nat.PortMap{},\n\t\t\t\tNetworks: map[string]*NetworkEndpointSettings{\n\t\t\t\t\t\"unknown-eth0.100\": {\n\t\t\t\t\t\tIPAddress:   \"10.0.4.30\",\n\t\t\t\t\t\tIPPrefixLen: 24,\n\t\t\t\t\t\tMacAddress:  \"xx:xx:xx:xx:xx:xx\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcase {\n\t\tt.Run(tc.name, func(tt *testing.T) {\n\t\t\td, _ := networkSettingsFromNative(tc.n, tc.s)\n\t\t\tassert.DeepEqual(tt, d, tc.expected)\n\t\t})\n\t}\n}\n\nfunc TestCpuSettingsFromNative(t *testing.T) {\n\t// Helper function to create uint64 pointer\n\tuint64Ptr := func(i uint64) *uint64 {\n\t\treturn &i\n\t}\n\n\tint64Ptr := func(i int64) *int64 {\n\t\treturn &i\n\t}\n\n\ttestcases := []struct {\n\t\tname     string\n\t\tspec     *specs.Spec\n\t\texpected *CPUSettings\n\t}{\n\t\t{\n\t\t\tname:     \"Test with empty spec\",\n\t\t\tspec:     &specs.Spec{},\n\t\t\texpected: &CPUSettings{},\n\t\t},\n\t\t{\n\t\t\tname: \"Full CPU Settings\",\n\t\t\tspec: &specs.Spec{\n\t\t\t\tLinux: &specs.Linux{\n\t\t\t\t\tResources: &specs.LinuxResources{\n\t\t\t\t\t\tCPU: &specs.LinuxCPU{\n\t\t\t\t\t\t\tCpus:            \"0-3\",\n\t\t\t\t\t\t\tMems:            \"0-1\",\n\t\t\t\t\t\t\tShares:          uint64Ptr(1024),\n\t\t\t\t\t\t\tQuota:           int64Ptr(100000),\n\t\t\t\t\t\t\tPeriod:          uint64Ptr(100000),\n\t\t\t\t\t\t\tRealtimePeriod:  uint64Ptr(1000000),\n\t\t\t\t\t\t\tRealtimeRuntime: int64Ptr(950000),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &CPUSettings{\n\t\t\t\tCPUSetCpus:         \"0-3\",\n\t\t\t\tCPUSetMems:         \"0-1\",\n\t\t\t\tCPUShares:          1024,\n\t\t\t\tCPUQuota:           100000,\n\t\t\t\tCPUPeriod:          100000,\n\t\t\t\tCPURealtimePeriod:  1000000,\n\t\t\t\tCPURealtimeRuntime: 950000,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Partial CPU Settings\",\n\t\t\tspec: &specs.Spec{\n\t\t\t\tLinux: &specs.Linux{\n\t\t\t\t\tResources: &specs.LinuxResources{\n\t\t\t\t\t\tCPU: &specs.LinuxCPU{\n\t\t\t\t\t\t\tCpus:   \"0,1\",\n\t\t\t\t\t\t\tShares: uint64Ptr(512),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &CPUSettings{\n\t\t\t\tCPUSetCpus: \"0,1\",\n\t\t\t\tCPUShares:  512,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Zero Values Should Be Ignored\",\n\t\t\tspec: &specs.Spec{\n\t\t\t\tLinux: &specs.Linux{\n\t\t\t\t\tResources: &specs.LinuxResources{\n\t\t\t\t\t\tCPU: &specs.LinuxCPU{\n\t\t\t\t\t\t\tShares:          uint64Ptr(0),\n\t\t\t\t\t\t\tQuota:           int64Ptr(0),\n\t\t\t\t\t\t\tPeriod:          uint64Ptr(0),\n\t\t\t\t\t\t\tRealtimePeriod:  uint64Ptr(0),\n\t\t\t\t\t\t\tRealtimeRuntime: int64Ptr(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &CPUSettings{},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult, err := cpuSettingsFromNative(tc.spec)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tassert.DeepEqual(t, result, tc.expected)\n\t\t})\n\t}\n}\n\nfunc TestImageFromNative(t *testing.T) {\n\tt.Run(\"parses RepoTags/Digests and RootFS Layers\", func(t *testing.T) {\n\t\tcreatedTime := time.Now().UTC()\n\n\t\timg := native.Image{\n\t\t\tImage: images.Image{\n\t\t\t\tName: \"myrepo/myimage:custom\",\n\t\t\t\tTarget: ocispec.Descriptor{\n\t\t\t\t\tDigest: digest.Digest(\"sha256:targetdigest\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tImageConfigDesc: ocispec.Descriptor{\n\t\t\t\tDigest: digest.Digest(\"sha256:configdigest\"),\n\t\t\t},\n\t\t\tImageConfig: ocispec.Image{\n\t\t\t\tRootFS: ocispec.RootFS{\n\t\t\t\t\tType:    \"layers\",\n\t\t\t\t\tDiffIDs: []digest.Digest{\"sha256:layer1\", \"sha256:layer2\"},\n\t\t\t\t},\n\t\t\t\tHistory: []ocispec.History{\n\t\t\t\t\t{\n\t\t\t\t\t\tCreated: &createdTime,\n\t\t\t\t\t\tAuthor:  \"test-author\",\n\t\t\t\t\t\tComment: \"test-comment\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tout, err := ImageFromNative(&img)\n\t\tassert.NilError(t, err)\n\n\t\t// ID, tags, digests\n\t\tassert.Equal(t, out.ID, \"sha256:configdigest\")\n\t\tassert.Equal(t, out.RepoTags[0], \"myrepo/myimage:custom\")\n\t\tassert.Equal(t, out.RepoDigests[0], \"myrepo/myimage@sha256:targetdigest\")\n\n\t\t// RootFS\n\t\tassert.DeepEqual(t, out.RootFS.Layers, []string{\"sha256:layer1\", \"sha256:layer2\"})\n\n\t\t// History\n\t\tassert.Equal(t, out.Author, \"test-author\")\n\t\tassert.Equal(t, out.Comment, \"test-comment\")\n\t\tassert.Equal(t, out.Created, createdTime.Format(time.RFC3339Nano))\n\t})\n\n\tt.Run(\"parses Healthcheck label\", func(t *testing.T) {\n\t\ttestcases := []struct {\n\t\t\tname     string\n\t\t\tlabels   map[string]string\n\t\t\texpected *healthcheck.Healthcheck\n\t\t}{\n\t\t\t{\n\t\t\t\tname: \"Valid Healthcheck Label\",\n\t\t\t\tlabels: map[string]string{\n\t\t\t\t\tlabels.HealthCheck: `{\n\t\t\t\t\t\t\"test\": [\"CMD-SHELL\", \"curl -f http://localhost/ || exit 1\"],\n\t\t\t\t\t\t\"interval\": 30000000000,\n\t\t\t\t\t\t\"timeout\": 5000000000\n\t\t\t\t\t}`,\n\t\t\t\t},\n\t\t\t\texpected: &healthcheck.Healthcheck{\n\t\t\t\t\tTest:     []string{\"CMD-SHELL\", \"curl -f http://localhost/ || exit 1\"},\n\t\t\t\t\tInterval: time.Second * 30,\n\t\t\t\t\tTimeout:  time.Second * 5,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"No Healthcheck Label\",\n\t\t\t\tlabels:   map[string]string{},\n\t\t\t\texpected: nil,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tc := range testcases {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\timg := native.Image{\n\t\t\t\t\tImageConfig: ocispec.Image{\n\t\t\t\t\t\tConfig: ocispec.ImageConfig{\n\t\t\t\t\t\t\tLabels: tc.labels,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tout, err := ImageFromNative(&img)\n\t\t\t\tassert.NilError(t, err)\n\t\t\t\tassert.DeepEqual(t, out.Config.Healthcheck, tc.expected)\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/inspecttypes/dockercompat/info.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/moby/blob/v20.10.8/api/types/types.go\n   Portions from https://github.com/docker/cli/blob/v20.10.8/cli/command/system/version.go\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.8/NOTICE , https://github.com/docker/cli/blob/v20.10.8/NOTICE\n*/\n\npackage dockercompat\n\n// Info mimics a `docker info` object.\n// From https://github.com/moby/moby/blob/v20.10.8/api/types/types.go#L146-L216\ntype Info struct {\n\tID          string\n\tDriver      string\n\tPlugins     PluginsInfo\n\tMemoryLimit bool\n\tSwapLimit   bool\n\t// KernelMemory is omitted because it is deprecated in the Moby\n\tCPUCfsPeriod      bool `json:\"CpuCfsPeriod\"`\n\tCPUCfsQuota       bool `json:\"CpuCfsQuota\"`\n\tCPUShares         bool\n\tCPUSet            bool\n\tCPURealtime       bool\n\tPidsLimit         bool\n\tIPv4Forwarding    bool\n\tBridgeNfIptables  bool\n\tBridgeNfIP6tables bool `json:\"BridgeNfIp6tables\"`\n\t// Nfd is omitted because it does not make sense for nerdctl\n\tOomKillDisable bool\n\t// NGoroutines is omitted because it does not make sense for nerdctl\n\tSystemTime    string\n\tLoggingDriver string\n\tCgroupDriver  string\n\tCgroupVersion string `json:\",omitempty\"`\n\t// NEventsListener is omitted because it does not make sense for nerdctl\n\tKernelVersion   string\n\tOperatingSystem string\n\tOSType          string\n\tArchitecture    string // e.g., \"x86_64\", not \"amd64\" (Corresponds to Docker)\n\tNCPU            int\n\tMemTotal        int64\n\tName            string\n\tServerVersion   string\n\tSecurityOptions []string\n\n\tWarnings []string\n}\n\ntype PluginsInfo struct {\n\tLog     []string\n\tStorage []string // nerdctl extension\n}\n\n// VersionInfo mimics a `docker version` object.\n// From https://github.com/docker/cli/blob/v20.10.8/cli/command/system/version.go#L68-L72\ntype VersionInfo struct {\n\tClient ClientVersion\n\tServer *ServerVersion\n}\n\n// ClientVersion is from https://github.com/docker/cli/blob/v20.10.8/cli/command/system/version.go#L74-L87\ntype ClientVersion struct {\n\tVersion    string\n\tGitCommit  string\n\tGoVersion  string\n\tOs         string             // GOOS\n\tArch       string             // GOARCH\n\tComponents []ComponentVersion // nerdctl extension\n}\n\n// ComponentVersion describes the version information for a specific component.\n// From https://github.com/moby/moby/blob/v20.10.8/api/types/types.go#L112-L117\ntype ComponentVersion struct {\n\tName    string\n\tVersion string\n\tDetails map[string]string `json:\",omitempty\"`\n}\n\n// ServerVersion is from https://github.com/moby/moby/blob/v20.10.8/api/types/types.go#L119-L137\ntype ServerVersion struct {\n\tComponents []ComponentVersion\n\t// Deprecated fields are not added here\n}\n"
  },
  {
    "path": "pkg/inspecttypes/native/container.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage native\n\nimport (\n\t\"net\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/go-cni\"\n)\n\n// Container corresponds to a containerd-native container object.\n// Not compatible with `docker container inspect`.\ntype Container struct {\n\tcontainers.Container\n\tSpec    interface{} `json:\"Spec,omitempty\"`\n\tProcess *Process    `json:\"Process,omitempty\"`\n}\n\ntype Process struct {\n\tPid    int               `json:\"Pid,omitempty\"`\n\tStatus containerd.Status `json:\"Status,omitempty\"`\n\tNetNS  *NetNS            `json:\"NetNS,omitempty\"`\n}\n\n// NetNS is designed not to depend on CNI\ntype NetNS struct {\n\t// PrimaryInterface is a net.Interface.Index value, not an array index.\n\t// Zero means unset.\n\tPrimaryInterface int            `json:\"PrimaryInterface,omitempty\"`\n\tInterfaces       []NetInterface `json:\"Interfaces,omitempty\"`\n\tPortMappings     []cni.PortMapping\n}\n\n// NetInterface wraps net.Interface for JSON marshallability.\n// No support for unmarshalling.\ntype NetInterface struct {\n\tnet.Interface\n\t// HardwareAddr overrides Interface.HardwareAddr\n\tHardwareAddr string\n\t// Flags overrides Interface.Flags\n\tFlags []string\n\tAddrs []string\n}\n"
  },
  {
    "path": "pkg/inspecttypes/native/image.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage native\n\nimport (\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n)\n\n// Image corresponds to a containerd-native image object.\n// Not compatible with `docker image inspect`.\ntype Image struct {\n\tImage        images.Image        `json:\"Image\"`\n\tIndexDesc    *ocispec.Descriptor `json:\"IndexDesc,omitempty\"`\n\tIndex        *ocispec.Index      `json:\"Index,omitempty\"`\n\tManifestDesc *ocispec.Descriptor `json:\"ManifestDesc,omitempty\"`\n\tManifest     *ocispec.Manifest   `json:\"Manifest,omitempty\"`\n\t// e.g., \"application/vnd.docker.container.image.v1+json\"\n\tImageConfigDesc ocispec.Descriptor `json:\"ImageConfigDesc\"`\n\tImageConfig     ocispec.Image      `json:\"ImageConfig\"`\n\tSize            int64              `json:\"size\"`\n}\n"
  },
  {
    "path": "pkg/inspecttypes/native/info.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage native\n\nimport (\n\tintrospection \"github.com/containerd/containerd/api/services/introspection/v1\"\n\tversion \"github.com/containerd/containerd/api/services/version/v1\"\n)\n\ntype Info struct {\n\tNamespace     string      `json:\"Namespace,omitempty\"`\n\tSnapshotter   string      `json:\"Snapshotter,omitempty\"`\n\tCgroupManager string      `json:\"CgroupManager,omitempty\"`\n\tRootless      bool        `json:\"Rootless,omitempty\"`\n\tDaemon        *DaemonInfo `json:\"Daemon,omitempty\"`\n}\n\ntype DaemonInfo struct {\n\tPlugins *introspection.PluginsResponse `json:\"Plugins,omitempty\"`\n\tServer  *introspection.ServerResponse  `json:\"Server,omitempty\"`\n\tVersion *version.VersionResponse       `json:\"Version,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/inspecttypes/native/namespace.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage native\n\ntype Namespace struct {\n\tName   string             `json:\"Name\"`\n\tLabels *map[string]string `json:\"Labels,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/inspecttypes/native/network.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage native\n\nimport \"encoding/json\"\n\n// Network corresponds to pkg/netutil.NetworkConfig\ntype Network struct {\n\tCNI           json.RawMessage    `json:\"CNI,omitempty\"`\n\tNerdctlID     *string            `json:\"NerdctlID\"`\n\tNerdctlLabels *map[string]string `json:\"NerdctlLabels,omitempty\"`\n\tFile          string             `json:\"File,omitempty\"`\n\tContainers    []*Container       `json:\"Containers\"`\n}\n"
  },
  {
    "path": "pkg/inspecttypes/native/volume.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage native\n\n// Volume is also compatible with Docker\ntype Volume struct {\n\tName       string             `json:\"Name\"`\n\tMountpoint string             `json:\"Mountpoint\"`\n\tLabels     *map[string]string `json:\"Labels,omitempty\"`\n\tSize       int64              `json:\"Size,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/consts.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\t// Max size of path components\n\tpathComponentMaxLength = 255\n\tprivateFilePermission  = os.FileMode(0o600)\n\tprivateDirPermission   = os.FileMode(0o700)\n)\n\nvar (\n\t// Lightweight indirection to ease testing\n\tioCopy = io.Copy\n\n\t// Location (under XDG data home) used for markers and backups\n\tfilesystemOpsPath = \"filesystem-ops\"\n\t// Suffix for markers and backup files\n\tmarkerSuffix = \"in-progress\"\n\tbackupSuffix = \"backup\"\n\n\t// holdLocation points to where markers and backup files will be held. This should NOT be let to /tmp,\n\t// but instead be explicitly configured with SetFilesystemOpsDirectory.\n\tholdLocation = os.TempDir()\n)\n\nfunc SetFilesystemOpsDirectory(path string) error {\n\tholdLocation = filepath.Join(path, filesystemOpsPath)\n\treturn os.MkdirAll(holdLocation, privateDirPermission)\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/errors.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport \"errors\"\n\nvar (\n\tErrLockFail          = errors.New(\"failed to acquire lock\")\n\tErrUnlockFail        = errors.New(\"failed to release lock\")\n\tErrLockIsNil         = errors.New(\"nil lock\")\n\tErrInvalidPath       = errors.New(\"invalid path\")\n\tErrFilesystemFailure = errors.New(\"filesystem error\")\n)\n"
  },
  {
    "path": "pkg/internal/filesystem/helpers.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"crypto/sha256\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\nconst (\n\tremoveMarker = \"remove\"\n)\n\nfunc ensureRecovery(filename string) (err error) {\n\t// Check for a marker file.\n\t// No marker means all fine, nothing to be done.\n\t// Any other error is a hard error.\n\tvar op string\n\tif op, err = markerRead(filename); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\terr = nil\n\t\t}\n\t\treturn err\n\t}\n\n\t// We have a marker. We know we were interrupted.\n\t// Check for a possible backup file.\n\tvar exists bool\n\tif exists, err = backupExists(filename); err != nil {\n\t\treturn err\n\t}\n\n\t// If we have a backup, restore from it\n\tif exists {\n\t\tif err = backupRestore(filename); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_ = backupRemove(filename)\n\t} else {\n\t\t// We do not see a backup.\n\t\t// Do we have a final destination then?\n\t\t_, err = os.Stat(filename)\n\t\t// Any error but does not exist is a hard error.\n\t\tif err != nil && !os.IsNotExist(err) {\n\t\t\treturn err\n\t\t}\n\n\t\t// If we do NOT have a destination, nothing to be done - we already took care of it, though we were interrupted\n\t\t// mid-recovery.\n\n\t\t// If we DO have a destination:\n\t\tif err == nil {\n\t\t\t// Either:\n\t\t\t// - there was no original, so we need to remove it (marker contains `remove`)\n\t\t\t// - or we were interrupted ALSO during the recovery attempt, after the backup restore above and before deleting the marker\n\t\t\t// in which case we do NOT want to remove as the file has already been restored.\n\t\t\tif op == removeMarker {\n\t\t\t\t// Errors on remove are hard errors.\n\t\t\t\tif err = os.Remove(filename); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ok, we successfully recovered, now, remove the marker and return\n\treturn markerRemove(filename)\n}\n\n// backupSave does perform a backup of the provided file at `path`.\nfunc backupSave(path string) error {\n\treturn internalCopy(path, backupLocation(path))\n}\n\n// backupRestore restores a file from its backup.\n// On success the backup is deleted.\nfunc backupRestore(path string) error {\n\terr := internalCopy(backupLocation(path), path)\n\tif err == nil {\n\t\terr = os.Remove(backupLocation(path))\n\t}\n\n\treturn err\n}\n\nfunc backupRemove(path string) error {\n\treturn os.Remove(backupLocation(path))\n}\n\n// backupExists checks if a backup file exists for file located at `path`.\nfunc backupExists(path string) (bool, error) {\n\t_, err := os.Stat(backupLocation(path))\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\n\treturn err == nil, err\n}\n\n// backupLocation returns the location of the backup for path.\nfunc backupLocation(path string) string {\n\treturn location(path) + backupSuffix\n}\n\n// markerCreate saves a marker file with the current time.\n// Markers are used to indicate an operation is in progress and allow for disaster recovery.\nfunc markerCreate(path string, op string) (err error) {\n\tvar marker *os.File\n\tmarker, err = os.OpenFile(markerLocation(path), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, privateFilePermission)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t// If we errored on sync or close, remove the marker (ignore removal errors)\n\t\tif err = errors.Join(err, marker.Close()); err != nil {\n\t\t\t_ = markerRemove(path)\n\t\t}\n\t}()\n\n\t_, err = marker.Write([]byte(op))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn marker.Sync()\n}\n\n// markerRead reads the content of a marker file if it exists (contains the time at which it was created).\nfunc markerRead(path string) (string, error) {\n\tdata, err := os.ReadFile(markerLocation(path))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(data), nil\n}\n\n// markerRemove deletes a marker file.\nfunc markerRemove(path string) error {\n\treturn os.Remove(markerLocation(path))\n}\n\n// markerLocation returns the location of the marker file for a given path.\nfunc markerLocation(path string) string {\n\treturn location(path) + markerSuffix\n}\n\n// location returns the filesystem-ops path associated with a given file (where marker and backups are located).\n// The location is unique (see hash), and shows the first 16 characters of the filename for readability.\nfunc location(path string) string {\n\tdir := filepath.Dir(path)\n\tbase := filepath.Base(path)\n\tpretty := base\n\t// Ensure that we do not blow up filesystem length limits\n\tif len(pretty) > 16 {\n\t\tpretty = pretty[:16]\n\t}\n\treturn filepath.Join(holdLocation, hash(dir)+\"-\"+pretty+\"-\"+hash(base)+\"-\")\n}\n\n// hash does return the first 8 characters of the shasum256 of the provided string.\n// Chances of collision are 50% with 77,000 *simultaneous* entries.\nfunc hash(s string) string {\n\treturn fmt.Sprintf(\"%x\", sha256.Sum256([]byte(s)))[0:8]\n}\n\n// internalCopy performs a simple copy from source to destination.\n// This in itself is not safe.\nfunc internalCopy(sourcePath, destinationPath string) (err error) {\n\tvar source *os.File\n\n\t// Open source\n\tsource, err = os.OpenFile(sourcePath, os.O_RDONLY, privateFilePermission)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\terr = errors.Join(err, source.Close())\n\t}()\n\n\t// Read file length\n\tsrcInfo, err := source.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn fileWrite(source, srcInfo.Size(), destinationPath, privateFilePermission, srcInfo.ModTime())\n}\n\n// fileWrite performs a simple write to the destination file from the provided io.Reader.\n// This in itself is not safe.\nfunc fileWrite(source io.Reader, size int64, destinationPath string, perm os.FileMode, mTime time.Time) (err error) {\n\tvar destination *os.File\n\tmustClose := true\n\n\t// Open destination\n\tdestination, err = os.OpenFile(destinationPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t// Close if need be.\n\t\tif mustClose {\n\t\t\terr = errors.Join(err, destination.Close())\n\t\t}\n\t}()\n\n\t// Copy over\n\tvar n int64\n\tn, err = ioCopy(destination, source)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n < size {\n\t\treturn io.ErrShortWrite\n\t}\n\n\t// Ensure data is committed\n\tif err = destination.Sync(); err != nil {\n\t\treturn err\n\t}\n\n\terr = destination.Close()\n\tmustClose = false\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !mTime.IsZero() {\n\t\terr = os.Chtimes(destinationPath, mTime, mTime)\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/lock.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Portions from internal go\n//\n// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n//\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:LICENSE\n\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/cmd/go/internal/lockedfile/internal/filelock/filelock.go\n\npackage filesystem\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"runtime\"\n)\n\n// Lock places an advisory write lock on the file, blocking until it can be locked.\n//\n// If Lock returns nil, no other process will be able to place a read or write lock on the file until\n// this process exits, closes f, or calls Unlock on it.\nfunc Lock(path string) (file *os.File, err error) {\n\treturn commonlock(path, writeLock)\n}\n\n// ReadOnlyLock places an advisory read lock on the file, blocking until it can be locked.\n//\n// If ReadOnlyLock returns nil, no other process will be able to place a write lock on\n// the file until this process exits, closes f, or calls Unlock on it.\nfunc ReadOnlyLock(path string) (file *os.File, err error) {\n\treturn commonlock(path, readLock)\n}\n\nfunc commonlock(path string, mode lockType) (file *os.File, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrLockFail, err, file.Close())\n\t\t}\n\t}()\n\n\tif runtime.GOOS == \"windows\" {\n\t\t// LockFileEx does not work on directories, so check what we have first.\n\t\t// If that is a dir, swap out the path for a sidecar file instead (not inside the directory).\n\t\t// Note that this cannot be done in platform specific implementation without moving all the fd Open and Close\n\t\t// logic over there, which is undesirable.\n\t\tif sl, err := os.Stat(path); err == nil && sl.IsDir() {\n\t\t\tpath = path + \".nerdctl.lock\"\n\t\t}\n\t}\n\n\tfile, err = os.Open(path)\n\tif errors.Is(err, os.ErrNotExist) {\n\t\tfile, err = os.OpenFile(path, os.O_RDONLY|os.O_CREATE, privateFilePermission)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = platformSpecificLock(file, mode); err != nil {\n\t\treturn nil, errors.Join(err, file.Close())\n\t}\n\n\treturn file, nil\n}\n\n// Unlock removes an advisory lock placed on f by this process.\nfunc Unlock(lock *os.File) error {\n\tif lock == nil {\n\t\treturn ErrLockIsNil\n\t}\n\n\tif err := errors.Join(platformSpecificUnlock(lock), lock.Close()); err != nil {\n\t\treturn errors.Join(ErrUnlockFail, err)\n\t}\n\n\treturn nil\n}\n\n// WithLock executes the provided function after placing a write lock on `path`.\n// The lock is released once the function has been run, regardless of outcome.\nfunc WithLock(path string, function func() error) (err error) {\n\tfile, err := Lock(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\terr = errors.Join(Unlock(file), err)\n\t}()\n\n\treturn function()\n}\n\n// WithReadOnlyLock executes the provided function after placing a read lock on `path`.\n// The lock is released once the function has been run, regardless of outcome.\nfunc WithReadOnlyLock(path string, function func() error) (err error) {\n\tfile, err := ReadOnlyLock(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\terr = errors.Join(Unlock(file), err)\n\t}()\n\n\treturn function()\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/lock_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem_test\n\nimport (\n\t\"os\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nconst (\n\tmainroutine1 uint32 = 11\n\tmainroutine2 uint32 = 12\n\troutine1     uint32 = 1\n\troutine2     uint32 = 2\n\troutine3     uint32 = 3\n)\n\nfunc TestLockDir(t *testing.T) {\n\tt.Parallel()\n\n\ttempDir := t.TempDir()\n\t// Lock acquisition\n\tfile, err := filesystem.Lock(tempDir)\n\tassert.NilError(t, err, \"acquiring a lock should succeed\")\n\terr = filesystem.Unlock(file)\n\tassert.NilError(t, err, \"releasing a lock should succeed\")\n\n\tfile, err = filesystem.ReadOnlyLock(tempDir)\n\tassert.NilError(t, err, \"acquiring a read-only lock should succeed\")\n\tfile2, err := filesystem.ReadOnlyLock(tempDir)\n\tassert.NilError(t, err, \"acquiring another read-only lock should succeed\")\n\terr = filesystem.Unlock(file)\n\tassert.NilError(t, err, \"releasing a read-only lock should succeed\")\n\terr = filesystem.Unlock(file2)\n\tassert.NilError(t, err, \"releasing another read-only lock should succeed\")\n}\n\nfunc TestLockFile(t *testing.T) {\n\tt.Parallel()\n\n\ttempDir := t.TempDir()\n\tlock, err := os.CreateTemp(tempDir, \"lockfile\")\n\tassert.NilError(t, err, \"creating temp file should succeed\")\n\tdefer lock.Close()\n\t// Lock acquisition\n\tfile, err := filesystem.Lock(lock.Name())\n\tassert.NilError(t, err, \"acquiring a lock should succeed\")\n\terr = filesystem.Unlock(file)\n\tassert.NilError(t, err, \"releasing a lock should succeed\")\n\n\tfile, err = filesystem.ReadOnlyLock(lock.Name())\n\tassert.NilError(t, err, \"acquiring a read-only lock should succeed\")\n\tfile2, err := filesystem.ReadOnlyLock(lock.Name())\n\tassert.NilError(t, err, \"acquiring another read-only lock should succeed\")\n\terr = filesystem.Unlock(file)\n\tassert.NilError(t, err, \"releasing a read-only lock should succeed\")\n\terr = filesystem.Unlock(file2)\n\tassert.NilError(t, err, \"releasing another read-only lock should succeed\")\n}\n\nfunc TestLockWriteConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tvar waitGroup sync.WaitGroup\n\n\tvar concurrentKey uint32\n\n\ttempDir := t.TempDir()\n\n\twaitGroup.Add(2)\n\n\t// Start a lock, set the key, sleep 1s and confirm the key is still the same\n\tgo func() {\n\t\tdefer waitGroup.Done()\n\n\t\tlErr := filesystem.WithLock(tempDir, func() error {\n\t\t\tatomic.StoreUint32(&concurrentKey, routine1)\n\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), routine1)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\t// Wait 0.5s, start another lock, set the key, sleep 1s and confirm the key is still the same\n\tgo func() {\n\t\tdefer waitGroup.Done()\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\tlErr := filesystem.WithLock(tempDir, func() error {\n\t\t\tatomic.StoreUint32(&concurrentKey, routine2)\n\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), routine2)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\t// Start a lock, set the key, wait 1s, confirm the key is still the same\n\tlErr := filesystem.WithLock(tempDir, func() error {\n\t\tatomic.StoreUint32(&concurrentKey, mainroutine1)\n\n\t\ttime.Sleep(1 * time.Second)\n\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), mainroutine1)\n\n\t\treturn nil\n\t})\n\tassert.NilError(t, lErr, \"locking should not error\")\n\n\t// Wait 0.75s, start a lock, set the key, sleep 1s, confirm the key is unchanged\n\ttime.Sleep(750 * time.Millisecond)\n\n\tlErr = filesystem.WithLock(tempDir, func() error {\n\t\tatomic.StoreUint32(&concurrentKey, mainroutine2)\n\n\t\ttime.Sleep(1 * time.Second)\n\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), mainroutine2)\n\n\t\treturn nil\n\t})\n\n\tassert.NilError(t, lErr, \"locking should not error\")\n\n\twaitGroup.Wait()\n}\n\nfunc TestLockMultiRead(t *testing.T) {\n\tt.Parallel()\n\n\tvar waitGroup sync.WaitGroup\n\n\tvar concurrentKey uint32\n\n\ttempDir := t.TempDir()\n\n\twaitGroup.Add(3)\n\n\t// Start a readonly lock immediately\n\t// Then wait 1s inside the lock - confirm the key got changed by the second read routine\n\tgo func() {\n\t\tt.Log(\"Entering routine 1\")\n\n\t\tdefer waitGroup.Done()\n\n\t\tlErr := filesystem.WithReadOnlyLock(tempDir, func() error {\n\t\t\tt.Log(\"Entering routine 1 read lock\")\n\n\t\t\tatomic.StoreUint32(&concurrentKey, routine1)\n\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), routine2)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\t// Wait 0.5s before locking, then change the key\n\tgo func() {\n\t\tt.Log(\"Entering routine 2\")\n\n\t\tdefer waitGroup.Done()\n\n\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\tlErr := filesystem.WithReadOnlyLock(tempDir, func() error {\n\t\t\tt.Log(\"Entering routine 2 read lock\")\n\n\t\t\tatomic.StoreUint32(&concurrentKey, routine2)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\ttime.Sleep(50 * time.Millisecond)\n\t// Start a write lock, confirm we have waited for the read locks to finish, change the key\n\tgo func() {\n\t\tt.Log(\"Entering routine 3\")\n\n\t\tdefer waitGroup.Done()\n\n\t\tlErr := filesystem.WithLock(tempDir, func() error {\n\t\t\tt.Log(\"Entering routine 3 write lock\")\n\t\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), routine2)\n\n\t\t\tatomic.StoreUint32(&concurrentKey, routine3)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\twaitGroup.Wait()\n}\n\nfunc TestLockWriteBlocksRead(t *testing.T) {\n\tt.Parallel()\n\n\tvar waitGroup sync.WaitGroup\n\n\tvar concurrentKey uint32\n\n\ttempDir := t.TempDir()\n\n\twaitGroup.Add(2)\n\n\t// Start a lock, set the key, sleep 1s and confirm the key is still the same\n\tgo func() {\n\t\tdefer waitGroup.Done()\n\n\t\tlErr := filesystem.WithLock(tempDir, func() error {\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\tatomic.StoreUint32(&concurrentKey, routine1)\n\n\t\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), routine1)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\ttime.Sleep(50 * time.Millisecond)\n\n\t// Start a readonly lock immediately\n\t// Confirm the key has been set by the write lock\n\tgo func() {\n\t\tdefer waitGroup.Done()\n\n\t\tlErr := filesystem.WithReadOnlyLock(tempDir, func() error {\n\t\t\tassert.Equal(t, atomic.LoadUint32(&concurrentKey), routine1)\n\n\t\t\treturn nil\n\t\t})\n\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\twaitGroup.Wait()\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/lock_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Portions from internal go\n//\n// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n//\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:LICENSE\n\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/cmd/go/internal/lockedfile/internal/filelock/filelock_unix.go\n\npackage filesystem\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"syscall\"\n)\n\ntype lockType int16\n\nconst (\n\treadLock  lockType = syscall.LOCK_SH\n\twriteLock lockType = syscall.LOCK_EX\n)\n\nfunc platformSpecificLock(file *os.File, lockType lockType) error {\n\tvar err error\n\n\tfor {\n\t\terr = syscall.Flock(int(file.Fd()), int(lockType))\n\t\tif !errors.Is(err, syscall.EINTR) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc platformSpecificUnlock(file *os.File) error {\n\treturn syscall.Flock(int(file.Fd()), syscall.LOCK_UN)\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/lock_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Portions from internal go\n//\n// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n//\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:LICENSE\n\n// https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/cmd/go/internal/lockedfile/internal/filelock/filelock_windows.go\n\npackage filesystem\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\ntype lockType uint32\n\nconst (\n\t// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx\n\treadLock  lockType = 0\n\twriteLock lockType = windows.LOCKFILE_EXCLUSIVE_LOCK\n\n\treserved = 0\n\tallBytes = ^uint32(0)\n)\n\nfunc platformSpecificLock(file *os.File, lockType lockType) error {\n\treturn windows.LockFileEx(\n\t\twindows.Handle(file.Fd()),\n\t\tuint32(lockType),\n\t\treserved,\n\t\tallBytes,\n\t\tallBytes,\n\t\tnew(windows.Overlapped))\n}\n\nfunc platformSpecificUnlock(file *os.File) error {\n\treturn windows.UnlockFileEx(windows.Handle(file.Fd()), reserved, allBytes, allBytes, new(windows.Overlapped))\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/os.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"errors\"\n\t\"os\"\n)\n\nfunc ReadFile(filename string) (data []byte, err error) {\n\tif err = ensureRecovery(filename); err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err = os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrFilesystemFailure, err)\n\t}\n\n\treturn data, nil\n}\n\nfunc Stat(filename string) (os.FileInfo, error) {\n\tif err := ensureRecovery(filename); err != nil {\n\t\treturn nil, errors.Join(ErrFilesystemFailure, err)\n\t}\n\n\treturn os.Stat(filename)\n}\n\n// WriteFile implements an atomic and durable alternative to os.WriteFile that does not change inodes (unlike the usual\n// approach on atomic writes that relies on renaming files).\nfunc WriteFile(filename string, data []byte, perm os.FileMode) error {\n\t_, err := WriteFileWithRollback(filename, data, perm)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/path.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"errors\"\n\t\"strings\"\n)\n\nvar (\n\terrForbiddenChars     = errors.New(\"forbidden characters in path component\")\n\terrForbiddenKeywords  = errors.New(\"forbidden keywords in path component\")\n\terrInvalidPathTooLong = errors.New(\"path component must be shorter than 256 characters\")\n\terrInvalidPathEmpty   = errors.New(\"path component cannot be empty\")\n)\n\n// ValidatePathComponent will enforce os specific filename restrictions on a single path component.\nfunc ValidatePathComponent(pathComponent string) error {\n\t// https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits\n\tif len(pathComponent) > pathComponentMaxLength {\n\t\treturn errors.Join(ErrInvalidPath, errInvalidPathTooLong)\n\t}\n\n\tif strings.TrimSpace(pathComponent) == \"\" {\n\t\treturn errors.Join(ErrInvalidPath, errInvalidPathEmpty)\n\t}\n\n\tif err := validatePlatformSpecific(pathComponent); err != nil {\n\t\treturn errors.Join(ErrInvalidPath, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/path_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem_test\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nfunc TestFilesystemRestrictions(t *testing.T) {\n\tt.Parallel()\n\n\tinvalid := []string{\n\t\t\"/\",\n\t\t\"/start\",\n\t\t\"mid/dle\",\n\t\t\"end/\",\n\t\t\".\",\n\t\t\"..\",\n\t\t\"\",\n\t\tfmt.Sprintf(\"A%0255s\", \"A\"),\n\t}\n\n\tvalid := []string{\n\t\tfmt.Sprintf(\"A%0254s\", \"A\"),\n\t\t\"test\",\n\t\t\"test-hyphen\",\n\t\t\".start.dot\",\n\t\t\"mid.dot\",\n\t\t\"∞\",\n\t}\n\n\tif runtime.GOOS == \"windows\" {\n\t\tinvalid = append(invalid, []string{\n\t\t\t\"\\\\start\",\n\t\t\t\"mid\\\\dle\",\n\t\t\t\"end\\\\\",\n\t\t\t\"\\\\\",\n\t\t\t\"\\\\.\",\n\t\t\t\"com².whatever\",\n\t\t\t\"lpT2\",\n\t\t\t\"Prn.\",\n\t\t\t\"nUl\",\n\t\t\t\"AUX\",\n\t\t\t\"A<A\",\n\t\t\t\"A>A\",\n\t\t\t\"A:A\",\n\t\t\t\"A\\\"A\",\n\t\t\t\"A|A\",\n\t\t\t\"A?A\",\n\t\t\t\"A*A\",\n\t\t\t\"end.dot.\",\n\t\t\t\"end.space \",\n\t\t}...)\n\t}\n\n\tfor _, v := range invalid {\n\t\terr := filesystem.ValidatePathComponent(v)\n\t\tassert.ErrorIs(t, err, filesystem.ErrInvalidPath, v)\n\t}\n\n\tfor _, v := range valid {\n\t\terr := filesystem.ValidatePathComponent(v)\n\t\tassert.NilError(t, err, v)\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/path_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n)\n\n// Note that Darwin has different restrictions on colons.\n// https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names\nvar (\n\tdisallowedKeywords = regexp.MustCompile(`^([.]|[.][.])$`)\n\treservedCharacters = regexp.MustCompile(`[\\x{0}/]`)\n)\n\nfunc validatePlatformSpecific(pathComponent string) error {\n\tif reservedCharacters.MatchString(pathComponent) {\n\t\treturn fmt.Errorf(\"%w: %q (%q)\", errForbiddenChars, pathComponent, reservedCharacters)\n\t}\n\n\tif disallowedKeywords.MatchString(pathComponent) {\n\t\treturn fmt.Errorf(\"%w: %q (%q)\", errForbiddenKeywords, pathComponent, disallowedKeywords)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/path_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n)\n\n// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file\n// https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names\nvar (\n\tdisallowedKeywords = regexp.MustCompile(`(?i)^(con|prn|nul|aux|com[1-9¹²³]|lpt[1-9¹²³])([.].*)?$`)\n\treservedCharacters = regexp.MustCompile(`[\\x{0}-\\x{1f}<>:\"/\\\\|?*]`)\n\n\terrNoEndingSpaceDot = errors.New(\"component cannot end with a space or dot\")\n)\n\nfunc validatePlatformSpecific(pathComponent string) error {\n\tif reservedCharacters.MatchString(pathComponent) {\n\t\treturn fmt.Errorf(\"%w: %q (%q)\", errForbiddenChars, pathComponent, reservedCharacters)\n\t}\n\n\tif disallowedKeywords.MatchString(pathComponent) {\n\t\treturn fmt.Errorf(\"%w: %q (%q)\", errForbiddenKeywords, pathComponent, disallowedKeywords)\n\t}\n\n\tif pathComponent[len(pathComponent)-1:] == \".\" || pathComponent[len(pathComponent)-1:] == \" \" {\n\t\treturn fmt.Errorf(\"%w: %q\", errNoEndingSpaceDot, pathComponent)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/umask.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"math\"\n\t\"sync\"\n)\n\nvar (\n\tmu    sync.Mutex\n\tcMask = -1\n)\n\n// GetUmask retrieves the current umask.\nfunc GetUmask() uint32 {\n\tif cMask != -1 {\n\t\treturn uint32(cMask)\n\t}\n\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\tcMask = umask(0)\n\n\t// FIXME: one day... we will get rid of 32 bits arm...\n\tcMask64 := int64(cMask)\n\tif cMask64 > math.MaxUint32 || cMask < 0 {\n\t\tpanic(\"currently set user umask is out of range\")\n\t}\n\n\t_ = umask(cMask)\n\n\treturn uint32(cMask)\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/umask_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem_test\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nfunc TestUmask(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"windows does not have a unix-style umask\")\n\t}\n\n\tuserHostReportedUmask, err := exec.Command(\"sh\", \"-c\", \"umask\").CombinedOutput()\n\tassert.NilError(t, err, fmt.Sprintf(\n\t\t\"umask command should succeed (output: %s)\",\n\t\tuserHostReportedUmask,\n\t))\n\texpectedUmask, err := strconv.ParseInt(strings.TrimSpace(string(userHostReportedUmask)), 8, 0)\n\tassert.NilError(\n\t\tt,\n\t\terr,\n\t\tfmt.Sprintf(\"umask command should have returned parsable output (was: %s)\", userHostReportedUmask),\n\t)\n\n\tuserMask := filesystem.GetUmask()\n\tassert.Equal(t, expectedUmask, int64(userMask), \"system reported umask and implementation umask are the same\")\n\n\tuserHostReportedUmask, err = exec.Command(\"sh\", \"-c\", \"umask\").CombinedOutput()\n\tassert.NilError(t, err)\n\texpectedUmask, err = strconv.ParseInt(strings.TrimSpace(string(userHostReportedUmask)), 8, 0)\n\tassert.NilError(t, err)\n\n\tassert.Equal(t, expectedUmask, int64(userMask), \"system reported umask has not changed\")\n}\n\nfunc TestUmaskConcurrent(t *testing.T) {\n\tt.Parallel()\n\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"windows does not have a unix-style umask\")\n\t}\n\n\tuserHostReportedUmask, err := exec.Command(\"sh\", \"-c\", \"umask\").Output()\n\tassert.NilError(t, err)\n\texpectedUmask, err := strconv.ParseInt(strings.TrimSpace(string(userHostReportedUmask)), 8, 0)\n\tassert.NilError(t, err)\n\n\tvar counter int32 = 100\n\n\tch := make(chan uint32)\n\n\tfor range counter {\n\t\tgo func(ch chan uint32) {\n\t\t\tu := filesystem.GetUmask()\n\t\t\tif atomic.AddInt32(&counter, -1) == 0 {\n\t\t\t\tch <- u\n\t\t\t}\n\t\t}(ch)\n\t}\n\n\tret := <-ch\n\tassert.Equal(t, expectedUmask, int64(ret))\n\tuserHostReportedUmask, err = exec.Command(\"sh\", \"-c\", \"umask\").Output()\n\tassert.NilError(t, err)\n\tnewUmask, err := strconv.ParseInt(strings.TrimSpace(string(userHostReportedUmask)), 8, 0)\n\tassert.NilError(t, err)\n\tassert.Equal(t, newUmask, expectedUmask, \"system reported umask has not changed\")\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/umask_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport \"syscall\"\n\nfunc umask(mask int) int {\n\treturn syscall.Umask(mask)\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/umask_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nfunc umask(_ int) int {\n\treturn 0\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/writefile_rename.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\n// WriteFileWithRename is a drop-in replacement for os.WriteFile, with the same signature and almost identical behavior\n// (see note below on inodes).\n// Unlike os.WriteFile, it does provide extra guarantees:\n// - Atomicity (provided by rename - *mostly* atomic, except on OS crash, where rename behavior is undefined)\n// - Durability (sync-ed)\n// Note that:\n// - this does not provide Isolation (a locking mechanism needs to be used independently to enforce that)\n// - Consistency is orthogonal here, and high-level operations that expect it across a set of unrelated ops need to\n// implement locking, rollback, and disaster recovery\n// - this will change inode in case the file already exist - therefore, there are cases where this cannot be used\n// (specifically if a file is mounted inside a container) - these are the exception though, and in almost all cases,\n// this method should be preferred over os.WriteFile\n// Finally note that we do not do anything smart wrt symlinks.\n// User is expected to resolve symlink for the destination before calling this if needed.\nfunc WriteFileWithRename(filename string, data []byte, perm os.FileMode) error {\n\treturn CopyToFileWithRename(filename, bytes.NewBuffer(data), int64(len(data)), perm, time.Time{})\n}\n\n// CopyToFileWithRename is an atomic wrapper around io.Copy(file, reader). See notes above in WriteFile for details.\nfunc CopyToFileWithRename(filename string, reader io.Reader, dataSize int64, perm os.FileMode, mTime time.Time) (err error) {\n\tvar tmpFile *os.File\n\tmustClose := true\n\n\tdefer func() {\n\t\t// Close if we have not already\n\t\tif mustClose {\n\t\t\terr = errors.Join(err, tmpFile.Close())\n\t\t}\n\n\t\t// On error, wrap it into ErrFilesystemFailure (and ensure we don't leak temp files)\n\t\tif err != nil {\n\t\t\tif tmpFile != nil {\n\t\t\t\terr = errors.Join(err, os.Remove(tmpFile.Name()))\n\t\t\t}\n\t\t\terr = errors.Join(ErrFilesystemFailure, err)\n\t\t}\n\t}()\n\n\t// Ensure we set permission honoring umask to be compatible with os.WriteFile\n\tperm = (^os.FileMode(GetUmask())) & perm\n\n\t// Create a new temp file.\n\ttmpFile, err = os.CreateTemp(filepath.Dir(filename), \".tmp-\"+filepath.Base(filename))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Set permissions\n\tif err = os.Chmod(tmpFile.Name(), perm); err != nil {\n\t\treturn err\n\t}\n\n\t// Write data\n\tn, err := ioCopy(tmpFile, reader)\n\tif err == nil && n < dataSize {\n\t\treturn io.ErrShortWrite\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Sync it, ensuring the data cannot be lost\n\tif err = tmpFile.Sync(); err != nil {\n\t\treturn err\n\t}\n\n\t// Close\n\tif err = tmpFile.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tmustClose = false\n\n\t// Set mtime if requested\n\tif !mTime.IsZero() {\n\t\tif err = os.Chtimes(tmpFile.Name(), mTime, mTime); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Rename to final destination (hopefully on the same volume)\n\t// NOTE: this is atomic in *most* cases - it might not be if the OS crashes.\n\treturn os.Rename(tmpFile.Name(), filename)\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/writefile_rollback.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage filesystem\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os\"\n\t\"time\"\n)\n\n// WriteFileWithRollback implements an atomic and durable file write operation with rollback.\n// The rollback callback may be called by higher-level operations in case there is a need to\n// revert changes as part of a more complex, multi-prong operation.\n// Note that with or without rollback, WriteFileWithRollback does ensure disaster recovery.\nfunc WriteFileWithRollback(filename string, data []byte, perm os.FileMode) (rollback func() error, err error) {\n\t// Ensure there are no interrupted operations (leftover marker file and backup), or restore them if need be.\n\t// If this is failing, we are dead in the water.\n\tif err = ensureRecovery(filename); err != nil {\n\t\treturn nil, errors.Join(ErrFilesystemFailure, err)\n\t}\n\n\t// On error, call recovery to rollback changes.\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrFilesystemFailure, err, ensureRecovery(filename))\n\t\t}\n\t}()\n\n\t// If the file does not exist\n\tmarkerData := \"\"\n\tif _, err = os.Stat(filename); err != nil {\n\t\t// Any error but does not exist is a hard error.\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn nil, err\n\t\t}\n\t\t// Otherwise, rollback and marker is \"remove\"\n\t\tmarkerData = removeMarker\n\t\trollback = func() error {\n\t\t\treturn os.Remove(filename)\n\t\t}\n\t} else {\n\t\t// Destination exists.\n\t\t// Rollback will be: restore data from the backup\n\t\trollback = func() error {\n\t\t\treturn backupRestore(filename)\n\t\t}\n\t}\n\n\t// Make sure no leftover backup file is here\n\t// Note: this happens after a successful write. Generally not a problem, except if the file is then deleted,\n\t// then written to again, and that second write would fail.\n\t_ = backupRemove(filename)\n\n\t// Create the marker. Failure to do so is a hard error.\n\tif err = markerCreate(filename, markerData); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If the file exists, we need to back it up.\n\tif markerData == \"\" {\n\t\t// Back it up now. Remove on failure.\n\t\tif err = backupSave(filename); err != nil {\n\t\t\t_ = backupRemove(filename)\n\t\t\t_ = markerRemove(filename)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Now, write the content to the destination.\n\tif err = fileWrite(bytes.NewReader(data), int64(len(data)), filename, perm, time.Time{}); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Remove the marker.\n\tif err = markerRemove(filename); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// On success, return the rollback\n\treturn rollback, nil\n}\n"
  },
  {
    "path": "pkg/internal/filesystem/writefile_rollback_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n//nolint:forbidigo\npackage filesystem\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestRollbackForNonExistentFile(t *testing.T) {\n\t// Test that calling the rollback after writing to a new existent file does remove the file\n\n\t// Create file\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"non-existent-file\")\n\n\t// Write to it and check that this went through\n\trollback, err := WriteFileWithRollback(fp, []byte(\"new content\"), 0o600)\n\tassert.NilError(t, err)\n\tcn, _ := os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"new content\")\n\n\t// Roll it back and check it has been removed.\n\terr = rollback()\n\tassert.NilError(t, err)\n\t_, err = os.ReadFile(fp)\n\tassert.Assert(t, os.IsNotExist(err))\n}\n\nfunc TestRollbackForPreexistingFile(t *testing.T) {\n\t// Test that calling the rollback after writing to a pre-existing file does restore the original\n\n\t// Create a file with pre-existing content\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"pre-existing-file\")\n\t_ = os.WriteFile(fp, []byte(\"original content\"), 0o600)\n\tcn, _ := os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"original content\")\n\n\t// Write to it and check that this went through\n\trollback, err := WriteFileWithRollback(fp, []byte(\"updated content\"), 0o600)\n\tassert.NilError(t, err)\n\n\tcn, _ = os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"updated content\")\n\n\t// Roll it back and check we have the original\n\terr = rollback()\n\tassert.NilError(t, err)\n\tcn, _ = os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"original content\")\n}\n\nfunc TestBackupFailure(t *testing.T) {\n\t// Test that if backup is failing, a pre-existing file is restored to its original value.\n\n\t// Create a file with pre-existing content\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"pre-existing-file\")\n\t_ = os.WriteFile(fp, []byte(\"original content\"), 0o600)\n\tcn, _ := os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"original content\")\n\n\tfakeError := errors.New(\"fake error\")\n\t// Override ioCopy to simulate an error creating the backup\n\tioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) {\n\t\treturn 0, fakeError\n\t}\n\n\t// Write. Check that we still have the original.\n\trollback, err := WriteFileWithRollback(fp, []byte(\"updated content\"), 0o600)\n\tassert.ErrorIs(t, err, fakeError)\n\tassert.Assert(t, rollback == nil)\n\tcn, _ = os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"original content\")\n}\n\nfunc TestWriteFailure(t *testing.T) {\n\t// Test that if write to a non-existent file is failing, the file is deleted.\n\n\t// Create file\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"non-existent-file\")\n\n\tfakeError := errors.New(\"fake error\")\n\t// Override ioCopy to simulate an error while writing to the destination\n\t// Note: since the file does not exist, there will be no backup\n\tioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) {\n\t\treturn 0, fakeError\n\t}\n\n\t// Write. Check that the file has been removed\n\trollback, err := WriteFileWithRollback(fp, []byte(\"update\"), 0o600)\n\tassert.ErrorIs(t, err, fakeError)\n\tassert.Assert(t, rollback == nil)\n\t_, err = os.ReadFile(fp)\n\tassert.Assert(t, os.IsNotExist(err))\n\n\t// Restore io copy\n\tioCopy = io.Copy\n}\n\nfunc TestShortWriteFailure(t *testing.T) {\n\t// Test that a write failing to write all content to a non-existent file will delete the file.\n\n\t// Create file\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"non-existent-file\")\n\n\t// Override ioCopy to simulate a short write\n\tioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) {\n\t\treturn 1, nil\n\t}\n\n\t// Write. Check that we still have the original.\n\trollback, err := WriteFileWithRollback(fp, []byte(\"update\"), 0o600)\n\tassert.ErrorIs(t, err, io.ErrShortWrite)\n\tassert.Assert(t, rollback == nil)\n\t_, err = os.ReadFile(fp)\n\tassert.Assert(t, os.IsNotExist(err))\n\n\t// Restore io copy\n\tioCopy = io.Copy\n}\n\nfunc TestDisasterRecoveryFromBackup(t *testing.T) {\n\t// Test that a file that has left-over backup and marker will get restored to its original content\n\n\t// Create file\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"pre-existing-file\")\n\t_ = os.WriteFile(fp, []byte(\"original content\"), 0o600)\n\n\t// Artificially create leftover marker\n\t_ = markerCreate(fp, \"\")\n\t// Artificially create leftover backup\n\t_ = backupSave(fp)\n\n\t// Pork the file, to simulate interrupted write with leftover marker and backup\n\t_ = os.WriteFile(fp, []byte(\"porked\"), 0o600)\n\n\t// Now, see that disaster recovery got the backup\n\terr := ensureRecovery(fp)\n\tassert.NilError(t, err)\n\n\tcn, _ := os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"original content\")\n}\n\nfunc TestDisasterRecoveryNoBackup1(t *testing.T) {\n\t// Test that a previously non-existent file with a marker left-over will get deleted\n\n\t// Create file\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"non-existent-file\")\n\n\t// Artificially create leftover marker\n\t_ = markerCreate(fp, removeMarker)\n\n\t// Pork the file. mtime will be > marker mtime, meaning we expect the file to get deleted\n\t_ = os.WriteFile(fp, []byte(\"porked\"), 0o600)\n\n\terr := ensureRecovery(fp)\n\tassert.NilError(t, err)\n\n\t_, err = os.ReadFile(fp)\n\tassert.Assert(t, os.IsNotExist(err))\n}\n\nfunc TestDisasterRecoveryNoBackup2(t *testing.T) {\n\t// Test that a file with a more recent marker leftover and no backup will be left untouched.\n\n\t// Create file\n\tdir := t.TempDir()\n\tfp := filepath.Join(dir, \"pre-existing-file\")\n\t_ = os.WriteFile(fp, []byte(\"original content\"), 0o600)\n\n\t// Artificially create leftover marker\n\t_ = markerCreate(fp, \"\")\n\n\terr := ensureRecovery(fp)\n\tassert.NilError(t, err)\n\n\tcn, _ := os.ReadFile(fp)\n\tassert.Equal(t, string(cn), \"original content\")\n}\n"
  },
  {
    "path": "pkg/ipcutil/ipcutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipcutil\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n)\n\ntype IPCMode string\n\ntype IPC struct {\n\tMode IPCMode `json:\"mode,omitempty\"`\n\t// VictimContainer is only used when mode is container\n\tVictimContainerID *string `json:\"victimContainerId,omitempty\"`\n\n\t// HostShmPath is only used when mode is shareable\n\tHostShmPath *string `json:\"hostShmPath,omitempty\"`\n\n\t// ShmSize is only used when mode is private or shareable\n\t// Devshm size in bytes\n\tShmSize string `json:\"shmSize,omitempty\"`\n}\n\nconst (\n\tPrivate   IPCMode = \"private\"\n\tHost      IPCMode = \"host\"\n\tShareable IPCMode = \"shareable\"\n\tContainer IPCMode = \"container\"\n)\n\n// DetectFlags detects IPC mode from the given ipc string and shmSize string.\n// If ipc is empty, it returns IPC{Mode: Private}.\nfunc DetectFlags(ctx context.Context, client *containerd.Client, stateDir string, ipc string, shmSize string) (IPC, error) {\n\tvar res IPC\n\tres.ShmSize = shmSize\n\tswitch ipc {\n\tcase \"\", \"private\":\n\t\tres.Mode = Private\n\tcase \"host\":\n\t\tres.Mode = Host\n\tcase \"shareable\":\n\t\tres.Mode = Shareable\n\t\tshmPath := filepath.Join(stateDir, \"shm\")\n\t\tres.HostShmPath = &shmPath\n\tdefault: // container:<id|name>\n\t\tres.Mode = Container\n\t\tparsed := strings.Split(ipc, \":\")\n\t\tif len(parsed) < 2 || parsed[0] != \"container\" {\n\t\t\treturn res, fmt.Errorf(\"invalid ipc namespace. Set --ipc=[host|container:<name|id>\")\n\t\t}\n\n\t\tcontainerName := parsed[1]\n\t\twalker := &containerwalker.ContainerWalker{\n\t\t\tClient: client,\n\t\t\tOnFound: func(ctx context.Context, found containerwalker.Found) error {\n\t\t\t\tif found.MatchCount > 1 {\n\t\t\t\t\treturn fmt.Errorf(\"multiple IDs found with provided prefix: %s\", found.Req)\n\t\t\t\t}\n\t\t\t\tvictimContainerID := found.Container.ID()\n\t\t\t\tres.VictimContainerID = &victimContainerID\n\n\t\t\t\treturn nil\n\t\t\t},\n\t\t}\n\t\tmatchedCount, err := walker.Walk(ctx, containerName)\n\t\tif err != nil {\n\t\t\treturn res, err\n\t\t}\n\t\tif matchedCount < 1 {\n\t\t\treturn res, fmt.Errorf(\"no such container: %s\", containerName)\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\n// EncodeIPCLabel encodes IPC spec into a label.\nfunc EncodeIPCLabel(ipc IPC) (string, error) {\n\tif ipc.Mode == \"\" {\n\t\treturn \"\", nil\n\t}\n\tb, err := json.Marshal(ipc)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), nil\n}\n\n// DecodeIPCLabel decodes IPC spec from a label.\n// For backward compatibility, if ipcLabel is empty, it returns IPC{Mode: Private}.\nfunc DecodeIPCLabel(ipcLabel string) (IPC, error) {\n\tif ipcLabel == \"\" {\n\t\treturn IPC{\n\t\t\tMode: Private,\n\t\t}, nil\n\t}\n\n\tvar ipc IPC\n\tif err := json.Unmarshal([]byte(ipcLabel), &ipc); err != nil {\n\t\treturn IPC{}, err\n\t}\n\treturn ipc, nil\n}\n\n// GenerateIPCOpts generates IPC spec opts from the given IPC.\nfunc GenerateIPCOpts(ctx context.Context, ipc IPC, client *containerd.Client) ([]oci.SpecOpts, error) {\n\topts := make([]oci.SpecOpts, 0)\n\n\tswitch ipc.Mode {\n\tcase Private:\n\t\t// If nothing is specified, or if private, default to normal behavior\n\t\tif len(ipc.ShmSize) > 0 {\n\t\t\tshmBytes, err := units.RAMInBytes(ipc.ShmSize)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\topts = append(opts, oci.WithDevShmSize(shmBytes/1024))\n\t\t}\n\tcase Host:\n\t\topts = append(opts, withBindMountHostIPC)\n\t\tif runtime.GOOS != \"windows\" {\n\t\t\topts = append(opts, oci.WithHostNamespace(specs.IPCNamespace))\n\t\t}\n\tcase Shareable:\n\t\tif ipc.HostShmPath == nil {\n\t\t\treturn nil, errors.New(\"ipc mode is shareable, but host shm path is nil\")\n\t\t}\n\t\terr := makeShareableDevshm(*ipc.HostShmPath, ipc.ShmSize)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, withBindMountHostOtherSourceIPC(*ipc.HostShmPath))\n\tcase Container:\n\t\tif ipc.VictimContainerID == nil {\n\t\t\treturn nil, errors.New(\"ipc mode is container, but victim container id is nil\")\n\t\t}\n\t\ttargetCon, err := client.LoadContainer(ctx, *ipc.VictimContainerID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttask, err := targetCon.Task(ctx, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tstatus, err := task.Status(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif status.Status != containerd.Running {\n\t\t\treturn nil, fmt.Errorf(\"shared container is not running\")\n\t\t}\n\n\t\ttargetConLabels, err := targetCon.Labels(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttargetConIPC, err := DecodeIPCLabel(targetConLabels[labels.IPC])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif targetConIPC.Mode == Host {\n\t\t\topts = append(opts, oci.WithHostNamespace(specs.IPCNamespace))\n\t\t\topts = append(opts, withBindMountHostIPC)\n\t\t\treturn opts, nil\n\t\t} else if targetConIPC.Mode != Shareable {\n\t\t\treturn nil, errors.New(\"victim container's ipc mode is not shareable\")\n\t\t}\n\n\t\tif targetConIPC.HostShmPath == nil {\n\t\t\treturn nil, errors.New(\"victim container's host shm path is nil\")\n\t\t}\n\n\t\topts = append(opts, withBindMountHostOtherSourceIPC(*targetConIPC.HostShmPath))\n\t\tns := specs.LinuxNamespace{\n\t\t\tType: specs.IPCNamespace,\n\t\t\tPath: fmt.Sprintf(\"/proc/%d/ns/ipc\", task.Pid()),\n\t\t}\n\t\topts = append(opts, oci.WithLinuxNamespace(ns))\n\t}\n\n\treturn opts, nil\n}\n\n// WithBindMountHostOtherSourceIPC replaces /dev/shm mount with rbind by the given path on host\nfunc withBindMountHostOtherSourceIPC(source string) oci.SpecOpts {\n\treturn func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\t\tfor i, m := range s.Mounts {\n\t\t\tp := path.Clean(m.Destination)\n\t\t\tif p == \"/dev/shm\" {\n\t\t\t\ts.Mounts[i] = specs.Mount{\n\t\t\t\t\tType:        \"bind\",\n\t\t\t\t\tDestination: p,\n\t\t\t\t\tSource:      source,\n\t\t\t\t\tOptions:     []string{\"rbind\", \"nosuid\", \"noexec\", \"nodev\"},\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithBindMountHostIPC replaces /dev/shm and /dev/mqueue mount with rbind.\n// Required for --ipc=host on rootless.\nfunc withBindMountHostIPC(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {\n\tfor i, m := range s.Mounts {\n\t\tswitch p := path.Clean(m.Destination); p {\n\t\tcase \"/dev/shm\", \"/dev/mqueue\":\n\t\t\ts.Mounts[i] = specs.Mount{\n\t\t\t\tDestination: p,\n\t\t\t\tType:        \"bind\",\n\t\t\t\tSource:      p,\n\t\t\t\tOptions:     []string{\"rbind\", \"nosuid\", \"noexec\", \"nodev\"},\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc CleanUp(ipc IPC) error {\n\treturn cleanUpPlatformSpecificIPC(ipc)\n}\n"
  },
  {
    "path": "pkg/ipcutil/ipcutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipcutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/docker/go-units\"\n\t\"golang.org/x/sys/unix\"\n)\n\n// makeShareableDevshm returns devshm directory path on host when there is no error.\nfunc makeShareableDevshm(shmPath, shmSize string) error {\n\tshmproperty := \"mode=1777\"\n\tif len(shmSize) > 0 {\n\t\tshmBytes, err := units.RAMInBytes(shmSize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tshmproperty = fmt.Sprintf(\"%s,size=%d\", shmproperty, shmBytes)\n\t}\n\terr := os.MkdirAll(shmPath, 0700)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = unix.Mount(\"/dev/shm\", shmPath, \"tmpfs\", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), shmproperty)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// cleanUpPlatformSpecificIPC cleans up platform specific IPC.\nfunc cleanUpPlatformSpecificIPC(ipc IPC) error {\n\tif ipc.Mode == Shareable && ipc.HostShmPath != nil {\n\t\terr := unix.Unmount(*ipc.HostShmPath, 0)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = os.RemoveAll(*ipc.HostShmPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/ipcutil/ipcutil_other.go",
    "content": "//go:build !(linux || windows)\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipcutil\n\nimport \"fmt\"\n\n// makeShareableDevshm returns devshm directory path on host when there is no error.\nfunc makeShareableDevshm(shmPath, shmSize string) error {\n\treturn fmt.Errorf(\"unix does not support shareable devshm\")\n}\n\n// cleanUpPlatformSpecificIPC cleans up platform specific IPC.\nfunc cleanUpPlatformSpecificIPC(ipc IPC) error {\n\tif ipc.Mode == Shareable {\n\t\treturn fmt.Errorf(\"unix does not support shareable devshm\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/ipcutil/ipcutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipcutil\n\nimport \"fmt\"\n\n// makeShareableDevshm returns devshm directory path on host when there is no error.\nfunc makeShareableDevshm(shmPath, shmSize string) error {\n\treturn fmt.Errorf(\"windows does not support shareable devshm\")\n}\n\n// cleanUpPlatformSpecificIPC cleans up platform specific IPC.\nfunc cleanUpPlatformSpecificIPC(ipc IPC) error {\n\tif ipc.Mode == Shareable {\n\t\treturn fmt.Errorf(\"windows does not support shareable devshm\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/ipfs/image_ipfs.go",
    "content": "//go:build !no_ipfs\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\t\"github.com/containerd/stargz-snapshotter/ipfs\"\n\tipfsclient \"github.com/containerd/stargz-snapshotter/ipfs/client\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n)\n\nconst ipfsPathEnv = \"IPFS_PATH\"\n\n// EnsureImage pull the specified image from IPFS.\nfunc EnsureImage(ctx context.Context, client *containerd.Client, scheme, ref, ipfsPath string, options types.ImagePullOptions) (*imgutil.EnsuredImage, error) {\n\tswitch options.Mode {\n\tcase \"always\", \"missing\", \"never\":\n\t\t// NOP\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected pull mode: %q\", options.Mode)\n\t}\n\tswitch scheme {\n\tcase \"ipfs\", \"ipns\":\n\t\t// NOP\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected scheme: %q\", scheme)\n\t}\n\n\t// if not `always` pull and given one platform and image found locally, return existing image directly.\n\tif options.Mode != \"always\" && len(options.OCISpecPlatform) == 1 {\n\t\tif res, err := imgutil.GetExistingImage(ctx, client, options.GOptions.Snapshotter, ref, options.OCISpecPlatform[0]); err == nil {\n\t\t\treturn res, nil\n\t\t} else if !errdefs.IsNotFound(err) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif options.Mode == \"never\" {\n\t\treturn nil, fmt.Errorf(\"image %q is not available\", ref)\n\t}\n\tr, err := ipfs.NewResolver(ipfs.ResolverOptions{\n\t\tScheme:   scheme,\n\t\tIPFSPath: lookupIPFSPath(ipfsPath),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn imgutil.PullImage(ctx, client, r, ref, options)\n}\n\n// Push pushes the specified image to IPFS.\nfunc Push(ctx context.Context, client *containerd.Client, rawRef string, layerConvert converter.ConvertFunc, allPlatforms bool, platform []string, ensureImage bool, ipfsPath string) (string, error) {\n\tplatMC, err := platformutil.NewMatchComparer(allPlatforms, platform)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tipath := lookupIPFSPath(ipfsPath)\n\tif ensureImage {\n\t\t// Ensure image contents are fully downloaded\n\t\tlog.G(ctx).Infof(\"ensuring image contents\")\n\t\tif err := ensureContentsOfIPFSImage(ctx, client, rawRef, allPlatforms, platform, ipath); err != nil {\n\t\t\tlog.G(ctx).WithError(err).Warnf(\"failed to ensure the existence of image %q\", rawRef)\n\t\t}\n\t}\n\treturn ipfs.PushWithIPFSPath(ctx, client, rawRef, layerConvert, platMC, &ipath)\n}\n\n// ensureContentsOfIPFSImage ensures that the entire contents of an existing IPFS image are fully downloaded to containerd.\nfunc ensureContentsOfIPFSImage(ctx context.Context, client *containerd.Client, ref string, allPlatforms bool, platform []string, ipfsPath string) error {\n\tiurl, err := ipfsclient.GetIPFSAPIAddress(ipfsPath, \"http\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tplatMC, err := platformutil.NewMatchComparer(allPlatforms, platform)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar img images.Image\n\twalker := &imagewalker.ImageWalker{\n\t\tClient: client,\n\t\tOnFound: func(ctx context.Context, found imagewalker.Found) error {\n\t\t\timg = found.Image\n\t\t\treturn nil\n\t\t},\n\t}\n\tn, err := walker.Walk(ctx, ref)\n\tif err != nil {\n\t\treturn err\n\t} else if n == 0 {\n\t\treturn fmt.Errorf(\"image does not exist: %q\", ref)\n\t} else if n > 1 {\n\t\treturn fmt.Errorf(\"ambiguous reference %q matched %d objects\", ref, n)\n\t}\n\tcs := client.ContentStore()\n\tchildrenHandler := images.ChildrenHandler(cs)\n\tchildrenHandler = images.SetChildrenLabels(cs, childrenHandler)\n\tchildrenHandler = images.FilterPlatforms(childrenHandler, platMC)\n\treturn images.Dispatch(ctx, images.Handlers(\n\t\tremotes.FetchHandler(cs, &fetcher{ipfsclient.New(iurl)}),\n\t\tchildrenHandler,\n\t), nil, img.Target)\n}\n\n// fetcher fetches a file from IPFS\n// TODO: fix github.com/containerd/stargz-snapshotter/ipfs to export this and we should import that\ntype fetcher struct {\n\tipfsclient *ipfsclient.Client\n}\n\nfunc (f *fetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {\n\tcid, err := ipfs.GetCID(desc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toff, size := 0, int(desc.Size)\n\treturn f.ipfsclient.Get(\"/ipfs/\"+cid, &off, &size)\n}\n\n// If IPFS_PATH is specified, this will be used.\n// If not, \"~/.ipfs\" will be used.\n// The behaviour is compatible to kubo: https://github.com/ipfs/go-ipfs-http-client/blob/171fcd55e3b743c38fb9d78a34a3a703ee0b5e89/api.go#L43-L44\n// Optionally takes ipfsPath string having the highest priority.\nfunc lookupIPFSPath(ipfsPath string) string {\n\tvar ipath string\n\tif idir := os.Getenv(ipfsPathEnv); idir != \"\" {\n\t\tipath = idir\n\t}\n\tif ipath == \"\" {\n\t\tipath = \"~/.ipfs\"\n\t}\n\tif ipfsPath != \"\" {\n\t\tipath = ipfsPath\n\t}\n\treturn ipath\n}\n"
  },
  {
    "path": "pkg/ipfs/image_noipfs.go",
    "content": "//go:build no_ipfs\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"context\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/images/converter\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\n// EnsureImage pull the specified image from IPFS.\nfunc EnsureImage(ctx context.Context, client *containerd.Client, scheme, ref, ipfsPath string, options types.ImagePullOptions) (*imgutil.EnsuredImage, error) {\n\treturn nil, ErrNotImplemented\n}\n\n// Push pushes the specified image to IPFS.\nfunc Push(ctx context.Context, client *containerd.Client, rawRef string, layerConvert converter.ConvertFunc, allPlatforms bool, platform []string, ensureImage bool, ipfsPath string) (string, error) {\n\treturn \"\", ErrNotImplemented\n}\n"
  },
  {
    "path": "pkg/ipfs/noipfs.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/containerd/errdefs\"\n)\n\nvar ErrNotImplemented = fmt.Errorf(\"%w: ipfs is disabled by the distributor of this build\", errdefs.ErrNotImplemented)\n"
  },
  {
    "path": "pkg/ipfs/registry.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"time\"\n)\n\n// RegistryOptions represents options to configure the registry.\ntype RegistryOptions struct {\n\n\t// Times to retry query on IPFS. Zero or lower value means no retry.\n\tReadRetryNum int\n\n\t// ReadTimeout is timeout duration of a read request to IPFS. Zero means no timeout.\n\tReadTimeout time.Duration\n\n\t// IpfsPath is the IPFS_PATH value to be used for ipfs command.\n\tIpfsPath string\n}\n"
  },
  {
    "path": "pkg/ipfs/registry_ipfs.go",
    "content": "//go:build !no_ipfs\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/log\"\n\tipfsclient \"github.com/containerd/stargz-snapshotter/ipfs/client\"\n)\n\nfunc NewRegistry(options RegistryOptions) (http.Handler, error) {\n\t// HTTP is only supported as of now. We can add https support here if needed (e.g. for connecting to it via proxy, etc)\n\tiurl, err := ipfsclient.GetIPFSAPIAddress(lookupIPFSPath(options.IpfsPath), \"http\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &server{options, ipfsclient.New(iurl)}, nil\n}\n\n// server is a read-only registry which converts OCI Distribution Spec's pull-related API to IPFS\n// https://github.com/opencontainers/distribution-spec/blob/v1.0/spec.md#pull\ntype server struct {\n\tconfig     RegistryOptions\n\tipfsclient *ipfsclient.Client\n}\n\nvar manifestRegexp = regexp.MustCompile(`/v2/ipfs/([a-z0-9]+)/manifests/(.*)`)\nvar blobsRegexp = regexp.MustCompile(`/v2/ipfs/([a-z0-9]+)/blobs/(.*)`)\n\nfunc (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tcid, content, mediaType, size, err := s.serve(r)\n\tif err != nil {\n\t\tlog.L.WithError(err).Warnf(\"failed to serve %q %q\", r.Method, r.URL.Path)\n\t\t// TODO: support response body following OCI Distribution Spec's error response format spec:\n\t\t// https://github.com/opencontainers/distribution-spec/blob/v1.0/spec.md#error-codes\n\t\thttp.Error(w, \"\", http.StatusNotFound)\n\t\treturn\n\t}\n\tif content == nil {\n\t\tlog.L.Debugf(\"returning without contents\")\n\t\tw.WriteHeader(200)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", mediaType)\n\tw.Header().Set(\"Content-Length\", strconv.FormatInt(size, 10))\n\tif r.Method == \"GET\" {\n\t\thttp.ServeContent(w, r, \"\", time.Now(), content)\n\t\tlog.L.WithField(\"CID\", cid).Debugf(\"served file\")\n\t}\n}\n\nfunc (s *server) serve(r *http.Request) (string, io.ReadSeeker, string, int64, error) {\n\tif r.Method != \"GET\" && r.Method != \"HEAD\" {\n\t\treturn \"\", nil, \"\", 0, fmt.Errorf(\"unsupported method\")\n\t}\n\n\tif r.URL.Path == \"/v2/\" {\n\t\tlog.L.Debugf(\"requested /v2/\")\n\t\treturn \"\", nil, \"\", 0, nil\n\t}\n\n\tif matches := manifestRegexp.FindStringSubmatch(r.URL.Path); len(matches) != 0 {\n\t\tcidStr, ref := matches[1], matches[2]\n\t\tif _, dgstErr := digest.Parse(ref); dgstErr == nil {\n\t\t\tresolvedCID, content, mediaType, size, err := s.serveContentByDigest(r.Context(), cidStr, ref)\n\t\t\tif !images.IsManifestType(mediaType) && !images.IsIndexType(mediaType) {\n\t\t\t\treturn \"\", nil, \"\", 0, fmt.Errorf(\"cannot serve non-manifest from manifest API: %q\", mediaType)\n\t\t\t}\n\t\t\tlog.L.WithField(\"root CID\", cidStr).WithField(\"digest\", ref).WithField(\"resolved CID\", resolvedCID).Debugf(\"resolved manifest by digest\")\n\t\t\treturn resolvedCID, content, mediaType, size, err\n\t\t}\n\t\tif ref != \"latest\" {\n\t\t\treturn \"\", nil, \"\", 0, fmt.Errorf(\"tag of %q must be latest but got %q\", cidStr, ref)\n\t\t}\n\t\tresolvedCID, content, mediaType, size, err := s.serveContentByCID(r.Context(), cidStr)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, \"\", 0, err\n\t\t}\n\t\tlog.L.WithField(\"root CID\", cidStr).WithField(\"resolved CID\", resolvedCID).Debugf(\"resolved manifest by cid\")\n\t\treturn resolvedCID, content, mediaType, size, nil\n\t}\n\n\tif matches := blobsRegexp.FindStringSubmatch(r.URL.Path); len(matches) != 0 {\n\t\trootCIDStr, dgstStr := matches[1], matches[2]\n\t\tresolvedCID, content, mediaType, size, err := s.serveContentByDigest(r.Context(), rootCIDStr, dgstStr)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, \"\", 0, err\n\t\t}\n\t\tlog.L.WithField(\"root CID\", rootCIDStr).WithField(\"digest\", dgstStr).WithField(\"resolved CID\", resolvedCID).Debugf(\"resolved blob by digest\")\n\t\treturn resolvedCID, content, mediaType, size, nil\n\t}\n\n\treturn \"\", nil, \"\", 0, fmt.Errorf(\"unsupported path\")\n}\n\nfunc (s *server) serveContentByCID(ctx context.Context, targetCID string) (resC string, r io.ReadSeeker, mediaType string, size int64, err error) {\n\t// TODO: make sure cidStr is a vaild CID?\n\tc, desc, err := s.resolveCIDOfRootBlob(ctx, targetCID)\n\tif err != nil {\n\t\treturn \"\", nil, \"\", 0, err\n\t}\n\trc, err := s.getReadSeeker(ctx, c)\n\tif err != nil {\n\t\treturn \"\", nil, \"\", 0, err\n\t}\n\treturn c, rc, getMediaType(desc), desc.Size, nil\n}\n\nfunc (s *server) serveContentByDigest(ctx context.Context, rootCID, digestStr string) (resC string, r io.ReadSeeker, mediaType string, size int64, err error) {\n\tdgst, err := digest.Parse(digestStr)\n\tif err != nil {\n\t\treturn \"\", nil, \"\", 0, err\n\t}\n\t_, rootDesc, err := s.resolveCIDOfRootBlob(ctx, rootCID)\n\tif err != nil {\n\t\treturn \"\", nil, \"\", 0, err\n\t}\n\ttargetCID, targetDesc, err := s.resolveCIDOfDigest(ctx, dgst, rootDesc)\n\tif err != nil {\n\t\treturn \"\", nil, \"\", 0, err\n\t}\n\trc, err := s.getReadSeeker(ctx, targetCID)\n\tif err != nil {\n\t\treturn \"\", nil, \"\", 0, err\n\t}\n\treturn targetCID, rc, getMediaType(targetDesc), targetDesc.Size, nil\n}\n\nfunc (s *server) getReadSeeker(ctx context.Context, c string) (io.ReadSeeker, error) {\n\tsr, err := s.getFile(ctx, c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newBufReadSeeker(sr), nil\n}\n\nfunc (s *server) getFile(ctx context.Context, c string) (*io.SectionReader, error) {\n\tst, err := s.ipfsclient.StatCID(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tra := &retryReaderAt{\n\t\tctx: ctx,\n\t\treadAtFunc: func(ctx context.Context, p []byte, off int64) (int, error) {\n\t\t\tofst, size := int(off), len(p)\n\t\t\tr, err := s.ipfsclient.Get(\"/ipfs/\"+c, &ofst, &size)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\treturn io.ReadFull(r, p)\n\t\t},\n\t\ttimeout: s.config.ReadTimeout,\n\t\tretry:   s.config.ReadRetryNum,\n\t}\n\treturn io.NewSectionReader(ra, 0, int64(st.Size)), nil\n}\n\nfunc (s *server) resolveCIDOfRootBlob(ctx context.Context, c string) (string, ocispec.Descriptor, error) {\n\trc, err := s.getReadSeeker(ctx, c)\n\tif err != nil {\n\t\treturn \"\", ocispec.Descriptor{}, err\n\t}\n\tvar desc ocispec.Descriptor\n\tif err := json.NewDecoder(rc).Decode(&desc); err != nil {\n\t\treturn \"\", ocispec.Descriptor{}, err\n\t}\n\tc, err = getIPFSCID(desc)\n\tif err != nil {\n\t\treturn \"\", ocispec.Descriptor{}, err\n\t}\n\treturn c, desc, nil\n}\n\nfunc (s *server) resolveCIDOfDigest(ctx context.Context, dgst digest.Digest, desc ocispec.Descriptor) (string, ocispec.Descriptor, error) {\n\tc, err := getIPFSCID(desc)\n\tif err != nil {\n\t\treturn \"\", ocispec.Descriptor{}, err\n\t}\n\tif desc.Digest == dgst {\n\t\treturn c, desc, nil // hit\n\t}\n\tif !images.IsManifestType(desc.MediaType) && !images.IsIndexType(desc.MediaType) {\n\t\t// This is not the target blob and have no child. Early return here and avoid querying this blob.\n\t\treturn \"\", ocispec.Descriptor{}, fmt.Errorf(\"blob doesn't match\")\n\t}\n\tsr, err := s.getFile(ctx, c)\n\tif err != nil {\n\t\treturn \"\", ocispec.Descriptor{}, err\n\t}\n\tdescs, err := images.Children(ctx, &readerProvider{desc, sr}, desc)\n\tif err != nil {\n\t\treturn \"\", ocispec.Descriptor{}, err\n\t}\n\tvar errs []error\n\tfor _, desc := range descs {\n\t\tgotCID, gotDesc, err := s.resolveCIDOfDigest(ctx, dgst, desc)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\treturn gotCID, gotDesc, nil\n\t}\n\tallErr := errors.Join(errs...)\n\tif allErr == nil {\n\t\treturn \"\", ocispec.Descriptor{}, fmt.Errorf(\"not found\")\n\t}\n\treturn \"\", ocispec.Descriptor{}, allErr\n}\n\nfunc getIPFSCID(desc ocispec.Descriptor) (string, error) {\n\tfor _, u := range desc.URLs {\n\t\tif strings.HasPrefix(u, \"ipfs://\") {\n\t\t\t// support only content addressable URL (ipfs://<CID>)\n\t\t\treturn u[7:], nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"no CID is recorded in %s\", desc.Digest)\n}\n\nfunc getMediaType(desc ocispec.Descriptor) string {\n\tif images.IsManifestType(desc.MediaType) || images.IsIndexType(desc.MediaType) || images.IsConfigType(desc.MediaType) {\n\t\treturn desc.MediaType\n\t}\n\treturn \"application/octet-stream\"\n}\n\ntype retryReaderAt struct {\n\tctx        context.Context\n\treadAtFunc func(ctx context.Context, p []byte, off int64) (int, error)\n\ttimeout    time.Duration\n\tretry      int\n}\n\nfunc (r *retryReaderAt) ReadAt(p []byte, off int64) (int, error) {\n\tif r.retry < 0 {\n\t\tr.retry = 0\n\t}\n\tfor i := 0; i <= r.retry; i++ {\n\t\tctx := r.ctx\n\t\tif r.timeout != 0 {\n\t\t\tvar cancel context.CancelFunc\n\t\t\tctx, cancel = context.WithTimeout(ctx, r.timeout)\n\t\t\tdefer cancel()\n\t\t}\n\t\tn, err := r.readAtFunc(ctx, p, off)\n\t\tif err == nil {\n\t\t\treturn n, nil\n\t\t} else if !errors.Is(err, context.DeadlineExceeded) {\n\t\t\treturn 0, err\n\t\t}\n\t\t// deadline exceeded. retry.\n\t}\n\treturn 0, context.DeadlineExceeded\n}\n\nfunc newBufReadSeeker(rs io.ReadSeeker) io.ReadSeeker {\n\trsc := &bufReadSeeker{\n\t\trs: rs,\n\t}\n\trsc.curR = bufio.NewReaderSize(rsc.rs, 512*1024)\n\treturn rsc\n}\n\ntype bufReadSeeker struct {\n\trs   io.ReadSeeker\n\tcurR *bufio.Reader\n}\n\nfunc (r *bufReadSeeker) Read(p []byte) (int, error) {\n\treturn r.curR.Read(p)\n}\n\nfunc (r *bufReadSeeker) Seek(offset int64, whence int) (int64, error) {\n\tn, err := r.rs.Seek(offset, whence)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tr.curR.Reset(r.rs)\n\treturn n, nil\n}\n\ntype readerProvider struct {\n\tdesc ocispec.Descriptor\n\tr    *io.SectionReader\n}\n\nfunc (p *readerProvider) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {\n\tif desc.Digest != p.desc.Digest || desc.Size != p.desc.Size {\n\t\treturn nil, fmt.Errorf(\"unexpected content\")\n\t}\n\treturn &contentReaderAt{p.r}, nil\n}\n\ntype contentReaderAt struct {\n\t*io.SectionReader\n}\n\nfunc (r *contentReaderAt) Close() error { return nil }\n"
  },
  {
    "path": "pkg/ipfs/registry_noipfs.go",
    "content": "//go:build no_ipfs\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ipfs\n\nimport (\n\t\"net/http\"\n)\n\nfunc NewRegistry(options RegistryOptions) (http.Handler, error) {\n\treturn nil, ErrNotImplemented\n}\n"
  },
  {
    "path": "pkg/labels/k8slabels/k8slabels.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package k8slabels defines Kubernetes container labels\npackage k8slabels\n\nconst (\n\tPodNamespace  = \"io.kubernetes.pod.namespace\"\n\tPodName       = \"io.kubernetes.pod.name\"\n\tContainerName = \"io.kubernetes.container.name\"\n\n\tContainerMetadataExtension = \"io.cri-containerd.container.metadata\"\n\tContainerType              = \"io.cri-containerd.kind\"\n)\n"
  },
  {
    "path": "pkg/labels/labels.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package labels defines labels that are set to containerd containers as labels.\n// The labels defined in this package are also passed to OCI containers as annotations.\npackage labels\n\nconst (\n\t// Prefix is the common prefix of nerdctl labels\n\tPrefix = \"nerdctl/\"\n\n\t// Namespace is the containerd namespace such as \"default\", \"k8s.io\"\n\tNamespace = Prefix + \"namespace\"\n\n\t// Name is a human-friendly name.\n\t// WARNING: multiple containers may have same the name label\n\tName = Prefix + \"name\"\n\n\t//Compose Project Name\n\tComposeProject = \"com.docker.compose.project\"\n\n\t//Compose Service Name\n\tComposeService = \"com.docker.compose.service\"\n\n\t//Compose Network Name\n\tComposeNetwork = \"com.docker.compose.network\"\n\n\t//Compose Volume Name\n\tComposeVolume = \"com.docker.compose.volume\"\n\n\t// ComposeConfigHash stores the service configuration hash used for convergence decisions\n\tComposeConfigHash = \"com.docker.compose.config-hash\"\n\n\t// Hostname\n\tHostname = Prefix + \"hostname\"\n\n\t// Domainname\n\tDomainname = Prefix + \"domainname\"\n\n\t// ExtraHosts are HostIPs to appended to /etc/hosts\n\tExtraHosts = Prefix + \"extraHosts\"\n\n\t// StateDir is \"/var/lib/nerdctl/<ADDRHASH>/containers/<NAMESPACE>/<ID>\"\n\tStateDir = Prefix + \"state-dir\"\n\n\t// Networks is a JSON-marshalled string of []string, e.g. []string{\"bridge\"}.\n\t// Currently, the length of the slice must be 1.\n\tNetworks = Prefix + \"networks\"\n\n\t// DEPRECATED : https://github.com/containerd/nerdctl/pull/4290\n\t// Ports is a JSON-marshalled string of []cni.PortMapping .\n\tPorts = Prefix + \"ports\"\n\n\t// IPAddress is the static IP address of the container assigned by the user\n\tIPAddress = Prefix + \"ip\"\n\n\t// IP6Address is the static IP6 address of the container assigned by the user\n\tIP6Address = Prefix + \"ip6\"\n\n\t// LogURI is the log URI\n\tLogURI = Prefix + \"log-uri\"\n\n\t// PIDFile is the `nerdctl run --pidfile`\n\t// (CLI flag is \"pidfile\", not \"pid-file\", for Podman compatibility)\n\tPIDFile = Prefix + \"pid-file\"\n\n\t// AnonymousVolumes is a JSON-marshalled string of []string\n\tAnonymousVolumes = Prefix + \"anonymous-volumes\"\n\n\t// Platform is the normalized platform string like \"linux/ppc64le\".\n\tPlatform = Prefix + \"platform\"\n\n\t// Mounts is the mount points for the container.\n\tMounts = Prefix + \"mounts\"\n\n\t// StopTimeout is seconds to wait for stop a container.\n\tStopTimeout = Prefix + \"stop-timeout\"\n\n\tMACAddress = Prefix + \"mac-address\"\n\n\t// PIDContainer is the `nerdctl run --pid` for restarting\n\tPIDContainer = Prefix + \"pid-container\"\n\n\t// IPC is the `nerectl run --ipc` for restrating\n\t// IPC indicates ipc victim container.\n\tIPC = Prefix + \"ipc\"\n\n\t// Error encapsulates a container human-readable string\n\t// that describes container error.\n\tError = Prefix + \"error\"\n\n\t// NerdctlDefaultNetwork indicates whether a network is the default network\n\t// created and owned by Nerdctl.\n\t// Boolean value which can be parsed with strconv.ParseBool() is required.\n\t// (like \"nerdctl/default-network=true\" or \"nerdctl/default-network=false\")\n\tNerdctlDefaultNetwork = Prefix + \"default-network\"\n\n\t// ContainerAutoRemove is to check whether the --rm option is specified.\n\tContainerAutoRemove = Prefix + \"auto-remove\"\n\n\t// LogConfig defines the logging configuration passed to the container\n\tLogConfig = Prefix + \"log-config\"\n\n\t// HostConfigLabel sets the dockercompat host config values\n\tHostConfigLabel = Prefix + \"host-config\"\n\n\t// DNSSettings sets the dockercompat DNS config values\n\tDNSSetting = Prefix + \"dns\"\n\n\t// User is the username of the container\n\tUser = Prefix + \"user\"\n\n\t// HealthCheck stores the health check configuration used to run health checks on the container\n\tHealthCheck = Prefix + \"healthcheck\"\n\n\t// HealthState stores the current health state (status and failing streak).\n\tHealthState = Prefix + \"healthstate\"\n)\n"
  },
  {
    "path": "pkg/logging/cri_logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n\tForked from https://github.com/kubernetes/kubernetes/blob/a66aad2d80dacc70025f95a8f97d2549ebd3208c/pkg/kubelet/kuberuntime/logs/logs.go\n\tCopyright The Kubernetes Authors.\n\tLicensed under the Apache License, Version 2.0\n*/\n\npackage logging\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/logging/tail\"\n)\n\n// LogStreamType is the type of the stream in CRI container log.\ntype LogStreamType string\n\nconst (\n\t// Stdout is the stream type for stdout.\n\tStdout LogStreamType = \"stdout\"\n\t// Stderr is the stream type for stderr.\n\tStderr LogStreamType = \"stderr\"\n\n\t// logForceCheckPeriod is the period to check for a new read\n\tlogForceCheckPeriod = 1 * time.Second\n)\n\n// LogTag is the tag of a log line in CRI container log.\n// Currently defined log tags:\n// * First tag: Partial/Full - P/F.\n// The field in the container log format can be extended to include multiple\n// tags by using a delimiter, but changes should be rare. If it becomes clear\n// that better extensibility is desired, a more extensible format (e.g., json)\n// should be adopted as a replacement and/or addition.\ntype LogTag string\n\nconst (\n\t// LogTagPartial means the line is part of multiple lines.\n\tLogTagPartial LogTag = \"P\"\n\t// LogTagFull means the line is a single full line or the end of multiple lines.\n\tLogTagFull LogTag = \"F\"\n\t// LogTagDelimiter is the delimiter for different log tags.\n\tLogTagDelimiter = \":\"\n)\n\n// Loads log entries from logfiles produced by the Text-logger driver and forwards\n// them to the provided io.Writers after applying the provided logging options.\nfunc viewLogsCRI(lvopts LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error {\n\tif lvopts.LogPath == \"\" {\n\t\treturn fmt.Errorf(\"logpath is nil \")\n\t}\n\n\treturn ReadLogs(&lvopts, stdout, stderr, stopChannel)\n}\n\n// ReadLogs read the container log and redirect into stdout and stderr.\n// Note that containerID is only needed when following the log, or else\n// just pass in empty string \"\".\nfunc ReadLogs(opts *LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error {\n\tvar logPath = opts.LogPath\n\tevaluated, err := filepath.EvalSymlinks(logPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to try resolving symlinks in path %q: %v\", logPath, err)\n\t}\n\tlogPath = evaluated\n\tf, err := os.Open(logPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open log file %q: %v\", logPath, err)\n\t}\n\tdefer func() {\n\t\tf.Close()\n\t}()\n\n\t// Search start point based on tail line.\n\tstart, err := tail.FindTailLineStartIndex(f, opts.Tail)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to tail %d lines of log file %q: %v\", opts.Tail, logPath, err)\n\t}\n\n\tif _, err := f.Seek(start, io.SeekStart); err != nil {\n\t\treturn fmt.Errorf(\"failed to seek in log file %q: %v\", logPath, err)\n\t}\n\n\tvar watcher *fsnotify.Watcher\n\n\tlimitedMode := (opts.Tail > 0) && (!opts.Follow)\n\tlimitedNum := opts.Tail\n\t// Start parsing the logs.\n\tr := bufio.NewReader(f)\n\n\tvar stop bool\n\tisNewLine := true\n\twriter := newLogWriter(stdout, stderr, opts)\n\tmsg := &logMessage{}\n\tbaseName := filepath.Base(logPath)\n\tdir := filepath.Dir(logPath)\n\n\tfor {\n\t\tselect {\n\t\tcase <-stopChannel:\n\t\t\tlog.L.Debugf(\"received stop signal while reading cri logfile, returning\")\n\t\t\treturn nil\n\t\tdefault:\n\t\t\tif stop || (limitedMode && limitedNum == 0) {\n\t\t\t\tlog.L.Debugf(\"finished parsing log file, path: %s\", logPath)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tl, err := r.ReadBytes(eol[0])\n\t\t\tif err != nil {\n\t\t\t\tif err != io.EOF { // This is an real error\n\t\t\t\t\treturn fmt.Errorf(\"failed to read log file %q: %v\", logPath, err)\n\t\t\t\t}\n\t\t\t\tif opts.Follow {\n\t\t\t\t\t// Reset seek so that if this is an incomplete line,\n\t\t\t\t\t// it will be read again.\n\t\t\t\t\tif _, err := f.Seek(-int64(len(l)), io.SeekCurrent); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to reset seek in log file %q: %v\", logPath, err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif watcher == nil {\n\t\t\t\t\t\t// Initialize the watcher if it has not been initialized yet.\n\t\t\t\t\t\tif watcher, err = NewLogFileWatcher(dir); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefer watcher.Close()\n\t\t\t\t\t\t// If we just created the watcher, try again to read as we might have missed\n\t\t\t\t\t\t// the event.\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tvar recreated bool\n\t\t\t\t\t// Wait until the next log change.\n\t\t\t\t\trecreated, err = startTail(context.Background(), baseName, watcher)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tif recreated {\n\t\t\t\t\t\tnewF, err := openFileShareDelete(logPath)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\t\t\t\t\t//If the user application outputs logs too quickly,\n\t\t\t\t\t\t\t\t//There is a slight possibility that nerdctl has just rotated the log file,\n\t\t\t\t\t\t\t\t//try opening it once more.\n\t\t\t\t\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnewF, err = openFileShareDelete(logPath)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn fmt.Errorf(\"failed to open cri logfile %q: %w\", logPath, err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tf.Close()\n\t\t\t\t\t\tf = newF\n\t\t\t\t\t\tr = bufio.NewReader(f)\n\t\t\t\t\t}\n\n\t\t\t\t\t// If the container exited consume data until the next EOF\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Should stop after writing the remaining content.\n\t\t\t\tstop = true\n\t\t\t\tif len(l) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlog.L.Debugf(\"incomplete line in log file, path: %s, line: %s\", logPath, l)\n\t\t\t}\n\n\t\t\t// Parse the log line.\n\t\t\tmsg.reset()\n\t\t\tif err := ParseCRILog(l, msg); err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed when parsing line in log file, path: %s, line: %s\", logPath, l)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Write the log line into the stream.\n\t\t\tif err := writer.write(msg, isNewLine); err != nil {\n\t\t\t\tif err == errMaximumWrite {\n\t\t\t\t\tlog.L.Debugf(\"finished parsing log file, hit bytes limit path: %s\", logPath)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tlog.L.WithError(err).Errorf(\"failed when writing line to log file, path: %s, line: %s\", logPath, l)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif limitedMode {\n\t\t\t\tlimitedNum--\n\t\t\t}\n\t\t\tif len(msg.log) > 0 {\n\t\t\t\tisNewLine = msg.log[len(msg.log)-1] == eol[0]\n\t\t\t} else {\n\t\t\t\tisNewLine = true\n\t\t\t}\n\t\t}\n\t}\n}\n\nvar (\n\t// eol is the end-of-line sign in the log.\n\teol = []byte{'\\n'}\n\t// delimiter is the delimiter for timestamp and stream type in log line.\n\tdelimiter = []byte{' '}\n\t// tagDelimiter is the delimiter for log tags.\n\ttagDelimiter = []byte(\":\")\n)\n\n// logWriter controls the writing into the stream based on the log options.\ntype logWriter struct {\n\tstdout io.Writer\n\tstderr io.Writer\n\topts   *LogViewOptions\n\tremain int64\n}\n\n// errMaximumWrite is returned when all bytes have been written.\nvar errMaximumWrite = errors.New(\"maximum write\")\n\n// errShortWrite is returned when the message is not fully written.\nvar errShortWrite = errors.New(\"short write\")\n\nfunc newLogWriter(stdout io.Writer, stderr io.Writer, opts *LogViewOptions) *logWriter {\n\tw := &logWriter{\n\t\tstdout: stdout,\n\t\tstderr: stderr,\n\t\topts:   opts,\n\t\tremain: math.MaxInt64, // initialize it as infinity\n\t}\n\t//if opts.bytes >= 0 {\n\t//\tw.remain = opts.bytes\n\t//}\n\treturn w\n}\n\n// writeLogs writes logs into stdout, stderr.\nfunc (w *logWriter) write(msg *logMessage, addPrefix bool) error {\n\n\t//if msg.timestamp.Before(ts) {\n\t//\t// Skip the line because it's older than since\n\t//\treturn nil\n\t//}\n\tline := msg.log\n\tif w.opts.Timestamps && addPrefix {\n\t\tprefix := append([]byte(msg.timestamp.Format(log.RFC3339NanoFixed)), delimiter[0])\n\t\tline = append(prefix, line...)\n\t}\n\t// If the line is longer than the remaining bytes, cut it.\n\tif int64(len(line)) > w.remain {\n\t\tline = line[:w.remain]\n\t}\n\t// Get the proper stream to write to.\n\tvar stream io.Writer\n\tswitch msg.stream {\n\tcase Stdout:\n\t\tstream = w.stdout\n\tcase Stderr:\n\t\tstream = w.stderr\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected stream type %q\", msg.stream)\n\t}\n\tn, err := stream.Write(line)\n\tw.remain -= int64(n)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// If the line has not been fully written, return errShortWrite\n\tif n < len(line) {\n\t\treturn errShortWrite\n\t}\n\t// If there are no more bytes left, return errMaximumWrite\n\tif w.remain <= 0 {\n\t\treturn errMaximumWrite\n\t}\n\treturn nil\n}\n\n// logMessage is the CRI internal log type.\ntype logMessage struct {\n\ttimestamp time.Time\n\tstream    LogStreamType\n\tlog       []byte\n}\n\n// reset the log to nil.\nfunc (l *logMessage) reset() {\n\tl.timestamp = time.Time{}\n\tl.stream = \"\"\n\tl.log = nil\n}\n\n// ParseCRILog parses logs in CRI log format. CRI Log format example:\n//\n//\t2016-10-06T00:17:09.669794202Z stdout P log content 1\n//\t2016-10-06T00:17:09.669794203Z stderr F log content 2\nfunc ParseCRILog(log []byte, msg *logMessage) error {\n\tvar err error\n\t// Parse timestamp\n\tidx := bytes.Index(log, delimiter)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"timestamp is not found\")\n\t}\n\tmsg.timestamp, err = time.Parse(time.RFC3339Nano, string(log[:idx]))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected timestamp format %q: %v\", time.RFC3339Nano, err)\n\t}\n\n\t// Parse stream type\n\tlog = log[idx+1:]\n\tidx = bytes.Index(log, delimiter)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"stream type is not found\")\n\t}\n\tmsg.stream = LogStreamType(log[:idx])\n\tif msg.stream != Stdout && msg.stream != Stderr {\n\t\treturn fmt.Errorf(\"unexpected stream type %q\", msg.stream)\n\t}\n\n\t// Parse log tag\n\tlog = log[idx+1:]\n\tidx = bytes.Index(log, delimiter)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"log tag is not found\")\n\t}\n\t// Keep this forward compatible.\n\ttags := bytes.Split(log[:idx], tagDelimiter)\n\tpartial := LogTag(tags[0]) == LogTagPartial\n\t// Trim the tailing new line if this is a partial line.\n\tif partial && len(log) > 0 && log[len(log)-1] == '\\n' {\n\t\tlog = log[:len(log)-1]\n\t}\n\n\t// Get log content\n\tmsg.log = log[idx+1:]\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/logging/cri_logger_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n\tForked from https://github.com/kubernetes/kubernetes/blob/a66aad2d80dacc70025f95a8f97d2549ebd3208c/pkg/kubelet/kuberuntime/logs/logs_test.go\n\tCopyright The Kubernetes Authors.\n\tLicensed under the Apache License, Version 2.0\n*/\n\npackage logging\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestReadLogs(t *testing.T) {\n\tfile, err := os.CreateTemp(\"\", \"TestFollowLogs\")\n\tif err != nil {\n\t\tt.Fatalf(\"unable to create temp file\")\n\t}\n\tdefer os.Remove(file.Name())\n\tfile.WriteString(`2016-10-06T00:17:09.669794202Z stdout F line1` + \"\\n\")\n\tfile.WriteString(`2016-10-06T00:17:10.669794202Z stdout F line2` + \"\\n\")\n\tfile.WriteString(`2016-10-06T00:17:11.669794202Z stdout F line3` + \"\\n\")\n\n\tstopChan := make(chan os.Signal)\n\ttestCases := []struct {\n\t\tname           string\n\t\tlogViewOptions LogViewOptions\n\t\texpected       string\n\t}{\n\t\t{\n\t\t\tname: \"default log options should output all lines\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    0,\n\t\t\t},\n\t\t\texpected: \"line1\\nline2\\nline3\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"using Tail 2 should output last 2 lines\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    2,\n\t\t\t},\n\t\t\texpected: \"line2\\nline3\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"using Tail 4 should output all lines when the log has less than 4 lines\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    4,\n\t\t\t},\n\t\t\texpected: \"line1\\nline2\\nline3\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"using Tail 0 should output all\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    0,\n\t\t\t},\n\t\t\texpected: \"line1\\nline2\\nline3\\n\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tstdoutBuf := bytes.NewBuffer(nil)\n\t\t\tstderrBuf := bytes.NewBuffer(nil)\n\t\t\terr = ReadLogs(&tc.logViewOptions, stdoutBuf, stderrBuf, stopChan)\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err.Error())\n\t\t\t}\n\t\t\tif stderrBuf.Len() > 0 {\n\t\t\t\tt.Fatalf(\"Stderr: %v\", stderrBuf.String())\n\t\t\t}\n\t\t\tif actual := stdoutBuf.String(); tc.expected != actual {\n\t\t\t\tt.Fatalf(\"Actual output does not match expected.\\nActual:  %v\\nExpected: %v\\n\", actual, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseLog(t *testing.T) {\n\ttimestamp, err := time.Parse(time.RFC3339Nano, \"2016-10-20T18:39:20.57606443Z\")\n\n\tif err != nil {\n\t\tt.Fatalf(\"Parse Time err %s\", err.Error())\n\t}\n\tlogmsg := &logMessage{}\n\tfor c, test := range []struct {\n\t\tline string\n\t\tmsg  *logMessage\n\t\terr  bool\n\t}{\n\t\t{ // CRI log format stdout\n\t\t\tline: \"2016-10-20T18:39:20.57606443Z stdout F cri stdout test log\\n\",\n\t\t\tmsg: &logMessage{\n\t\t\t\ttimestamp: timestamp,\n\t\t\t\tstream:    Stdout,\n\t\t\t\tlog:       []byte(\"cri stdout test log\\n\"),\n\t\t\t},\n\t\t},\n\t\t{ // CRI log format stderr\n\t\t\tline: \"2016-10-20T18:39:20.57606443Z stderr F cri stderr test log\\n\",\n\t\t\tmsg: &logMessage{\n\t\t\t\ttimestamp: timestamp,\n\t\t\t\tstream:    Stderr,\n\t\t\t\tlog:       []byte(\"cri stderr test log\\n\"),\n\t\t\t},\n\t\t},\n\t\t{ // Unsupported Log format\n\t\t\tline: \"unsupported log format test log\\n\",\n\t\t\tmsg:  &logMessage{},\n\t\t\terr:  true,\n\t\t},\n\t\t{ // Partial CRI log line\n\t\t\tline: \"2016-10-20T18:39:20.57606443Z stdout P cri stdout partial test log\\n\",\n\t\t\tmsg: &logMessage{\n\t\t\t\ttimestamp: timestamp,\n\t\t\t\tstream:    Stdout,\n\t\t\t\tlog:       []byte(\"cri stdout partial test log\"),\n\t\t\t},\n\t\t},\n\t\t{ // Partial CRI log line with multiple log tags.\n\t\t\tline: \"2016-10-20T18:39:20.57606443Z stdout P:TAG1:TAG2 cri stdout partial test log\\n\",\n\t\t\tmsg: &logMessage{\n\t\t\t\ttimestamp: timestamp,\n\t\t\t\tstream:    Stdout,\n\t\t\t\tlog:       []byte(\"cri stdout partial test log\"),\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Logf(\"TestCase #%d: %+v\", c, test)\n\n\t\terr = ParseCRILog([]byte(test.line), logmsg)\n\t\tif err != nil {\n\t\t\tif test.err {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"ParseCRILog err %s \", err.Error())\n\t\t}\n\n\t\tif !reflect.DeepEqual(test.msg, logmsg) {\n\t\t\tt.Errorf(\"ParseCRILog failed, msg is %#v,test.msg is %#v\", logmsg, test.msg)\n\t\t}\n\n\t}\n}\n\nfunc TestReadLogsLimitsWithTimestamps(t *testing.T) {\n\tlogLineFmt := \"2022-10-29T16:10:22.592603036-05:00 stdout P %v\\n\"\n\tlogLineNewLine := \"2022-10-29T16:10:22.592603036-05:00 stdout F \\n\"\n\n\ttmpfile, err := os.CreateTemp(\"\", \"log.*.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"unable to create temp file\")\n\t}\n\n\tstopChan := make(chan os.Signal)\n\n\tcount := 10000\n\n\tfor i := 0; i < count; i++ {\n\t\tfmt.Fprintf(tmpfile, logLineFmt, i)\n\t}\n\ttmpfile.WriteString(logLineNewLine)\n\n\tfor i := 0; i < count; i++ {\n\t\tfmt.Fprintf(tmpfile, logLineFmt, i)\n\t}\n\ttmpfile.WriteString(logLineNewLine)\n\n\t// two lines are in the buffer\n\n\tdefer os.Remove(tmpfile.Name()) // clean up\n\n\ttmpfile.Close()\n\n\tvar buf bytes.Buffer\n\tw := io.MultiWriter(&buf)\n\n\terr = ReadLogs(&LogViewOptions{LogPath: tmpfile.Name(), Tail: 0, Timestamps: true}, w, w, stopChan)\n\tif err != nil {\n\t\tt.Errorf(\"ReadLogs file %s failed %s\", tmpfile.Name(), err.Error())\n\t}\n\n\tlineCount := 0\n\tscanner := bufio.NewScanner(bytes.NewReader(buf.Bytes()))\n\tfor scanner.Scan() {\n\t\tlineCount++\n\n\t\t// Split the line\n\t\tts, logline, _ := bytes.Cut(scanner.Bytes(), []byte(\" \"))\n\n\t\t// Verification\n\t\t//   1. The timestamp should exist\n\t\t//   2. The last item in the log should be 9999\n\t\t_, err = time.Parse(time.RFC3339, string(ts))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"timestamp not found, err: %s\", err.Error())\n\t\t}\n\n\t\tif !bytes.HasSuffix(logline, []byte(\"9999\")) {\n\t\t\tt.Errorf(\"the complete log found, err: %s\", err.Error())\n\t\t}\n\t}\n\n\tif lineCount != 2 {\n\t\tt.Errorf(\"should have two lines, lineCount= %d\", lineCount)\n\t}\n}\n\nfunc TestReadRotatedLog(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"windows implementation does not seem to work right now and should be fixed: https://github.com/containerd/nerdctl/issues/3554\")\n\t}\n\tfile, err := os.CreateTemp(tmpDir, \"logfile\")\n\tif err != nil {\n\t\tt.Errorf(\"unable to create temp file, error: %s\", err.Error())\n\t}\n\tstdoutBuf := &bytes.Buffer{}\n\tstderrBuf := &bytes.Buffer{}\n\tcontainerStoped := make(chan os.Signal)\n\t// Start to follow the container's log.\n\tfileName := file.Name()\n\tgo func() {\n\t\tlvOpts := &LogViewOptions{\n\t\t\tFollow:  true,\n\t\t\tLogPath: fileName,\n\t\t}\n\t\t_ = ReadLogs(lvOpts, stdoutBuf, stderrBuf, containerStoped)\n\t}()\n\n\t// log in stdout\n\texpectedStdout := \"line0line2line4line6line8\"\n\t// log in stderr\n\texpectedStderr := \"line1line3line5line7line9\"\n\n\tdir := filepath.Dir(file.Name())\n\tbaseName := filepath.Base(file.Name())\n\n\t// Write 10 lines to log file.\n\t// Let ReadLogs start.\n\ttime.Sleep(50 * time.Millisecond)\n\n\tfor line := 0; line < 10; line++ {\n\t\t// Write the first three lines to log file\n\t\tnow := time.Now().Format(time.RFC3339Nano)\n\t\tif line%2 == 0 {\n\t\t\tfmt.Fprintf(file, \"%s stdout P line%d\\n\", now, line)\n\n\t\t} else {\n\t\t\tfmt.Fprintf(file, \"%s stderr P line%d\\n\", now, line)\n\t\t}\n\n\t\ttime.Sleep(1 * time.Millisecond)\n\n\t\tif line == 5 {\n\t\t\tfile.Close()\n\t\t\t// Pretend to rotate the log.\n\t\t\trotatedName := fmt.Sprintf(\"%s.%s\", baseName, time.Now().Format(\"220060102-150405\"))\n\t\t\trotatedName = filepath.Join(dir, rotatedName)\n\t\t\tif err := os.Rename(filepath.Join(dir, baseName), rotatedName); err != nil {\n\t\t\t\tt.Errorf(\"failed to rotate log %q to %q, error: %s\", file.Name(), rotatedName, err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t\tnewF := filepath.Join(dir, baseName)\n\t\t\tif file, err = os.Create(newF); err != nil {\n\t\t\t\tt.Errorf(\"unable to create new log file, error: %s\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// Finished writing into the file, close it, so we can delete it later.\n\terr = file.Close()\n\tif err != nil {\n\t\tt.Errorf(\"could not close file, error: %s\", err.Error())\n\t}\n\n\ttime.Sleep(2 * time.Second)\n\t// Make the function ReadLogs end.\n\tclose(containerStoped)\n\n\tif expectedStdout != stdoutBuf.String() {\n\t\tt.Errorf(\"expected: %s, acoutal: %s\", expectedStdout, stdoutBuf.String())\n\t}\n\n\tif expectedStderr != stderrBuf.String() {\n\t\tt.Errorf(\"expected: %s, acoutal: %s\", expectedStderr, stderrBuf.String())\n\t}\n}\n"
  },
  {
    "path": "pkg/logging/detail_writer.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport \"io\"\n\ntype DetailWriter struct {\n\tw      io.Writer\n\tprefix string\n}\n\nfunc NewDetailWriter(w io.Writer, prefix string) io.Writer {\n\treturn &DetailWriter{\n\t\tw:      w,\n\t\tprefix: prefix,\n\t}\n}\n\nfunc (dw *DetailWriter) Write(p []byte) (n int, err error) {\n\tif len(p) > 0 {\n\t\tif _, err = dw.w.Write([]byte(dw.prefix)); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treturn dw.w.Write(p)\n\t}\n\treturn 0, nil\n}\n"
  },
  {
    "path": "pkg/logging/fluentd_logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/url\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/fluent/fluent-logger-golang/fluent\"\n\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\ntype FluentdLogger struct {\n\tOpts         map[string]string\n\tfluentClient *fluent.Fluent\n\tconfig       *logging.Config\n}\n\nconst (\n\tfluentAddress                 = \"fluentd-address\"\n\tfluentdAsync                  = \"fluentd-async\"\n\tfluentdBufferLimit            = \"fluentd-buffer-limit\"\n\tfluentdRetryWait              = \"fluentd-retry-wait\"\n\tfluentdMaxRetries             = \"fluentd-max-retries\"\n\tfluentdSubSecondPrecision     = \"fluentd-sub-second-precision\"\n\tfluentdAsyncReconnectInterval = \"fluentd-async-reconnect-interval\"\n\tfluentRequestAck              = \"fluentd-request-ack\"\n)\n\nvar FluentdLogOpts = []string{\n\tfluentAddress,\n\tfluentdAsync,\n\tfluentdBufferLimit,\n\tfluentdRetryWait,\n\tfluentdMaxRetries,\n\tfluentdSubSecondPrecision,\n\tfluentdAsyncReconnectInterval,\n\tfluentRequestAck,\n\tTag,\n}\n\nconst (\n\tdefaultBufferLimit = 1024 * 1024\n\tdefaultHost        = \"127.0.0.1\"\n\tdefaultPort        = 24224\n\tdefaultProtocol    = \"tcp\"\n\n\tdefaultMaxRetries = math.MaxInt32\n\tdefaultRetryWait  = 1000 * time.Millisecond\n\n\tminReconnectInterval = 100 * time.Millisecond\n\tmaxReconnectInterval = 10 * time.Second\n)\n\nfunc FluentdLogOptsValidate(logOptMap map[string]string) error {\n\tfor key := range logOptMap {\n\t\tif !strutil.InStringSlice(FluentdLogOpts, key) {\n\t\t\tlog.L.Warnf(\"log-opt %s is ignored for fluentd log driver\", key)\n\t\t}\n\t}\n\tif _, ok := logOptMap[fluentAddress]; !ok {\n\t\tlog.L.Warnf(\"%s is missing for fluentd log driver, the default value %s:%d will be used\", fluentAddress, defaultHost, defaultPort)\n\t}\n\treturn nil\n}\n\ntype fluentdLocation struct {\n\tprotocol string\n\thost     string\n\tport     int\n\tpath     string\n}\n\nfunc (f *FluentdLogger) Init(dataStore, ns, id string) error {\n\treturn nil\n}\n\nfunc (f *FluentdLogger) PreProcess(_ context.Context, _ string, config *logging.Config) error {\n\tif runtime.GOOS == \"windows\" {\n\t\t// TODO: support fluentd on windows\n\t\treturn fmt.Errorf(\"logging to fluentd is not supported on windows\")\n\t}\n\tfluentConfig, err := parseFluentdConfig(f.Opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfluentClient, err := fluent.New(fluentConfig)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create fluent client: %w\", err)\n\t}\n\tf.fluentClient = fluentClient\n\tf.config = config\n\treturn nil\n}\nfunc (f *FluentdLogger) Process(stdout <-chan string, stderr <-chan string) error {\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tfun := func(wg *sync.WaitGroup, dataChan <-chan string, id, namespace, source string) {\n\t\tdefer wg.Done()\n\t\tmetaData := map[string]string{\n\t\t\t\"container_id\": id,\n\t\t\t\"namespace\":    namespace,\n\t\t\t\"source\":       source,\n\t\t}\n\t\tfor log := range dataChan {\n\t\t\tmetaData[\"log\"] = log\n\t\t\tf.fluentClient.PostWithTime(f.Opts[Tag], time.Now(), metaData)\n\t\t}\n\t}\n\tgo fun(&wg, stdout, f.config.ID, f.config.Namespace, \"stdout\")\n\tgo fun(&wg, stderr, f.config.ID, f.config.Namespace, \"stderr\")\n\n\twg.Wait()\n\treturn nil\n}\n\nfunc (f *FluentdLogger) PostProcess() error {\n\tdefer f.fluentClient.Close()\n\treturn nil\n}\n\nfunc parseAddress(address string) (*fluentdLocation, error) {\n\tif address == \"\" {\n\t\treturn &fluentdLocation{\n\t\t\tprotocol: defaultProtocol,\n\t\t\thost:     defaultHost,\n\t\t\tport:     defaultPort,\n\t\t}, nil\n\t}\n\tif !strings.Contains(address, \"://\") {\n\t\taddress = defaultProtocol + \"://\" + address\n\t}\n\ttempURL, err := url.Parse(address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch tempURL.Scheme {\n\tcase \"unix\":\n\t\tif strings.TrimLeft(tempURL.Path, \"/\") == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"unix socket path must not be empty\")\n\t\t}\n\t\treturn &fluentdLocation{\n\t\t\tprotocol: tempURL.Scheme,\n\t\t\tpath:     tempURL.Path,\n\t\t}, nil\n\tcase \"tcp\", \"tls\":\n\t// continue to process below\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported protocol: %s\", tempURL.Scheme)\n\t}\n\tif tempURL.Path != \"\" {\n\t\treturn nil, fmt.Errorf(\"path is not supported: %s\", tempURL.Path)\n\t}\n\thost := defaultHost\n\tport := defaultPort\n\tif h := tempURL.Hostname(); h != \"\" {\n\t\thost = h\n\t}\n\tif p := tempURL.Port(); p != \"\" {\n\t\tportNum, err := strconv.ParseUint(p, 10, 16)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error occurs %v,invalid port\", err)\n\t\t}\n\t\tport = int(portNum)\n\t}\n\treturn &fluentdLocation{\n\t\tprotocol: tempURL.Scheme,\n\t\thost:     host,\n\t\tport:     port,\n\t}, nil\n}\n\nfunc ValidateFluentdLoggerOpts(config map[string]string) error {\n\tfor key := range config {\n\t\tswitch key {\n\t\tcase Tag:\n\t\tcase fluentdBufferLimit:\n\t\tcase fluentdMaxRetries:\n\t\tcase fluentdRetryWait:\n\t\tcase fluentdSubSecondPrecision:\n\t\tcase fluentdAsync:\n\t\tcase fluentAddress:\n\t\tcase fluentdAsyncReconnectInterval:\n\t\tcase fluentRequestAck:\n\t\t// Accepted logger opts\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown log opt '%s' for fluentd log driver\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc parseFluentdConfig(config map[string]string) (fluent.Config, error) {\n\tresult := fluent.Config{}\n\tlocation, err := parseAddress(config[fluentAddress])\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"error occurs %w,invalid fluentd address (%s)\", err, config[fluentAddress])\n\t}\n\tbufferLimit := defaultBufferLimit\n\tif config[fluentdBufferLimit] != \"\" {\n\t\tbufferLimit, err = strconv.Atoi(config[fluentdBufferLimit])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid buffer limit (%s)\", err, config[fluentdBufferLimit])\n\t\t}\n\t}\n\tretryWait := int(defaultRetryWait)\n\tif config[fluentdRetryWait] != \"\" {\n\t\ttemp, err := time.ParseDuration(config[fluentdRetryWait])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid retry wait (%s)\", err, config[fluentdRetryWait])\n\t\t}\n\t\tretryWait = int(temp.Milliseconds())\n\t}\n\tmaxRetries := defaultMaxRetries\n\tif config[fluentdMaxRetries] != \"\" {\n\t\tmaxRetries, err = strconv.Atoi(config[fluentdMaxRetries])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid max retries (%s)\", err, config[fluentdMaxRetries])\n\t\t}\n\t}\n\tasync := false\n\tif config[fluentdAsync] != \"\" {\n\t\tasync, err = strconv.ParseBool(config[fluentdAsync])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid async (%s)\", err, config[fluentdAsync])\n\t\t}\n\t}\n\tasyncReconnectInterval := 0\n\tif config[fluentdAsyncReconnectInterval] != \"\" {\n\t\ttempDuration, err := time.ParseDuration(config[fluentdAsyncReconnectInterval])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid async connect interval (%s)\", err, config[fluentdAsyncReconnectInterval])\n\t\t}\n\t\tif tempDuration != 0 && (tempDuration < minReconnectInterval || tempDuration > maxReconnectInterval) {\n\t\t\treturn result, fmt.Errorf(\"invalid async connect interval (%s), must be between %d and %d\", config[fluentdAsyncReconnectInterval], minReconnectInterval.Milliseconds(), maxReconnectInterval.Milliseconds())\n\t\t}\n\t\tasyncReconnectInterval = int(tempDuration.Milliseconds())\n\t}\n\tsubSecondPrecision := false\n\tif config[fluentdSubSecondPrecision] != \"\" {\n\t\tsubSecondPrecision, err = strconv.ParseBool(config[fluentdSubSecondPrecision])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid sub second precision (%s)\", err, config[fluentdSubSecondPrecision])\n\t\t}\n\t}\n\trequestAck := false\n\tif config[fluentRequestAck] != \"\" {\n\t\trequestAck, err = strconv.ParseBool(config[fluentRequestAck])\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"error occurs %w,invalid request ack (%s)\", err, config[fluentRequestAck])\n\t\t}\n\t}\n\tresult = fluent.Config{\n\t\tFluentPort:             location.port,\n\t\tFluentHost:             location.host,\n\t\tFluentNetwork:          location.protocol,\n\t\tFluentSocketPath:       location.path,\n\t\tBufferLimit:            bufferLimit,\n\t\tRetryWait:              retryWait,\n\t\tMaxRetry:               maxRetries,\n\t\tAsync:                  async,\n\t\tAsyncReconnectInterval: asyncReconnectInterval,\n\t\tSubSecondPrecision:     subSecondPrecision,\n\t\tRequestAck:             requestAck,\n\t}\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/logging/fluentd_logger_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/fluent/fluent-logger-golang/fluent\"\n)\n\nfunc TestParseAddress(t *testing.T) {\n\ttype args struct {\n\t\taddress string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    *fluentdLocation\n\t\twantErr bool\n\t}{\n\t\t{name: \"empty\", args: args{address: \"\"}, want: &fluentdLocation{protocol: \"tcp\", host: \"127.0.0.1\", port: 24224}, wantErr: false},\n\t\t{name: \"unix\", args: args{address: \"unix:///var/run/fluentd/fluentd.sock\"}, want: &fluentdLocation{protocol: \"unix\", path: \"/var/run/fluentd/fluentd.sock\"}, wantErr: false},\n\t\t{name: \"tcp\", args: args{address: \"tcp://127.0.0.1:24224\"}, want: &fluentdLocation{protocol: \"tcp\", host: \"127.0.0.1\", port: 24224}, wantErr: false},\n\t\t{name: \"tcpWithPath\", args: args{address: \"tcp://127.0.0.1:24224/1234\"}, want: nil, wantErr: true},\n\t\t{name: \"unixWithEmpty\", args: args{address: \"unix://\"}, want: nil, wantErr: true},\n\t\t{name: \"invalidPath\", args: args{address: \"://asd123\"}, want: nil, wantErr: true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := parseAddress(tt.args.address)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"parseAddress() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"parseAddress() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestValidateFluentdLoggerOpts(t *testing.T) {\n\ttype args struct {\n\t\tconfig map[string]string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t// TODO: Add test cases.\n\t\t{name: \"empty\", args: args{config: map[string]string{}}, wantErr: false},\n\t\t{name: \"invalid\", args: args{config: map[string]string{\"foo\": \"bar\"}}, wantErr: true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif err := ValidateFluentdLoggerOpts(tt.args.config); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ValidateFluentdLoggerOpts() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseFluentdConfig(t *testing.T) {\n\ttype args struct {\n\t\tconfig map[string]string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    fluent.Config\n\t\twantErr bool\n\t}{\n\t\t{\"DefaultLocation\", args{\n\t\t\tconfig: map[string]string{}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputLocation\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentAddress: \"tcp://127.0.0.1:123\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             123,\n\t\t\t\tFluentHost:             \"127.0.0.1\",\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InvalidLocation\", args{config: map[string]string{fluentAddress: \"://asd123\"}}, fluent.Config{}, true},\n\t\t{\"InputAsyncOption\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentdAsync: \"true\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  true,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputAsyncReconnectOption\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentdAsyncReconnectInterval: \"100ms\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 100,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputBufferLimitOption\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentdBufferLimit: \"1000\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            1000,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputRetryWaitOption\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentdRetryWait: \"10s\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              10000,\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputMaxRetriesOption\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentdMaxRetries: \"100\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               100,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputSubSecondPrecision\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentdSubSecondPrecision: \"true\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     true,\n\t\t\t\tRequestAck:             false}, false},\n\t\t{\"InputRequestAck\", args{\n\t\t\tconfig: map[string]string{\n\t\t\t\tfluentRequestAck: \"true\",\n\t\t\t}},\n\t\t\tfluent.Config{\n\t\t\t\tFluentPort:             defaultPort,\n\t\t\t\tFluentHost:             defaultHost,\n\t\t\t\tFluentNetwork:          defaultProtocol,\n\t\t\t\tFluentSocketPath:       \"\",\n\t\t\t\tBufferLimit:            defaultBufferLimit,\n\t\t\t\tRetryWait:              int(defaultRetryWait),\n\t\t\t\tMaxRetry:               defaultMaxRetries,\n\t\t\t\tAsync:                  false,\n\t\t\t\tAsyncReconnectInterval: 0,\n\t\t\t\tSubSecondPrecision:     false,\n\t\t\t\tRequestAck:             true}, false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := parseFluentdConfig(tt.args.config)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"parseFluentdConfig() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"parseFluentdConfig() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/logging/journald_logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"sync\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/coreos/go-systemd/v22/journal\"\n\t\"github.com/docker/cli/templates\"\n\ttimetypes \"github.com/docker/docker/api/types/time\"\n\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nvar JournalDriverLogOpts = []string{\n\tTag,\n\tEnv,\n\tLabels,\n}\n\nfunc JournalLogOptsValidate(logOptMap map[string]string) error {\n\tfor key := range logOptMap {\n\t\tif !strutil.InStringSlice(JournalDriverLogOpts, key) {\n\t\t\tlog.L.Warnf(\"log-opt %s is ignored for journald log driver\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\ntype JournaldLogger struct {\n\tOpts    map[string]string\n\tvars    map[string]string\n\tAddress string\n}\n\ntype identifier struct {\n\tID        string\n\tFullID    string\n\tNamespace string\n}\n\nfunc (journaldLogger *JournaldLogger) Init(dataStore, ns, id string) error {\n\treturn nil\n}\n\nfunc (journaldLogger *JournaldLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {\n\tif !journal.Enabled() {\n\t\treturn errors.New(\"the local systemd journal is not available for logging\")\n\t}\n\tshortID := config.ID[:12]\n\tvar syslogIdentifier string\n\tif _, ok := journaldLogger.Opts[Tag]; !ok {\n\t\tsyslogIdentifier = shortID\n\t} else {\n\t\tvar tmpl *template.Template\n\t\tvar err error\n\t\ttmpl, err = templates.Parse(journaldLogger.Opts[Tag])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif tmpl != nil {\n\t\t\tidn := identifier{\n\t\t\t\tID:        shortID,\n\t\t\t\tFullID:    config.ID,\n\t\t\t\tNamespace: config.Namespace,\n\t\t\t}\n\t\t\tvar b bytes.Buffer\n\t\t\tif err := tmpl.Execute(&b, idn); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsyslogIdentifier = b.String()\n\t\t}\n\t}\n\n\tclient, ctx, cancel, err := clientutil.NewClient(ctx, config.Namespace, journaldLogger.Address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tcancel()\n\t\tclient.Close()\n\t}()\n\tcontainerID := config.ID\n\tcontainer, err := client.LoadContainer(ctx, containerID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainerLabels, err := container.Labels(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainerInfo, err := container.Info(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// construct log metadata for the container\n\tvars := map[string]string{\n\t\t\"SYSLOG_IDENTIFIER\": syslogIdentifier,\n\t\t\"CONTAINER_TAG\":     syslogIdentifier,\n\t\t\"CONTAINER_ID\":      shortID,\n\t\t\"CONTAINER_ID_FULL\": containerID,\n\t\t\"CONTAINER_NAME\":    containerutil.GetContainerName(containerLabels),\n\t\t\"IMAGE_NAME\":        containerInfo.Image,\n\t}\n\tjournaldLogger.vars = vars\n\treturn nil\n}\n\nfunc (journaldLogger *JournaldLogger) Process(stdout <-chan string, stderr <-chan string) error {\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tf := func(wg *sync.WaitGroup, dataChan <-chan string, pri journal.Priority, vars map[string]string) {\n\t\tdefer wg.Done()\n\t\tfor log := range dataChan {\n\t\t\tjournal.Send(log, pri, vars)\n\t\t}\n\t}\n\t// forward both stdout and stderr to the journal\n\tgo f(&wg, stdout, journal.PriInfo, journaldLogger.vars)\n\tgo f(&wg, stderr, journal.PriErr, journaldLogger.vars)\n\n\twg.Wait()\n\treturn nil\n}\n\nfunc (journaldLogger *JournaldLogger) PostProcess() error {\n\treturn nil\n}\n\n// Exec's `journalctl` with the provided arguments and hooks it up\n// to the given stdout/stderr streams.\nfunc FetchLogs(stdout, stderr io.Writer, journalctlArgs []string, stopChannel chan os.Signal) error {\n\tjournalctl, err := exec.LookPath(\"journalctl\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not find `journalctl` executable in PATH: %w\", err)\n\t}\n\n\tcmd := exec.Command(journalctl, journalctlArgs...)\n\tcmd.Stdout = stdout\n\tcmd.Stderr = stderr\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"failed to start journalctl command with args %#v: %w\", journalctlArgs, err)\n\t}\n\n\t// Setup killing goroutine:\n\tkilled := false\n\tgo func() {\n\t\t<-stopChannel\n\t\tkilled = true\n\t\tlog.L.Debugf(\"killing journalctl logs process with PID: %#v\", cmd.Process.Pid)\n\t\tcmd.Process.Kill()\n\t}()\n\n\terr = cmd.Wait()\n\tif exitError, ok := err.(*exec.ExitError); ok {\n\t\tif !killed && exitError.ExitCode() != 0 {\n\t\t\treturn fmt.Errorf(\"journalctl command exited with non-zero exit code (%d): %w\", exitError.ExitCode(), exitError)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Formats command line arguments for `journalctl` with the provided log viewing options and\n// exec's and redirects `journalctl`s outputs to the provided io.Writers.\nfunc viewLogsJournald(lvopts LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error {\n\tif !checkExecutableAvailableInPath(\"journalctl\") {\n\t\treturn fmt.Errorf(\"`journalctl` executable could not be found in PATH, cannot use Journald to view logs\")\n\t}\n\tshortID := lvopts.ContainerID[:12]\n\tvar journalctlArgs = []string{fmt.Sprintf(\"SYSLOG_IDENTIFIER=%s\", shortID), \"--output=cat\"}\n\tif lvopts.Follow {\n\t\tjournalctlArgs = append(journalctlArgs, \"-f\")\n\t}\n\tif lvopts.Since != \"\" {\n\t\t// using GetTimestamp from moby to keep time format consistency\n\t\tts, err := timetypes.GetTimestamp(lvopts.Since, time.Now())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid value for \\\"since\\\": %w\", err)\n\t\t}\n\t\tdate, err := prepareJournalCtlDate(ts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tjournalctlArgs = append(journalctlArgs, \"--since\", date)\n\t}\n\tif lvopts.Timestamps {\n\t\tlog.L.Warnf(\"unsupported Timestamps option for journald driver\")\n\t}\n\tif lvopts.Until != \"\" {\n\t\t// using GetTimestamp from moby to keep time format consistency\n\t\tts, err := timetypes.GetTimestamp(lvopts.Until, time.Now())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid value for \\\"until\\\": %w\", err)\n\t\t}\n\t\tdate, err := prepareJournalCtlDate(ts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tjournalctlArgs = append(journalctlArgs, \"--until\", date)\n\t}\n\treturn FetchLogs(stdout, stderr, journalctlArgs, stopChannel)\n}\n\nfunc prepareJournalCtlDate(t string) (string, error) {\n\ti, err := strconv.ParseInt(t, 10, 64)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ttm := time.Unix(i, 0)\n\ts := tm.Format(\"2006-01-02 15:04:05\")\n\treturn s, nil\n}\n"
  },
  {
    "path": "pkg/logging/json_logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/fahedouch/go-logrotate\"\n\t\"github.com/fsnotify/fsnotify\"\n\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging/jsonfile\"\n\t\"github.com/containerd/nerdctl/v2/pkg/logging/tail\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nvar JSONDriverLogOpts = []string{\n\tLogPath,\n\tMaxSize,\n\tMaxFile,\n\tEnv,\n\tLabels,\n}\n\ntype JSONLogger struct {\n\tOpts   map[string]string\n\tlogger *logrotate.Logger\n}\n\nfunc JSONFileLogOptsValidate(logOptMap map[string]string) error {\n\tfor key := range logOptMap {\n\t\tif !strutil.InStringSlice(JSONDriverLogOpts, key) {\n\t\t\tlog.L.Warnf(\"log-opt %s is ignored for json-file log driver\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (jsonLogger *JSONLogger) Init(dataStore, ns, id string) error {\n\t// Initialize the log file (https://github.com/containerd/nerdctl/issues/1071)\n\tvar jsonFilePath string\n\tif logPath, ok := jsonLogger.Opts[LogPath]; ok {\n\t\tjsonFilePath = logPath\n\t} else {\n\t\tjsonFilePath = jsonfile.Path(dataStore, ns, id)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(jsonFilePath), 0700); err != nil {\n\t\treturn err\n\t}\n\tif _, err := os.Stat(jsonFilePath); errors.Is(err, os.ErrNotExist) {\n\t\tif writeErr := filesystem.WriteFile(jsonFilePath, []byte{}, 0600); writeErr != nil {\n\t\t\treturn writeErr\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (jsonLogger *JSONLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {\n\tvar jsonFilePath string\n\tif logPath, ok := jsonLogger.Opts[LogPath]; ok {\n\t\tjsonFilePath = logPath\n\t} else {\n\t\tjsonFilePath = jsonfile.Path(dataStore, config.Namespace, config.ID)\n\t}\n\tl := &logrotate.Logger{\n\t\tFilename: jsonFilePath,\n\t}\n\t// MaxBytes is the maximum size in bytes of the log file before it gets\n\t// rotated. If not set, it defaults to 100 MiB.\n\t// see: https://github.com/fahedouch/go-logrotate/blob/6a8beddaea39b2b9c77109d7fa2fe92053c063e5/logrotate.go#L500\n\tif capacity, ok := jsonLogger.Opts[MaxSize]; ok {\n\t\tvar capVal int64\n\t\tvar err error\n\t\tcapVal, err = units.FromHumanSize(capacity)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif capVal <= 0 {\n\t\t\treturn fmt.Errorf(\"max-size must be a positive number\")\n\t\t}\n\t\tl.MaxBytes = capVal\n\t}\n\tmaxFile := 1\n\tif maxFileString, ok := jsonLogger.Opts[MaxFile]; ok {\n\t\tvar err error\n\t\tmaxFile, err = strconv.Atoi(maxFileString)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif maxFile < 1 {\n\t\t\treturn fmt.Errorf(\"max-file cannot be less than 1\")\n\t\t}\n\t}\n\t// MaxBackups does not include file to write logs to\n\tl.MaxBackups = maxFile - 1\n\tjsonLogger.logger = l\n\treturn nil\n}\n\nfunc (jsonLogger *JSONLogger) Process(stdout <-chan string, stderr <-chan string) error {\n\treturn jsonfile.Encode(stdout, stderr, jsonLogger.logger)\n}\n\nfunc (jsonLogger *JSONLogger) PostProcess() error {\n\treturn nil\n}\n\n// Loads log entries from logfiles produced by the json-logger driver and forwards\n// them to the provided io.Writers after applying the provided logging options.\nfunc viewLogsJSONFile(lvopts LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error {\n\tlogFilePath := jsonfile.Path(lvopts.DatastoreRootPath, lvopts.Namespace, lvopts.ContainerID)\n\tif _, err := os.Stat(logFilePath); err != nil {\n\t\t// FIXME: this is a workaround for the actual issue, not a real solution\n\t\t// https://github.com/containerd/nerdctl/issues/3187\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\tlog.L.Warnf(\"Racing log file creation. Pausing briefly.\")\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t\t_, err = os.Stat(logFilePath)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to stat JSON log file %w\", err)\n\t\t}\n\t}\n\n\treturn viewLogsJSONFileDirect(lvopts, logFilePath, stdout, stderr, stopChannel)\n}\n\n// Loads JSON log entries directly from the provided JSON log file.\n// If `LogViewOptions.Follow` is provided, it will refresh and re-read the file until\n// it receives something through the stopChannel.\nfunc viewLogsJSONFileDirect(lvopts LogViewOptions, jsonLogFilePath string, stdout, stderr io.Writer, stopChannel chan os.Signal) error {\n\tfin, err := os.OpenFile(jsonLogFilePath, os.O_RDONLY, 0400)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() { fin.Close() }()\n\n\t// Search start point based on tail line.\n\tstart, err := tail.FindTailLineStartIndex(fin, lvopts.Tail)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to tail %d lines of JSON logfile %q: %w\", lvopts.Tail, jsonLogFilePath, err)\n\t}\n\n\tif _, err := fin.Seek(start, io.SeekStart); err != nil {\n\t\treturn fmt.Errorf(\"failed to seek in log file %q from %d position: %w\", jsonLogFilePath, start, err)\n\t}\n\n\tlimitedMode := (lvopts.Tail > 0) && (!lvopts.Follow)\n\tlimitedNum := lvopts.Tail\n\tvar stop bool\n\tvar watcher *fsnotify.Watcher\n\tbaseName := filepath.Base(jsonLogFilePath)\n\tdir := filepath.Dir(jsonLogFilePath)\n\tretryTimes := 2\n\tbackBytes := 0\n\n\tfor {\n\t\tselect {\n\t\tcase <-stopChannel:\n\t\t\tlog.L.Debug(\"received stop signal while re-reading JSON logfile, returning\")\n\t\t\treturn nil\n\t\tdefault:\n\t\t\tif stop || (limitedMode && limitedNum == 0) {\n\t\t\t\tlog.L.Debugf(\"finished parsing log JSON filefile, path: %s\", jsonLogFilePath)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif line, err := jsonfile.Decode(stdout, stderr, fin, lvopts.Timestamps, lvopts.Since, lvopts.Until); err != nil {\n\t\t\t\tif len(line) > 0 {\n\t\t\t\t\ttime.Sleep(5 * time.Millisecond)\n\t\t\t\t\tif retryTimes == 0 {\n\t\t\t\t\t\tlog.L.Infof(\"finished parsing log JSON filefile, path: %s, line: %s\", jsonLogFilePath, string(line))\n\t\t\t\t\t\treturn fmt.Errorf(\"error occurred while doing read of JSON logfile %q: %w, retryTimes: %d\", jsonLogFilePath, err, retryTimes)\n\t\t\t\t\t}\n\t\t\t\t\tretryTimes--\n\t\t\t\t\tbackBytes = len(line)\n\t\t\t\t} else {\n\t\t\t\t\treturn fmt.Errorf(\"error occurred while doing read of JSON logfile %q: %w\", jsonLogFilePath, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tretryTimes = 2\n\t\t\t\tbackBytes = 0\n\t\t\t}\n\n\t\t\tif lvopts.Follow {\n\t\t\t\t// Get the current file handler's seek.\n\t\t\t\tlastPos, err := fin.Seek(int64(-backBytes), io.SeekCurrent)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error occurred while trying to seek JSON logfile %q at position %d: %s\", jsonLogFilePath, lastPos, err)\n\t\t\t\t}\n\n\t\t\t\tif watcher == nil {\n\t\t\t\t\t// Initialize the watcher if it has not been initialized yet.\n\t\t\t\t\tif watcher, err = NewLogFileWatcher(dir); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tdefer watcher.Close()\n\t\t\t\t\t// If we just created the watcher, try again to read as we might have missed\n\t\t\t\t\t// the event.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvar recreated bool\n\t\t\t\t// Wait until the next log change.\n\t\t\t\trecreated, err = startTail(context.Background(), baseName, watcher)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif recreated {\n\t\t\t\t\tnewF, err := openFileShareDelete(jsonLogFilePath)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\t\t\t\t//If the user application outputs logs too quickly,\n\t\t\t\t\t\t\t//There is a slight possibility that nerdctl has just rotated the log file,\n\t\t\t\t\t\t\t//try opening it once more.\n\t\t\t\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnewF, err = openFileShareDelete(jsonLogFilePath)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"failed to open JSON logfile %q: %w\", jsonLogFilePath, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfin.Close()\n\t\t\t\t\tfin = newF\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstop = true\n\t\t\t// Give the OS a second to breathe before re-opening the file:\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/logging/json_logger_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestReadRotatedJSONLog(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"windows implementation does not seem to work right now and should be fixed: https://github.com/containerd/nerdctl/issues/3554\")\n\t}\n\tfile, err := os.CreateTemp(tmpDir, \"logfile\")\n\tif err != nil {\n\t\tt.Errorf(\"unable to create temp file, error: %s\", err.Error())\n\t}\n\tstdoutBuf := &bytes.Buffer{}\n\tstderrBuf := &bytes.Buffer{}\n\tcontainerStopped := make(chan os.Signal)\n\t// Start to follow the container's log.\n\tfileName := file.Name()\n\tgo func() {\n\t\tlvOpts := LogViewOptions{\n\t\t\tFollow:  true,\n\t\t\tLogPath: fileName,\n\t\t}\n\t\tviewLogsJSONFileDirect(lvOpts, file.Name(), stdoutBuf, stderrBuf, containerStopped)\n\t}()\n\n\t// log in stdout\n\texpectedStdout := \"line0\\nline1\\nline2\\nline3\\nline4\\nline5\\nline6\\nline7\\nline8\\nline9\\n\"\n\tdir := filepath.Dir(file.Name())\n\tbaseName := filepath.Base(file.Name())\n\n\t// Write 10 lines to log file.\n\t// Let ReadLogs start.\n\ttime.Sleep(50 * time.Millisecond)\n\n\ttype logContent struct {\n\t\tLog    string `json:\"log\"`\n\t\tStream string `json:\"stream\"`\n\t\tTime   string `json:\"time\"`\n\t}\n\n\tfor line := 0; line < 10; line++ {\n\t\t// Write the first three lines to log file\n\t\tlog := logContent{}\n\t\tlog.Log = fmt.Sprintf(\"line%d\\n\", line)\n\t\tlog.Stream = \"stdout\"\n\t\tlog.Time = time.Now().Format(time.RFC3339Nano)\n\t\ttime.Sleep(1 * time.Millisecond)\n\t\tlogData, _ := json.Marshal(log)\n\t\tfile.Write(logData)\n\t\tfile.Write([]byte(\"\\n\"))\n\n\t\tif line == 5 {\n\t\t\tfile.Close()\n\t\t\t// Pretend to rotate the log.\n\t\t\trotatedName := fmt.Sprintf(\"%s.%s\", baseName, time.Now().Format(\"20060102-150405\"))\n\t\t\trotatedName = filepath.Join(dir, rotatedName)\n\t\t\tif err := os.Rename(filepath.Join(dir, baseName), rotatedName); err != nil {\n\t\t\t\tt.Errorf(\"failed to rotate log %q to %q, error: %s\", file.Name(), rotatedName, err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t\tnewF := filepath.Join(dir, baseName)\n\t\t\tif file, err = os.Create(newF); err != nil {\n\t\t\t\tt.Errorf(\"unable to create new log file, error: %s\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// Finished writing into the file, close it, so we can delete it later.\n\terr = file.Close()\n\tif err != nil {\n\t\tt.Errorf(\"could not close file, error: %s\", err.Error())\n\t}\n\n\ttime.Sleep(2 * time.Second)\n\t// Make the function ReadLogs end.\n\tclose(containerStopped)\n\n\tif expectedStdout != stdoutBuf.String() {\n\t\tt.Errorf(\"expected: %s, actual: %s\", expectedStdout, stdoutBuf.String())\n\t}\n}\n\nfunc TestReadJSONLogs(t *testing.T) {\n\tfile, err := os.CreateTemp(\"\", \"TestFollowLogs\")\n\tif err != nil {\n\t\tt.Fatalf(\"unable to create temp file\")\n\t}\n\tdefer os.Remove(file.Name())\n\tfile.WriteString(`{\"log\":\"line1\\n\",\"stream\":\"stdout\",\"time\":\"2024-07-12T03:09:24.916296732Z\"}` + \"\\n\")\n\tfile.WriteString(`{\"log\":\"line2\\n\",\"stream\":\"stdout\",\"time\":\"2024-07-12T03:09:24.916296732Z\"}` + \"\\n\")\n\tfile.WriteString(`{\"log\":\"line3\\n\",\"stream\":\"stdout\",\"time\":\"2024-07-12T03:09:24.916296732Z\"}` + \"\\n\")\n\n\tstopChan := make(chan os.Signal)\n\ttestCases := []struct {\n\t\tname           string\n\t\tlogViewOptions LogViewOptions\n\t\texpected       string\n\t}{\n\t\t{\n\t\t\tname: \"default log options should output all lines\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    0,\n\t\t\t},\n\t\t\texpected: \"line1\\nline2\\nline3\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"using Tail 2 should output last 2 lines\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    2,\n\t\t\t},\n\t\t\texpected: \"line2\\nline3\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"using Tail 4 should output all lines when the log has less than 4 lines\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    4,\n\t\t\t},\n\t\t\texpected: \"line1\\nline2\\nline3\\n\",\n\t\t},\n\t\t{\n\t\t\tname: \"using Tail 0 should output all\",\n\t\t\tlogViewOptions: LogViewOptions{\n\t\t\t\tLogPath: file.Name(),\n\t\t\t\tTail:    0,\n\t\t\t},\n\t\t\texpected: \"line1\\nline2\\nline3\\n\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tstdoutBuf := bytes.NewBuffer(nil)\n\t\t\tstderrBuf := bytes.NewBuffer(nil)\n\t\t\terr = viewLogsJSONFileDirect(tc.logViewOptions, file.Name(), stdoutBuf, stderrBuf, stopChan)\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err.Error())\n\t\t\t}\n\t\t\tif stderrBuf.Len() > 0 {\n\t\t\t\tt.Fatalf(\"Stderr: %v\", stderrBuf.String())\n\t\t\t}\n\t\t\tif actual := stdoutBuf.String(); tc.expected != actual {\n\t\t\t\tt.Fatalf(\"Actual output does not match expected.\\nActual:  %v\\nExpected: %v\\n\", actual, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/logging/jsonfile/jsonfile.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage jsonfile\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\ttimetypes \"github.com/docker/docker/api/types/time\"\n\n\t\"github.com/containerd/log\"\n)\n\n// Entry is compatible with Docker \"json-file\" logs\ntype Entry struct {\n\tLog    string    `json:\"log,omitempty\"`    // line, including \"\\r\\n\"\n\tStream string    `json:\"stream,omitempty\"` // \"stdout\" or \"stderr\"\n\tTime   time.Time `json:\"time\"`             // e.g. \"2020-12-11T20:29:41.939902251Z\"\n}\n\nfunc Path(dataStore, ns, id string) string {\n\t// the file name corresponds to Docker\n\treturn filepath.Join(dataStore, \"containers\", ns, id, id+\"-json.log\")\n}\n\nfunc Encode(stdout <-chan string, stderr <-chan string, writer io.Writer) error {\n\tenc := json.NewEncoder(writer)\n\tvar encMu sync.Mutex\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tf := func(dataChan <-chan string, name string) {\n\t\tdefer wg.Done()\n\t\te := &Entry{\n\t\t\tStream: name,\n\t\t}\n\t\tfor logEntry := range dataChan {\n\t\t\te.Log = logEntry\n\t\t\te.Time = time.Now().UTC()\n\t\t\tencMu.Lock()\n\t\t\tencErr := enc.Encode(e)\n\t\t\tencMu.Unlock()\n\t\t\tif encErr != nil {\n\t\t\t\tlog.L.WithError(encErr).Errorf(\"failed to encode JSON\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tgo f(stdout, \"stdout\")\n\tgo f(stderr, \"stderr\")\n\twg.Wait()\n\treturn nil\n}\n\nfunc writeEntry(e *Entry, stdout, stderr io.Writer, refTime time.Time, timestamps bool, since string, until string) error {\n\toutput := []byte{}\n\n\tif since != \"\" {\n\t\tts, err := timetypes.GetTimestamp(since, refTime)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid value for \\\"since\\\": %w\", err)\n\t\t}\n\t\tv := strings.Split(ts, \".\")\n\t\ti, err := strconv.ParseInt(v[0], 10, 64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif e.Time.Before(time.Unix(i, 0)) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif until != \"\" {\n\t\tts, err := timetypes.GetTimestamp(until, refTime)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid value for \\\"until\\\": %w\", err)\n\t\t}\n\t\tv := strings.Split(ts, \".\")\n\t\ti, err := strconv.ParseInt(v[0], 10, 64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif e.Time.After(time.Unix(i, 0)) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif timestamps {\n\t\toutput = append(output, []byte(e.Time.Format(time.RFC3339Nano))...)\n\t\toutput = append(output, ' ')\n\t}\n\n\toutput = append(output, []byte(e.Log)...)\n\n\tvar writeTo io.Writer\n\tswitch e.Stream {\n\tcase \"stdout\":\n\t\twriteTo = stdout\n\tcase \"stderr\":\n\t\twriteTo = stderr\n\tdefault:\n\t\tlog.L.Errorf(\"unknown stream name %q, entry=%+v\", e.Stream, e)\n\t}\n\n\tif writeTo != nil {\n\t\t_, err := writeTo.Write(output)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc Decode(stdout, stderr io.Writer, r io.Reader, timestamps bool, since string, until string) ([]byte, error) {\n\tdec := json.NewDecoder(r)\n\tnow := time.Now()\n\tfor {\n\t\tvar e Entry\n\t\tif err := dec.Decode(&e); err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\tline, err := io.ReadAll(dec.Buffered())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn line, err\n\t\t}\n\n\t\t// Write out the entry directly\n\t\terr := writeEntry(&e, stdout, stderr, now, timestamps, since, until)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"error while writing log entry to output stream\")\n\t\t}\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/logging/log_viewer.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels/k8slabels\"\n)\n\n// Type alias for functions which write out logs to the provided stdout/stderr Writers.\n// Depending on the provided `LogViewOptions.Follow` option, the function may block\n// indefinitely until something is sent through the `stopChannel`.\ntype LogViewerFunc func(lvopts LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error\n\nvar logViewers = make(map[string]LogViewerFunc)\n\n// Registers a LogViewerFunc for the\nfunc RegisterLogViewer(driverName string, lvfn LogViewerFunc) {\n\tif v, ok := logViewers[driverName]; ok {\n\t\tlog.L.Warnf(\"A LogViewerFunc with name %q has already been registered: %#v, overriding with %#v either way\", driverName, v, lvfn)\n\t}\n\tlogViewers[driverName] = lvfn\n}\n\nfunc init() {\n\tRegisterLogViewer(\"json-file\", viewLogsJSONFile)\n\tRegisterLogViewer(\"journald\", viewLogsJournald)\n\tRegisterLogViewer(\"cri\", viewLogsCRI)\n}\n\n// Returns a LogViewerFunc for the provided logging driver name.\nfunc getLogViewer(driverName string) (LogViewerFunc, error) {\n\tlv, ok := logViewers[driverName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"no log viewer type registered for logging driver %q\", driverName)\n\t}\n\treturn lv, nil\n}\n\n// Set of options passable to log viewers.\ntype LogViewOptions struct {\n\t// Identifier (ID) of the container and namespace it's in.\n\tContainerID string\n\tNamespace   string\n\n\t// Absolute path to the nerdctl datastore's root.\n\tDatastoreRootPath string\n\n\t// LogPath specify the log path for container created via CRI\n\tLogPath string\n\n\t// Whether or not to follow the output of the container logs.\n\tFollow bool\n\n\t// Whether or not to print timestampts for each line.\n\tTimestamps bool\n\n\t// Uint representing the number of most recent log entries to display. 0 = \"all\".\n\tTail uint\n\n\t// Start/end timestampts to filter logs by.\n\tSince string\n\tUntil string\n\n\t// Details enables showing extra details(env and label) in logs.\n\tDetails bool\n\n\t// DetailPrefix is the prefix added when Details is enabled.\n\tDetailPrefix *string\n}\n\nfunc (lvo *LogViewOptions) Validate() error {\n\tif lvo.ContainerID == \"\" || lvo.Namespace == \"\" {\n\t\treturn fmt.Errorf(\"log viewing options require a ContainerID and Namespace: %#v\", lvo)\n\t}\n\n\tif lvo.DatastoreRootPath == \"\" || !filepath.IsAbs(lvo.DatastoreRootPath) {\n\t\tabs, err := filepath.Abs(lvo.DatastoreRootPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.L.Warnf(\"given relative datastore path %q, transformed it to absolute path: %q\", lvo.DatastoreRootPath, abs)\n\t\tlvo.DatastoreRootPath = abs\n\t}\n\n\treturn nil\n}\n\n// Implements functionality for loading the logging configuration and\n// fetching/outputting container logs based on its internal LogViewOptions.\ntype ContainerLogViewer struct {\n\t// Logging configuration.\n\tloggingConfig LogConfig\n\n\t// Log viewing options and filters.\n\tlogViewingOptions LogViewOptions\n\n\t// Channel to send stop events to the viewer.\n\tstopChannel chan os.Signal\n}\n\n// Validates the given LogViewOptions, loads the logging config for the\n// given container and returns a ContainerLogViewer.\nfunc InitContainerLogViewer(containerLabels map[string]string, lvopts LogViewOptions, stopChannel chan os.Signal, experimental bool) (contlv *ContainerLogViewer, err error) {\n\tvar lcfg LogConfig\n\tif _, ok := containerLabels[k8slabels.ContainerType]; ok {\n\t\tlcfg.Driver = \"cri\"\n\t} else {\n\t\tif err := lvopts.Validate(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid LogViewOptions provided (%#v): %w\", lvopts, err)\n\t\t}\n\n\t\tlcfg, err = LoadLogConfig(lvopts.DatastoreRootPath, lvopts.Namespace, lvopts.ContainerID)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to load logging config: %w\", err)\n\t\t}\n\t}\n\n\tif lcfg.Driver == \"cri\" && !experimental {\n\t\treturn nil, fmt.Errorf(\"the `cri` log viewer requires nerdctl to be running in experimental mode\")\n\t}\n\n\tif lcfg.Driver == \"none\" {\n\t\treturn nil, fmt.Errorf(\"log type `none` was selected, nothing to log\")\n\t}\n\n\tlv := &ContainerLogViewer{\n\t\tloggingConfig:     lcfg,\n\t\tlogViewingOptions: lvopts,\n\t\tstopChannel:       stopChannel,\n\t}\n\n\treturn lv, nil\n}\n\n// Prints all logs for this LogViewer's containers to the provided io.Writers.\nfunc (lv *ContainerLogViewer) PrintLogsTo(stdout, stderr io.Writer) error {\n\tif lv.logViewingOptions.Details {\n\t\tif lv.logViewingOptions.DetailPrefix != nil {\n\t\t\tprefix := *lv.logViewingOptions.DetailPrefix + \" \"\n\t\t\tstdout = NewDetailWriter(stdout, prefix)\n\t\t\tstderr = NewDetailWriter(stderr, prefix)\n\t\t}\n\n\t}\n\tviewerFunc, err := getLogViewer(lv.loggingConfig.Driver)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn viewerFunc(lv.logViewingOptions, stdout, stderr, lv.stopChannel)\n}\n\n// Convenience wrapper for exec.LookPath.\nfunc checkExecutableAvailableInPath(executable string) bool {\n\t_, err := exec.LookPath(executable)\n\treturn err == nil\n}\n"
  },
  {
    "path": "pkg/logging/logging.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/muesli/cancelreader\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nconst (\n\t// MagicArgv1 is the magic argv1 for the containerd runtime v2 logging plugin mode.\n\tMagicArgv1 = \"_NERDCTL_INTERNAL_LOGGING\"\n\tLogPath    = \"log-path\"\n\tMaxSize    = \"max-size\"\n\tMaxFile    = \"max-file\"\n\tTag        = \"tag\"\n\tEnv        = \"env\"\n\tLabels     = \"labels\"\n)\n\ntype Driver interface {\n\tInit(dataStore, ns, id string) error\n\tPreProcess(ctx context.Context, dataStore string, config *logging.Config) error\n\tProcess(stdout <-chan string, stderr <-chan string) error\n\tPostProcess() error\n}\n\ntype DriverFactory func(map[string]string, string) (Driver, error)\ntype LogOptsValidateFunc func(logOptMap map[string]string) error\n\nvar drivers = make(map[string]DriverFactory)\nvar driversLogOptsValidateFunctions = make(map[string]LogOptsValidateFunc)\n\nfunc ValidateLogOpts(logDriver string, logOpts map[string]string) error {\n\tif value, ok := driversLogOptsValidateFunctions[logDriver]; ok && value != nil {\n\t\treturn value(logOpts)\n\t}\n\treturn nil\n}\n\nfunc RegisterDriver(name string, f DriverFactory, validateFunc LogOptsValidateFunc) {\n\tdrivers[name] = f\n\tdriversLogOptsValidateFunctions[name] = validateFunc\n}\n\nfunc Drivers() []string {\n\tvar ss []string // nolint: prealloc\n\tfor f := range drivers {\n\t\tss = append(ss, f)\n\t}\n\tsort.Strings(ss)\n\treturn ss\n}\n\nfunc GetDriver(name string, opts map[string]string, address string) (Driver, error) {\n\tdriverFactory, ok := drivers[name]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unknown logging driver %q: %w\", name, errdefs.ErrNotFound)\n\t}\n\treturn driverFactory(opts, address)\n}\n\nfunc init() {\n\tRegisterDriver(\"none\", func(opts map[string]string, address string) (Driver, error) {\n\t\treturn &NoneLogger{}, nil\n\t}, NoneLogOptsValidate)\n\tRegisterDriver(\"json-file\", func(opts map[string]string, address string) (Driver, error) {\n\t\treturn &JSONLogger{Opts: opts}, nil\n\t}, JSONFileLogOptsValidate)\n\tRegisterDriver(\"journald\", func(opts map[string]string, address string) (Driver, error) {\n\t\treturn &JournaldLogger{Opts: opts, Address: address}, nil\n\t}, JournalLogOptsValidate)\n\tRegisterDriver(\"fluentd\", func(opts map[string]string, address string) (Driver, error) {\n\t\treturn &FluentdLogger{Opts: opts}, nil\n\t}, FluentdLogOptsValidate)\n\tRegisterDriver(\"syslog\", func(opts map[string]string, address string) (Driver, error) {\n\t\treturn &SyslogLogger{Opts: opts}, nil\n\t}, SyslogOptsValidate)\n}\n\n// Main is the entrypoint for the containerd runtime v2 logging plugin mode.\n//\n// Should be called only if argv1 == MagicArgv1.\nfunc Main(argv2 string) error {\n\tfn, err := loggerFunc(argv2)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogging.Run(fn)\n\treturn nil\n}\n\n// LogConfig is marshalled as \"log-config.json\"\ntype LogConfig struct {\n\tDriver  string            `json:\"driver\"`\n\tOpts    map[string]string `json:\"opts,omitempty\"`\n\tLogURI  string            `json:\"-\"`\n\tAddress string            `json:\"address\"`\n}\n\n// LogConfigFilePath returns the path of log-config.json\nfunc LogConfigFilePath(dataStore, ns, id string) string {\n\treturn filepath.Join(dataStore, \"containers\", ns, id, \"log-config.json\")\n}\n\n// LoadLogConfig loads the log-config.json for the afferrent container store\nfunc LoadLogConfig(dataStore, ns, id string) (LogConfig, error) {\n\tlogConfig := LogConfig{}\n\n\tlogConfigFilePath := LogConfigFilePath(dataStore, ns, id)\n\tlogConfigData, err := filesystem.ReadFile(logConfigFilePath)\n\tif err != nil {\n\t\treturn logConfig, fmt.Errorf(\"failed to read log config file %q: %w\", logConfigFilePath, err)\n\t}\n\n\terr = json.Unmarshal(logConfigData, &logConfig)\n\tif err != nil {\n\t\treturn logConfig, fmt.Errorf(\"failed to load JSON logging config file %q: %w\", logConfigFilePath, err)\n\t}\n\treturn logConfig, nil\n}\n\nfunc getLockPath(dataStore, ns, id string) string {\n\treturn filepath.Join(dataStore, \"containers\", ns, id, \"logger-lock\")\n}\n\n// WaitForLogger waits until the logger has finished executing and processing container logs\nfunc WaitForLogger(dataStore, ns, id string) error {\n\treturn filesystem.WithLock(getLockPath(dataStore, ns, id), func() error {\n\t\treturn nil\n\t})\n}\n\nfunc getContainerWait(ctx context.Context, address string, config *logging.Config) (<-chan containerd.ExitStatus, error) {\n\tclient, err := containerd.New(strings.TrimPrefix(address, \"unix://\"), containerd.WithDefaultNamespace(config.Namespace))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcon, err := client.LoadContainer(ctx, config.ID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttask, err := con.Task(ctx, nil)\n\tif err == nil {\n\t\treturn task.Wait(ctx)\n\t}\n\tif !errdefs.IsNotFound(err) {\n\t\treturn nil, err\n\t}\n\n\t// If task was not found, it's possible that the container runtime is still being created.\n\t// Retry every 100ms.\n\tticker := time.NewTicker(100 * time.Millisecond)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, errors.New(\"timed out waiting for container task to start\")\n\t\tcase <-ticker.C:\n\t\t\ttask, err = con.Task(ctx, nil)\n\t\t\tif err != nil {\n\t\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn task.Wait(ctx)\n\t\t}\n\t}\n}\n\ntype ContainerWaitFunc func(ctx context.Context, address string, config *logging.Config) (<-chan containerd.ExitStatus, error)\n\nfunc loggingProcessAdapter(ctx context.Context, driver Driver, dataStore, address string, getContainerWait ContainerWaitFunc, config *logging.Config) error {\n\tif err := driver.PreProcess(ctx, dataStore, config); err != nil {\n\t\treturn err\n\t}\n\n\tstdoutR, err := cancelreader.NewReader(config.Stdout)\n\tif err != nil {\n\t\treturn err\n\t}\n\tstderrR, err := cancelreader.NewReader(config.Stderr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tgo func() {\n\t\t<-ctx.Done() // delivered on SIGTERM\n\t\tstdoutR.Cancel()\n\t\tstderrR.Cancel()\n\t}()\n\n\t// initialize goroutines to copy stdout and stderr streams to a closable pipe\n\tpipeStdoutR, pipeStdoutW := io.Pipe()\n\tpipeStderrR, pipeStderrW := io.Pipe()\n\tcopyStream := func(reader io.Reader, writer *io.PipeWriter) {\n\t\t// copy using a buffer of size 32K\n\t\tbuf := make([]byte, 32<<10)\n\t\t_, err := io.CopyBuffer(writer, reader, buf)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).Errorf(\"failed to copy stream: %s\", err)\n\t\t}\n\t}\n\tgo copyStream(stdoutR, pipeStdoutW)\n\tgo copyStream(stderrR, pipeStderrW)\n\n\tvar wg sync.WaitGroup\n\twg.Add(3)\n\tstdout := make(chan string, 10000)\n\tstderr := make(chan string, 10000)\n\tprocessLogFunc := func(reader io.Reader, dataChan chan string) {\n\t\tdefer wg.Done()\n\t\tdefer close(dataChan)\n\t\tr := bufio.NewReader(reader)\n\n\t\tvar err error\n\n\t\tfor err == nil {\n\t\t\tvar s string\n\t\t\ts, err = r.ReadString('\\n')\n\t\t\tif len(s) > 0 {\n\t\t\t\tdataChan <- s\n\t\t\t}\n\n\t\t\tif err != nil && err != io.EOF {\n\t\t\t\tlog.L.WithError(err).Error(\"failed to read log\")\n\t\t\t}\n\t\t}\n\t}\n\tgo processLogFunc(pipeStdoutR, stdout)\n\tgo processLogFunc(pipeStderrR, stderr)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tdriver.Process(stdout, stderr)\n\t}()\n\tgo func() {\n\t\t// close pipeStdoutW and pipeStderrW upon container exit\n\t\tdefer pipeStdoutW.Close()\n\t\tdefer pipeStderrW.Close()\n\n\t\texitCh, err := getContainerWait(ctx, address, config)\n\t\tif err != nil {\n\t\t\tlog.G(ctx).Errorf(\"failed to get container task wait channel: %v\", err)\n\t\t\treturn\n\t\t}\n\t\t<-exitCh\n\t}()\n\twg.Wait()\n\treturn driver.PostProcess()\n}\n\nfunc loggerFunc(dataStore string) (logging.LoggerFunc, error) {\n\tif dataStore == \"\" {\n\t\treturn nil, errors.New(\"got empty data store\")\n\t}\n\treturn func(ctx context.Context, config *logging.Config, ready func() error) error {\n\t\tif config.Namespace == \"\" || config.ID == \"\" {\n\t\t\treturn errors.New(\"got invalid config\")\n\t\t}\n\t\tlogConfigFilePath := LogConfigFilePath(dataStore, config.Namespace, config.ID)\n\t\tif _, err := os.Stat(logConfigFilePath); err == nil {\n\t\t\tlogConfig, err := LoadLogConfig(dataStore, config.Namespace, config.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdriver, err := GetDriver(logConfig.Driver, logConfig.Opts, logConfig.Address)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tloggerLock := getLockPath(dataStore, config.Namespace, config.ID)\n\n\t\t\t// the logger will obtain an exclusive lock on a file until the container is\n\t\t\t// stopped and the driver has finished processing all output,\n\t\t\t// so that waiting log viewers can be signalled when the process is complete.\n\t\t\treturn filesystem.WithLock(loggerLock, func() error {\n\t\t\t\tif err := ready(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t// getContainerWait is extracted as parameter to allow mocking in tests.\n\t\t\t\treturn loggingProcessAdapter(ctx, driver, dataStore, logConfig.Address, getContainerWait, config)\n\t\t\t})\n\t\t} else if !errors.Is(err, os.ErrNotExist) {\n\t\t\t// the file does not exist if the container was created with nerdctl < 0.20\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}, nil\n}\n\nfunc NewLogFileWatcher(dir string) (*fsnotify.Watcher, error) {\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create fsnotify watcher: %v\", err)\n\t}\n\tif err = watcher.Add(dir); err != nil {\n\t\twatcher.Close()\n\t\treturn nil, fmt.Errorf(\"failed to watch directory %q: %w\", dir, err)\n\t}\n\treturn watcher, nil\n}\n\n// startTail wait for the next log write.\n// the boolean value indicates if the log file was recreated;\n// the error is error happens during waiting new logs.\nfunc startTail(ctx context.Context, logName string, w *fsnotify.Watcher) (bool, error) {\n\terrRetry := 5\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn false, fmt.Errorf(\"context cancelled\")\n\t\tcase e := <-w.Events:\n\t\t\tswitch {\n\t\t\tcase e.Has(fsnotify.Write):\n\t\t\t\treturn false, nil\n\t\t\tcase e.Has(fsnotify.Create):\n\t\t\t\treturn filepath.Base(e.Name) == logName, nil\n\t\t\tdefault:\n\t\t\t\tlog.L.Debugf(\"Received unexpected fsnotify event: %v, retrying\", e)\n\t\t\t}\n\t\tcase err := <-w.Errors:\n\t\t\tlog.L.WithError(err).Debugf(\"Received fsnotify watch error, retrying unless no more retries left, retries: %d\", errRetry)\n\t\t\tif errRetry == 0 {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\terrRetry--\n\t\tcase <-time.After(logForceCheckPeriod):\n\t\t\treturn false, nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/logging/logging_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n)\n\ntype MockDriver struct {\n\tprocessed      bool\n\treceivedStdout []string\n\treceivedStderr []string\n}\n\nfunc (m *MockDriver) Init(dataStore, ns, id string) error {\n\treturn nil\n}\n\nfunc (m *MockDriver) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {\n\treturn nil\n}\n\nfunc (m *MockDriver) Process(stdout <-chan string, stderr <-chan string) error {\n\tfor line := range stdout {\n\t\tm.receivedStdout = append(m.receivedStdout, line)\n\t}\n\tfor line := range stderr {\n\t\tm.receivedStderr = append(m.receivedStderr, line)\n\t}\n\tm.processed = true\n\treturn nil\n}\n\nfunc (m *MockDriver) PostProcess() error {\n\treturn nil\n}\n\nfunc TestLoggingProcessAdapter(t *testing.T) {\n\t// Will process a normal String to stdout and a bigger one to stderr\n\tnormalString := generateRandomString(1024)\n\n\t// Generate 64KB of random text of bufio MaxScanTokenSize\n\t// https://github.com/containerd/nerdctl/issues/3343\n\thugeString := generateRandomString(bufio.MaxScanTokenSize)\n\n\t// Prepare mock driver and logging config\n\tdriver := &MockDriver{}\n\tstdoutBuffer := bytes.NewBufferString(normalString)\n\tstderrBuffer := bytes.NewBufferString(hugeString)\n\tconfig := &logging.Config{\n\t\tStdout: stdoutBuffer,\n\t\tStderr: stderrBuffer,\n\t}\n\n\t// Execute the logging process adapter\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tvar getContainerWaitMock ContainerWaitFunc = func(ctx context.Context, address string, config *logging.Config) (<-chan containerd.ExitStatus, error) {\n\t\texitChan := make(chan containerd.ExitStatus, 1)\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\texitChan <- containerd.ExitStatus{}\n\t\treturn exitChan, nil\n\t}\n\n\terr := loggingProcessAdapter(ctx, driver, \"testDataStore\", \"\", getContainerWaitMock, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// let bufio read the buffer\n\ttime.Sleep(50 * time.Millisecond)\n\n\t// Verify that the driver methods were called\n\tif !driver.processed {\n\t\tt.Fatal(\"process should be processed\")\n\t}\n\n\t// Verify that the driver received the expected data\n\tstdout := strings.Join(driver.receivedStdout, \"\\n\")\n\tstderr := strings.Join(driver.receivedStderr, \"\\n\")\n\n\tif stdout != normalString {\n\t\tt.Fatalf(\"stdout is %s, expected %s\", stdout, normalString)\n\t}\n\n\tif stderr != hugeString {\n\t\tt.Fatalf(\"stderr is %s, expected %s\", stderr, hugeString)\n\t}\n}\n\n// generateRandomString creates a random string of the given size.\nfunc generateRandomString(size int) string {\n\tcharacters := \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n\tvar sb strings.Builder\n\tfor i := 0; i < size; i++ {\n\t\tsb.WriteByte(characters[rand.Intn(len(characters))])\n\t}\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/logging/logs_other.go",
    "content": "//go:build !windows\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\nForked from https://github.com/kubernetes/kubernetes/blob/cc60b26dee4768e3c5aa0515bbf4ba1824ad38dc/staging/src/k8s.io/cri-client/pkg/logs/logs_other.go\nCopyright The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0\n*/\npackage logging\n\nimport (\n\t\"os\"\n)\n\nfunc openFileShareDelete(path string) (*os.File, error) {\n\t// Noop. Only relevant for Windows.\n\treturn os.Open(path)\n}\n"
  },
  {
    "path": "pkg/logging/logs_windows.go",
    "content": "//go:build windows\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\nForked from https://github.com/kubernetes/kubernetes/blob/cc60b26dee4768e3c5aa0515bbf4ba1824ad38dc/staging/src/k8s.io/cri-client/pkg/logs/logs_windows.go\nCopyright The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0\n*/\npackage logging\n\nimport (\n\t\"os\"\n\t\"syscall\"\n)\n\n// Based on Windows implementation of Windows' syscall.Open\n// https://cs.opensource.google/go/go/+/refs/tags/go1.22.2:src/syscall/syscall_windows.go;l=342\n// In addition to syscall.Open, this function also adds the syscall.FILE_SHARE_DELETE flag to sharemode,\n// which will allow us to read from the file without blocking the file from being deleted or renamed.\n// This is essential for Log Rotation which is done by renaming the open file. Without this, the file rename would fail.\nfunc openFileShareDelete(path string) (*os.File, error) {\n\tpathp, err := syscall.UTF16PtrFromString(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar access uint32 = syscall.GENERIC_READ\n\tvar sharemode uint32 = syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE\n\tvar createmode uint32 = syscall.OPEN_EXISTING\n\tvar attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL\n\n\thandle, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, attrs, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn os.NewFile(uintptr(handle), path), nil\n}\n"
  },
  {
    "path": "pkg/logging/none_logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"context\"\n\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n)\n\ntype NoneLogger struct {\n\tOpts map[string]string\n}\n\nfunc (n *NoneLogger) Init(dataStore, ns, id string) error {\n\treturn nil\n}\n\nfunc (n *NoneLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {\n\treturn nil\n}\n\nfunc (n *NoneLogger) Process(stdout <-chan string, stderr <-chan string) error {\n\treturn nil\n}\n\nfunc (n *NoneLogger) PostProcess() error {\n\treturn nil\n}\n\nfunc NoneLogOptsValidate(_ map[string]string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/logging/none_logger_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n)\n\nfunc TestNoneLogger(t *testing.T) {\n\t// Create a temporary directory for potential log files\n\ttmpDir := t.TempDir()\n\tctx := context.Background()\n\n\tlogger := &NoneLogger{\n\t\tOpts: map[string]string{},\n\t}\n\n\tt.Run(\"NoLoggingOccurs\", func(t *testing.T) {\n\t\tinitialFiles, err := os.ReadDir(tmpDir)\n\t\tassert.NilError(t, err, \"Failed to read temp dir\")\n\n\t\t// Run all logger methods\n\t\tlogger.Init(tmpDir, \"namespace\", \"id\")\n\t\tlogger.PreProcess(ctx, tmpDir, &logging.Config{})\n\n\t\tstdout := make(chan string)\n\t\tstderr := make(chan string)\n\n\t\tgo func() {\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tstdout <- \"test stdout\"\n\t\t\t\tstderr <- \"test stderr\"\n\t\t\t}\n\t\t\tclose(stdout)\n\t\t\tclose(stderr)\n\t\t}()\n\n\t\terr = logger.Process(stdout, stderr)\n\t\tassert.NilError(t, err, \"Process() returned unexpected error\")\n\n\t\tlogger.PostProcess()\n\n\t\t// Wait a bit to ensure any potential writes would have occurred\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\t// Check if any new files were created\n\t\tafterFiles, err := os.ReadDir(tmpDir)\n\t\tassert.NilError(t, err, \"Failed to read temp dir after operations\")\n\n\t\tassert.Equal(t, len(afterFiles), len(initialFiles), \"Expected no new files, but directory content changed\")\n\n\t})\n}\n"
  },
  {
    "path": "pkg/logging/syslog_logger.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/docker/go-connections/tlsconfig\"\n\tsyslog \"github.com/yuchanns/srslog\"\n\n\t\"github.com/containerd/containerd/v2/core/runtime/v2/logging\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nconst (\n\tsyslogAddress       = \"syslog-address\"\n\tsyslogFacility      = \"syslog-facility\"\n\tsyslogTLSCaCert     = \"syslog-tls-ca-cert\"\n\tsyslogTLSCert       = \"syslog-tls-cert\"\n\tsyslogTLSKey        = \"syslog-tls-key\"\n\tsyslogTLSSkipVerify = \"syslog-tls-skip-verify\"\n\tsyslogFormat        = \"syslog-format\"\n)\n\nvar syslogOpts = []string{\n\tsyslogAddress,\n\tsyslogFacility,\n\tsyslogTLSCaCert,\n\tsyslogTLSCert,\n\tsyslogTLSKey,\n\tsyslogTLSSkipVerify,\n\tsyslogFormat,\n\tTag,\n}\n\nvar syslogFacilities = map[string]syslog.Priority{\n\t\"kern\":     syslog.LOG_KERN,\n\t\"user\":     syslog.LOG_USER,\n\t\"mail\":     syslog.LOG_MAIL,\n\t\"daemon\":   syslog.LOG_DAEMON,\n\t\"auth\":     syslog.LOG_AUTH,\n\t\"syslog\":   syslog.LOG_SYSLOG,\n\t\"lpr\":      syslog.LOG_LPR,\n\t\"news\":     syslog.LOG_NEWS,\n\t\"uucp\":     syslog.LOG_UUCP,\n\t\"cron\":     syslog.LOG_CRON,\n\t\"authpriv\": syslog.LOG_AUTHPRIV,\n\t\"ftp\":      syslog.LOG_FTP,\n\t\"local0\":   syslog.LOG_LOCAL0,\n\t\"local1\":   syslog.LOG_LOCAL1,\n\t\"local2\":   syslog.LOG_LOCAL2,\n\t\"local3\":   syslog.LOG_LOCAL3,\n\t\"local4\":   syslog.LOG_LOCAL4,\n\t\"local5\":   syslog.LOG_LOCAL5,\n\t\"local6\":   syslog.LOG_LOCAL6,\n\t\"local7\":   syslog.LOG_LOCAL7,\n}\n\nconst (\n\tsyslogSecureProto = \"tcp+tls\"\n\tsyslogDefaultPort = \"514\"\n\n\tsyslogFormatRFC3164      = \"rfc3164\"\n\tsyslogFormatRFC5424      = \"rfc5424\"\n\tsyslogFormatRFC5424Micro = \"rfc5424micro\"\n)\n\nfunc SyslogOptsValidate(logOptMap map[string]string) error {\n\tfor key := range logOptMap {\n\t\tif !strutil.InStringSlice(syslogOpts, key) {\n\t\t\tlog.L.Warnf(\"log-opt %s is ignored for syslog log driver\", key)\n\t\t}\n\t}\n\tproto, _, err := parseSyslogAddress(logOptMap[syslogAddress])\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, err := parseSyslogFacility(logOptMap[syslogFacility]); err != nil {\n\t\treturn err\n\t}\n\tif _, _, err := parseSyslogLogFormat(logOptMap[syslogFormat], proto); err != nil {\n\t\treturn err\n\t}\n\tif proto == syslogSecureProto {\n\t\tif _, tlsErr := parseTLSConfig(logOptMap); tlsErr != nil {\n\t\t\treturn tlsErr\n\t\t}\n\t}\n\treturn nil\n}\n\ntype SyslogLogger struct {\n\tOpts   map[string]string\n\tlogger *syslog.Writer\n}\n\nfunc (sy *SyslogLogger) Init(dataStore string, ns string, id string) error {\n\treturn nil\n}\n\nfunc (sy *SyslogLogger) PreProcess(ctx context.Context, dataStore string, config *logging.Config) error {\n\tlogger, err := parseSyslog(config.ID, sy.Opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsy.logger = logger\n\treturn nil\n}\n\nfunc (sy *SyslogLogger) Process(stdout <-chan string, stderr <-chan string) error {\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tfn := func(dataChan <-chan string, logFn func(msg string) error) {\n\t\tdefer wg.Done()\n\t\tfor log := range dataChan {\n\t\t\tlogFn(log)\n\t\t}\n\t}\n\tgo fn(stdout, sy.logger.Info)\n\tgo fn(stderr, sy.logger.Err)\n\twg.Wait()\n\treturn nil\n}\n\nfunc (sy *SyslogLogger) PostProcess() error {\n\tdefer sy.logger.Close()\n\treturn nil\n}\n\nfunc parseSyslog(containerID string, config map[string]string) (*syslog.Writer, error) {\n\ttag := containerID[:12]\n\tif cfgTag, ok := config[Tag]; ok {\n\t\ttag = cfgTag\n\t}\n\tproto, address, err := parseSyslogAddress(config[syslogAddress])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfacility, err := parseSyslogFacility(config[syslogFacility])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsyslogFormatter, syslogFramer, err := parseSyslogLogFormat(config[syslogFormat], proto)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar logger *syslog.Writer\n\tif proto == syslogSecureProto {\n\t\ttlsConfig, tlsErr := parseTLSConfig(config)\n\t\tif tlsErr != nil {\n\t\t\treturn nil, tlsErr\n\t\t}\n\t\tlogger, err = syslog.DialWithTLSConfig(proto, address, facility, tag, tlsConfig)\n\t} else {\n\t\tlogger, err = syslog.Dial(proto, address, facility, tag)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlogger.SetFormatter(syslogFormatter)\n\tlogger.SetFramer(syslogFramer)\n\n\treturn logger, nil\n}\n\nfunc parseSyslogAddress(address string) (string, string, error) {\n\tif address == \"\" {\n\t\t// Docker-compatible: fallback to `unix:///dev/log`,\n\t\t// `unix:///var/run/syslog` or `unix:///var/run/log`. We do nothing\n\t\t// with the empty address, just leave it here and the srslog will\n\t\t// handle the fallback.\n\t\treturn \"\", \"\", nil\n\t}\n\taddr, err := url.Parse(address)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\t// unix and unixgram socket validation\n\tif addr.Scheme == \"unix\" || addr.Scheme == \"unixgram\" {\n\t\tif _, err := os.Stat(addr.Path); err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\t\treturn addr.Scheme, addr.Path, nil\n\t}\n\tif addr.Scheme != \"udp\" && addr.Scheme != \"tcp\" && addr.Scheme != syslogSecureProto {\n\t\treturn \"\", \"\", fmt.Errorf(\"unsupported scheme: '%s'\", addr.Scheme)\n\t}\n\n\t// here we process tcp|udp\n\thost := addr.Host\n\tif _, _, err := net.SplitHostPort(host); err != nil {\n\t\tif !strings.Contains(err.Error(), \"missing port in address\") {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\t\thost = net.JoinHostPort(host, syslogDefaultPort)\n\t}\n\n\treturn addr.Scheme, host, nil\n}\n\nfunc parseSyslogFacility(facility string) (syslog.Priority, error) {\n\tif facility == \"\" {\n\t\treturn syslog.LOG_DAEMON, nil\n\t}\n\n\tif syslogFacility, valid := syslogFacilities[facility]; valid {\n\t\treturn syslogFacility, nil\n\t}\n\n\tfInt, err := strconv.Atoi(facility)\n\tif err == nil && 0 <= fInt && fInt <= 23 {\n\t\treturn syslog.Priority(fInt << 3), nil\n\t}\n\n\treturn syslog.Priority(0), errors.New(\"invalid syslog facility\")\n}\n\nfunc parseTLSConfig(cfg map[string]string) (*tls.Config, error) {\n\t_, skipVerify := cfg[syslogTLSSkipVerify]\n\n\topts := tlsconfig.Options{\n\t\tCAFile:             cfg[syslogTLSCaCert],\n\t\tCertFile:           cfg[syslogTLSCert],\n\t\tKeyFile:            cfg[syslogTLSKey],\n\t\tInsecureSkipVerify: skipVerify,\n\t}\n\n\treturn tlsconfig.Client(opts)\n}\n\nfunc parseSyslogLogFormat(logFormat, proto string) (syslog.Formatter, syslog.Framer, error) {\n\tswitch logFormat {\n\tcase \"\":\n\t\treturn syslog.UnixFormatter, syslog.DefaultFramer, nil\n\tcase syslogFormatRFC3164:\n\t\treturn syslog.RFC3164Formatter, syslog.DefaultFramer, nil\n\tcase syslogFormatRFC5424:\n\t\tif proto == syslogSecureProto {\n\t\t\treturn syslog.RFC5424FormatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil\n\t\t}\n\t\treturn syslog.RFC5424FormatterWithAppNameAsTag, syslog.DefaultFramer, nil\n\tcase syslogFormatRFC5424Micro:\n\t\tif proto == syslogSecureProto {\n\t\t\treturn syslog.RFC5424MicroFormatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil\n\t\t}\n\t\treturn syslog.RFC5424MicroFormatterWithAppNameAsTag, syslog.DefaultFramer, nil\n\tdefault:\n\t\treturn nil, nil, errors.New(\"invalid syslog format\")\n\t}\n}\n"
  },
  {
    "path": "pkg/logging/tail/tail.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n\tForked from https://github.com/kubernetes/kubernetes/blob/master/pkg/util/tail/tail.go\n\tCopyright The Kubernetes Authors.\n\tLicensed under the Apache License, Version 2.0\n*/\n\npackage tail\n\nimport (\n\t\"bytes\"\n\t\"io\"\n)\n\nconst (\n\t// blockSize is the block size used in tail.\n\tblockSize = 1024\n)\n\nvar (\n\t// eol is the end-of-line sign in the log.\n\teol = []byte{'\\n'}\n)\n\n// FindTailLineStartIndex returns the start of last nth line.\n// * If n <= 0, return the beginning of the file.\n// * If n > 0, return the beginning of last nth line.\n// Notice that if the last line is incomplete (no end-of-line), it will not be counted\n// as one line.\nfunc FindTailLineStartIndex(f io.ReadSeeker, n uint) (int64, error) {\n\tif n <= 0 {\n\t\treturn 0, nil\n\t}\n\tsize, err := f.Seek(0, io.SeekEnd)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tvar left, cnt int64\n\tbuf := make([]byte, blockSize)\n\tfor right := size; right > 0 && uint(cnt) <= n; right -= blockSize {\n\t\tleft = right - blockSize\n\t\tif left < 0 {\n\t\t\tleft = 0\n\t\t\tbuf = make([]byte, right)\n\t\t}\n\t\tif _, err := f.Seek(left, io.SeekStart); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif _, err := f.Read(buf); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tcnt += int64(bytes.Count(buf, eol))\n\t}\n\tfor ; uint(cnt) > n; cnt-- {\n\t\tidx := bytes.Index(buf, eol) + 1\n\t\tbuf = buf[idx:]\n\t\tleft += int64(idx)\n\t}\n\treturn left, nil\n}\n"
  },
  {
    "path": "pkg/logging/tail/tail_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n\tForked from https://github.com/kubernetes/kubernetes/blob/master/pkg/util/tail/tail_test.go\n\tCopyright The Kubernetes Authors.\n\tLicensed under the Apache License, Version 2.0\n*/\n\npackage tail\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestTail(t *testing.T) {\n\tline := strings.Repeat(\"a\", blockSize)\n\ttestBytes := []byte(line + \"\\n\" +\n\t\tline + \"\\n\" +\n\t\tline + \"\\n\" +\n\t\tline + \"\\n\" +\n\t\tline[blockSize/2:]) // incomplete line\n\n\tfor c, test := range []struct {\n\t\tn     uint32\n\t\tstart int64\n\t}{\n\t\t{n: 0, start: 0},\n\t\t{n: 1, start: int64(len(line)+1) * 3},\n\t\t{n: 9999, start: 0},\n\t} {\n\t\tt.Logf(\"TestCase #%d: %+v\", c, test)\n\t\tr := bytes.NewReader(testBytes)\n\t\ts, err := FindTailLineStartIndex(r, uint(test.n))\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tif s != test.start {\n\t\t\tt.Errorf(\"%d != %d\", s, test.start)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/manifeststore/manifeststore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifeststore\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/manifesttypes\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\ntype Store interface {\n\tGet(listRef *referenceutil.ImageReference, manifestRef *referenceutil.ImageReference) (*manifesttypes.DockerManifestEntry, error)\n\t// GetList returns all the local manifests for a index or manifest list\n\tGetList(listRef *referenceutil.ImageReference) ([]*manifesttypes.DockerManifestEntry, error)\n\t// Save saves a manifest as part of a index or local manifest list\n\tSave(listRef, manifestRef *referenceutil.ImageReference, manifest *manifesttypes.DockerManifestEntry) error\n\t// Remove removes a index or local manifest list\n\tRemove(listRef *referenceutil.ImageReference) error\n}\n\ntype manifestStore struct {\n\tstore store.Store\n}\n\nfunc NewStore(dataRoot string) (Store, error) {\n\tmanifestRoot := filepath.Join(dataRoot, \"manifests\")\n\tst, err := store.New(manifestRoot, 0o755, 0o644)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create manifest store: %w\", err)\n\t}\n\treturn &manifestStore{store: st}, nil\n}\n\nfunc (s *manifestStore) Get(listRef *referenceutil.ImageReference, manifestRef *referenceutil.ImageReference) (*manifesttypes.DockerManifestEntry, error) {\n\tvar manifest *manifesttypes.DockerManifestEntry\n\terr := s.store.WithLock(func() error {\n\t\tlistPath := makeFilesafeName(listRef.String())\n\t\tmanifestPath := makeFilesafeName(manifestRef.String())\n\n\t\tvar err error\n\t\tmanifest, err = s.getManifestFromPath(listPath, manifestPath)\n\t\treturn err\n\t})\n\treturn manifest, err\n}\n\nfunc (s *manifestStore) GetList(listRef *referenceutil.ImageReference) ([]*manifesttypes.DockerManifestEntry, error) {\n\tlistPath := makeFilesafeName(listRef.String())\n\n\tif err := s.store.Lock(); err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.store.Release()\n\n\tmanifestPaths, err := s.store.List(listPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar manifests []*manifesttypes.DockerManifestEntry\n\tfor _, manifestPath := range manifestPaths {\n\t\tmanifest, err := s.getManifestFromPath(listPath, manifestPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmanifests = append(manifests, manifest)\n\t}\n\n\treturn manifests, nil\n}\n\nfunc (s *manifestStore) Save(listRef, manifestRef *referenceutil.ImageReference, manifest *manifesttypes.DockerManifestEntry) error {\n\treturn s.store.WithLock(func() error {\n\t\tlistPath := makeFilesafeName(listRef.String())\n\t\tif err := s.store.GroupEnsure(listPath); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tmanifestPath := makeFilesafeName(manifestRef.String())\n\t\tdata, err := json.Marshal(manifest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn s.store.Set(data, listPath, manifestPath)\n\t})\n}\n\nfunc (s *manifestStore) Remove(listRef *referenceutil.ImageReference) error {\n\treturn s.store.WithLock(func() error {\n\t\tlistPath := makeFilesafeName(listRef.String())\n\t\treturn s.store.Delete(listPath)\n\t})\n}\n\nfunc (s *manifestStore) getManifestFromPath(listPath, manifestPath string) (*manifesttypes.DockerManifestEntry, error) {\n\tdata, err := s.store.Get(listPath, manifestPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar manifest manifesttypes.DockerManifestEntry\n\tif err := json.Unmarshal(data, &manifest); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal manifest: %w\", err)\n\t}\n\n\treturn &manifest, nil\n}\n\nfunc makeFilesafeName(ref string) string {\n\tfileName := strings.ReplaceAll(ref, \":\", \"-\")\n\treturn strings.ReplaceAll(fileName, \"/\", \"_\")\n}\n"
  },
  {
    "path": "pkg/manifesttypes/manifesttypes.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifesttypes\n\nimport (\n\t\"github.com/opencontainers/go-digest\"\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n)\n\n// For Docker's verbose format\ntype (\n\t// DockerManifestEntry represents a single manifest entry in Docker's verbose format\n\tDockerManifestEntry struct {\n\t\tRef              string             `json:\"Ref\"`\n\t\tDescriptor       ocispec.Descriptor `json:\"Descriptor\"`\n\t\tRaw              string             `json:\"Raw\"`\n\t\tSchemaV2Manifest interface{}        `json:\"SchemaV2Manifest,omitempty\"`\n\t\tOCIManifest      interface{}        `json:\"OCIManifest,omitempty\"`\n\t}\n\n\tManifestStruct struct {\n\t\tSchemaVersion int                  `json:\"schemaVersion\"`\n\t\tMediaType     string               `json:\"mediaType\"`\n\t\tConfig        ocispec.Descriptor   `json:\"config\"`\n\t\tLayers        []ocispec.Descriptor `json:\"layers\"`\n\t\tAnnotations   map[string]string    `json:\"annotations,omitempty\"`\n\t}\n\n\tDockerManifestListStruct struct {\n\t\tSchemaVersion int                  `json:\"schemaVersion\"`\n\t\tMediaType     string               `json:\"mediaType\"`\n\t\tManifests     []ocispec.Descriptor `json:\"manifests\"`\n\t}\n\n\tDockerManifestStruct = ManifestStruct\n\tOCIManifestStruct    = ManifestStruct\n\tOCIIndexStruct       = ocispec.Index\n)\n\n// For manifest push, compatible with Docker distribution spec\ntype (\n\tDockerManifestDescriptor struct {\n\t\tMediaType string           `json:\"mediaType\"`\n\t\tSize      int64            `json:\"size\"`\n\t\tDigest    digest.Digest    `json:\"digest\"`\n\t\tPlatform  ocispec.Platform `json:\"platform\"`\n\t}\n\n\tDockerManifestList struct {\n\t\tSchemaVersion int                        `json:\"schemaVersion\"`\n\t\tMediaType     string                     `json:\"mediaType,omitempty\"`\n\t\tManifests     []DockerManifestDescriptor `json:\"manifests\"`\n\t}\n)\n"
  },
  {
    "path": "pkg/manifestutil/manifestutils.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage manifestutil\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/core/remotes\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil/dockerconfigresolver\"\n\t\"github.com/containerd/nerdctl/v2/pkg/manifesttypes\"\n\t\"github.com/containerd/nerdctl/v2/pkg/referenceutil\"\n)\n\n// manifestParser defines a function type for parsing manifest data\ntype manifestParser func([]byte) (interface{}, error)\n\n// manifestParsers maps media types to their parsing functions\nvar manifestParsers = map[string]manifestParser{\n\tocispec.MediaTypeImageManifest:            parseOCIManifest,\n\timages.MediaTypeDockerSchema2Manifest:     parseDockerManifest,\n\timages.MediaTypeDockerSchema2ManifestList: parseDockerManifestList,\n\tocispec.MediaTypeImageIndex:               parseOCIIndex,\n}\n\n// NoSuchManifestError represents an error when a manifest is not found\ntype NoSuchManifestError struct {\n\tRef string\n}\n\nfunc (e *NoSuchManifestError) Error() string {\n\treturn fmt.Sprintf(\"No such manifest: %s\", e.Ref)\n}\n\n// NewNoSuchManifestError creates a new NoSuchManifestError\nfunc NewNoSuchManifestError(ref string) error {\n\treturn &NoSuchManifestError{Ref: ref}\n}\n\n// ParseManifest parses manifest data based on media type\nfunc ParseManifest(mediaType string, data []byte) (interface{}, error) {\n\tif parser, exists := manifestParsers[mediaType]; exists {\n\t\treturn parser(data)\n\t}\n\treturn nil, fmt.Errorf(\"unsupported media type: %s\", mediaType)\n}\n\n// parseOCIManifest parses OCI manifest data\nfunc parseOCIManifest(data []byte) (interface{}, error) {\n\tvar ociManifest manifesttypes.OCIManifestStruct\n\tif err := json.Unmarshal(data, &ociManifest); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal manifest: %w\", err)\n\t}\n\treturn ociManifest, nil\n}\n\n// parseDockerManifest parses Docker manifest data\nfunc parseDockerManifest(data []byte) (interface{}, error) {\n\tvar dockerManifest manifesttypes.DockerManifestStruct\n\tif err := json.Unmarshal(data, &dockerManifest); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal docker manifest: %w\", err)\n\t}\n\treturn dockerManifest, nil\n}\n\n// parseDockerManifestList parses Docker manifest list data\nfunc parseDockerManifestList(data []byte) (interface{}, error) {\n\tvar manifestList manifesttypes.DockerManifestListStruct\n\tif err := json.Unmarshal(data, &manifestList); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal docker index: %w\", err)\n\t}\n\treturn manifestList, nil\n}\n\n// parseOCIIndex parses OCI index data\nfunc parseOCIIndex(data []byte) (interface{}, error) {\n\tvar index manifesttypes.OCIIndexStruct\n\tif err := json.Unmarshal(data, &index); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal index: %w\", err)\n\t}\n\treturn index, nil\n}\n\n// CreateResolver creates a resolver for registry operations\nfunc CreateResolver(ctx context.Context, domain string, globalOptions types.GlobalCommandOptions, insecure bool) (remotes.Resolver, error) {\n\tdOpts := buildResolverOptions(globalOptions, insecure)\n\n\tresolver, err := dockerconfigresolver.New(ctx, domain, dOpts...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create resolver: %w\", err)\n\t}\n\n\treturn resolver, nil\n}\n\n// buildResolverOptions builds resolver options based on global options and security settings\nfunc buildResolverOptions(globalOptions types.GlobalCommandOptions, insecure bool) []dockerconfigresolver.Opt {\n\tvar dOpts []dockerconfigresolver.Opt\n\n\tif insecure {\n\t\tdOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))\n\t}\n\tdOpts = append(dOpts, dockerconfigresolver.WithHostsDirs(globalOptions.HostsDir))\n\n\treturn dOpts\n}\n\n// FetchManifestData fetches manifest descriptor and data from the registry\nfunc FetchManifestData(ctx context.Context, resolver remotes.Resolver, ref string) (ocispec.Descriptor, []byte, error) {\n\t_, desc, err := resolver.Resolve(ctx, ref)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, nil, fmt.Errorf(\"failed to resolve %s: %w\", ref, err)\n\t}\n\n\tfetcher, err := resolver.Fetcher(ctx, ref)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, nil, fmt.Errorf(\"failed to create fetcher: %w\", err)\n\t}\n\n\trc, err := fetcher.Fetch(ctx, desc)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, nil, fmt.Errorf(\"failed to fetch manifest: %w\", err)\n\t}\n\tdefer rc.Close()\n\n\tdata, err := io.ReadAll(rc)\n\tif err != nil {\n\t\treturn ocispec.Descriptor{}, nil, fmt.Errorf(\"failed to read manifest data: %w\", err)\n\t}\n\n\treturn desc, data, nil\n}\n\n// GetManifest returns manifest, descriptor, and raw data in one call\nfunc GetManifest(ctx context.Context, parsedRef *referenceutil.ImageReference, globalOptions types.GlobalCommandOptions, insecure bool) (interface{}, ocispec.Descriptor, []byte, error) {\n\tresolver, err := CreateResolver(ctx, parsedRef.Domain, globalOptions, insecure)\n\tif err != nil {\n\t\treturn nil, ocispec.Descriptor{}, nil, fmt.Errorf(\"failed to create resolver: %w\", err)\n\t}\n\n\tdesc, data, err := FetchManifestData(ctx, resolver, parsedRef.String())\n\tif err != nil {\n\t\treturn nil, ocispec.Descriptor{}, nil, err\n\t}\n\n\tmanifest, err := ParseManifest(desc.MediaType, data)\n\tif err != nil {\n\t\treturn nil, ocispec.Descriptor{}, nil, err\n\t}\n\n\treturn manifest, desc, data, nil\n}\n\n// getManifestFieldName returns the appropriate field name based on media type\nfunc getManifestFieldName(mediaType string) string {\n\tswitch mediaType {\n\tcase images.MediaTypeDockerSchema2Manifest:\n\t\treturn \"SchemaV2Manifest\"\n\tcase ocispec.MediaTypeImageManifest:\n\t\treturn \"OCIManifest\"\n\tdefault:\n\t\treturn \"ManifestStruct\"\n\t}\n}\n\n// CreateManifestEntry creates a DockerManifestEntry with proper ManifestStruct\nfunc CreateManifestEntry(parsedRef *referenceutil.ImageReference, desc ocispec.Descriptor, rawData []byte) (manifesttypes.DockerManifestEntry, error) {\n\tvar ref string\n\tif parsedRef.Digest != \"\" {\n\t\tref = parsedRef.String()\n\t} else {\n\t\tref = fmt.Sprintf(\"%s@%s\", parsedRef.String(), desc.Digest.String())\n\t}\n\n\tentry := manifesttypes.DockerManifestEntry{\n\t\tRef:        ref,\n\t\tDescriptor: desc,\n\t\tRaw:        base64.StdEncoding.EncodeToString(rawData),\n\t}\n\n\tmanifest, err := ParseManifest(desc.MediaType, rawData)\n\tif err != nil {\n\t\treturn manifesttypes.DockerManifestEntry{}, fmt.Errorf(\"failed to parse manifest: %w\", err)\n\t}\n\n\tfieldName := getManifestFieldName(desc.MediaType)\n\tswitch fieldName {\n\tcase \"SchemaV2Manifest\":\n\t\tentry.SchemaV2Manifest = manifest\n\tcase \"OCIManifest\":\n\t\tentry.OCIManifest = manifest\n\t}\n\n\t// Special handling for OCI manifests to match Docker output\n\tif desc.MediaType == ocispec.MediaTypeImageManifest {\n\t\tentry.Descriptor.Annotations = nil\n\t}\n\n\treturn entry, nil\n}\n\n// getPlatformFromConfig return platform information from the config blob\nfunc getPlatformFromConfig(ctx context.Context, resolver remotes.Resolver, ref string, configDesc ocispec.Descriptor) (*ocispec.Platform, error) {\n\tfetcher, err := resolver.Fetcher(ctx, ref)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create fetcher: %w\", err)\n\t}\n\n\trc, err := fetcher.Fetch(ctx, configDesc)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch config: %w\", err)\n\t}\n\tdefer rc.Close()\n\n\tdata, err := io.ReadAll(rc)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read config data: %w\", err)\n\t}\n\n\tvar config ocispec.Image\n\tif err := json.Unmarshal(data, &config); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal config: %w\", err)\n\t}\n\treturn &config.Platform, nil\n\n}\n\n// GetPlatform return the platform information from manifest config\nfunc GetPlatform(ctx context.Context, domain string, globalOptions types.GlobalCommandOptions, insecure bool, ref string, manifest interface{}) (*ocispec.Platform, error) {\n\tresolver, err := CreateResolver(ctx, domain, globalOptions, insecure)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create resolver: %w\", err)\n\t}\n\n\tif ociManifest, ok := manifest.(manifesttypes.OCIManifestStruct); ok {\n\t\tif ociManifest.Config.Digest != \"\" {\n\t\t\tplatform, err := getPlatformFromConfig(ctx, resolver, ref, ociManifest.Config)\n\t\t\tif err == nil && platform != nil {\n\t\t\t\treturn platform, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tif dockerManifest, ok := manifest.(manifesttypes.DockerManifestStruct); ok {\n\t\tif dockerManifest.Config.Digest != \"\" {\n\t\t\tplatform, err := getPlatformFromConfig(ctx, resolver, ref, dockerManifest.Config)\n\t\t\tif err == nil && platform != nil {\n\t\t\t\treturn platform, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &ocispec.Platform{}, nil\n}\n"
  },
  {
    "path": "pkg/maputil/maputil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage maputil\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n)\n\n// MapBoolValueAsOpt will parse key as a command-line option.\n// If only key is specified will be treated as true,\n// otherwise, the value will be parsed and returned.\n// This is useful when command line flags have options.\n// Following examples illustrate this:\n// --security-opt xxx returns true\n// --security-opt xxx=true returns true\n// --security-opt xxx=false returns false\n// --security-opt xxx=invalid returns false and error\nfunc MapBoolValueAsOpt(m map[string]string, key string) (bool, error) {\n\tif str, ok := m[key]; ok {\n\t\tif str == \"\" {\n\t\t\treturn true, nil\n\t\t}\n\t\tb, err := strconv.ParseBool(str)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"invalid \\\"%s\\\" value: %q: %w\", key, str, err)\n\t\t}\n\t\treturn b, nil\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/maputil/maputil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage maputil\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestMapBoolValueAsOpt(t *testing.T) {\n\ttests := []struct {\n\t\tm       map[string]string\n\t\tkey     string\n\t\twant    bool\n\t\twantErr bool\n\t}{\n\t\t// cases that key does not exist\n\t\t{\n\t\t\tm:       map[string]string{},\n\t\t\tkey:     \"key\",\n\t\t\twant:    false,\n\t\t\twantErr: false,\n\t\t},\n\t\t// key exist, but has no value\n\t\t{\n\t\t\tm:       map[string]string{\"key\": \"\"},\n\t\t\tkey:     \"key\",\n\t\t\twant:    true,\n\t\t\twantErr: false,\n\t\t},\n\t\t// key exist, and set to true\n\t\t{\n\t\t\tm:       map[string]string{\"key\": \"true\"},\n\t\t\tkey:     \"key\",\n\t\t\twant:    true,\n\t\t\twantErr: false,\n\t\t},\n\t\t// key exist, and set to false\n\t\t{\n\t\t\tm:       map[string]string{\"key\": \"false\"},\n\t\t\tkey:     \"key\",\n\t\t\twant:    false,\n\t\t\twantErr: false,\n\t\t},\n\t\t// cases with error\n\t\t{\n\t\t\tm:       map[string]string{\"key\": \"abc\"},\n\t\t\tkey:     \"key\",\n\t\t\twant:    false,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor i, tt := range tests {\n\t\tgot, err := MapBoolValueAsOpt(tt.m, tt.key)\n\t\tassert.Equal(t, got, tt.want, fmt.Sprintf(\"case %d\", (i+1)))\n\t\tif (err != nil) != tt.wantErr {\n\t\t\tt.Errorf(\"MapBoolValueAsOpt() case %d error = %v, wantErr %v\", (i + 1), err, tt.wantErr)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/moby/sys/userns\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/idgen\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nconst (\n\tBind          = \"bind\"\n\tVolume        = \"volume\"\n\tTmpfs         = \"tmpfs\"\n\tNpipe         = \"npipe\"\n\tpathSeparator = string(os.PathSeparator)\n)\n\ntype Processed struct {\n\tType            string\n\tMount           specs.Mount\n\tName            string // name\n\tAnonymousVolume string // anonymous volume name\n\tMode            string\n\tOpts            []oci.SpecOpts\n}\n\ntype volumeSpec struct {\n\tType            string\n\tName            string\n\tSource          string\n\tAnonymousVolume string\n}\n\nfunc ProcessFlagV(s string, volStore volumestore.VolumeStore, createDir bool) (*Processed, error) {\n\tvar (\n\t\tres      *Processed\n\t\tvolSpec  volumeSpec\n\t\tsrc, dst string\n\t\toptions  []string\n\t)\n\n\tsplit, err := splitVolumeSpec(s)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to split volume mount specification: %v\", err)\n\t}\n\n\tswitch len(split) {\n\tcase 1:\n\t\t// validate destination\n\t\tdst = split[0]\n\t\tif _, err := validateAnonymousVolumeDestination(dst); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// create anonymous volume\n\t\tvolSpec, err = handleAnonymousVolumes(dst, volStore)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tsrc = volSpec.Source\n\t\tres = &Processed{\n\t\t\tType:            volSpec.Type,\n\t\t\tAnonymousVolume: volSpec.AnonymousVolume,\n\t\t}\n\tcase 2, 3:\n\t\t// Vaildate destination\n\t\tdst = split[1]\n\t\tdst = strings.TrimLeft(dst, \":\")\n\t\tif _, err := isValidPath(dst); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Get volume spec\n\t\tsrc = split[0]\n\t\tvolSpec, err = handleVolumeToMount(src, dst, volStore, createDir)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tsrc = volSpec.Source\n\t\tres = &Processed{\n\t\t\tType:            volSpec.Type,\n\t\t\tName:            volSpec.Name,\n\t\t\tAnonymousVolume: volSpec.AnonymousVolume,\n\t\t}\n\n\t\t// Parse volume options\n\t\tif len(split) == 3 {\n\t\t\tres.Mode = split[2]\n\n\t\t\trawOpts := res.Mode\n\n\t\t\toptions, res.Opts, err = getVolumeOptions(src, res.Type, rawOpts)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"failed to parse %q\", s)\n\t}\n\n\tfstype := DefaultMountType\n\tif runtime.GOOS != \"freebsd\" {\n\t\tfound := false\n\t\tfor _, opt := range options {\n\t\t\tswitch opt {\n\t\t\tcase \"rbind\", \"bind\":\n\t\t\t\tfstype = \"bind\"\n\t\t\t\tfound = true\n\t\t\t}\n\t\t\tif found {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\toptions = append(options, \"rbind\")\n\t\t}\n\t}\n\tres.Mount = specs.Mount{\n\t\tType:        fstype,\n\t\tSource:      cleanMount(src),\n\t\tDestination: cleanMount(dst),\n\t\tOptions:     options,\n\t}\n\tif userns.RunningInUserNS() {\n\t\tunpriv, err := UnprivilegedMountFlags(src)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get unprivileged mount flags for %q: %w\", src, err)\n\t\t}\n\t\tres.Mount.Options = strutil.DedupeStrSlice(append(res.Mount.Options, unpriv...))\n\t}\n\n\treturn res, nil\n}\n\nfunc handleBindMounts(source string, createDir bool) (volumeSpec, error) {\n\tvar res volumeSpec\n\tres.Type = Bind\n\tres.Source = source\n\n\t// Handle relative paths\n\tif !filepath.IsAbs(source) {\n\t\tabsPath, err := filepath.Abs(source)\n\t\tif err != nil {\n\t\t\treturn res, fmt.Errorf(\"failed to get the absolute path of %q: %w\", source, err)\n\t\t}\n\t\tres.Source = absPath\n\t}\n\n\t// Create dir if it does not exist\n\tif err := createDirOnHost(source, createDir); err != nil {\n\t\treturn res, err\n\t}\n\n\treturn res, nil\n}\n\nfunc handleAnonymousVolumes(s string, volStore volumestore.VolumeStore) (volumeSpec, error) {\n\tvar res volumeSpec\n\tres.AnonymousVolume = idgen.GenerateID()\n\n\tlog.L.Debugf(\"creating anonymous volume %q, for %q\", res.AnonymousVolume, s)\n\tanonVol, err := volStore.CreateWithoutLock(res.AnonymousVolume, []string{})\n\tif err != nil {\n\t\treturn res, fmt.Errorf(\"failed to create an anonymous volume %q: %w\", res.AnonymousVolume, err)\n\t}\n\n\tres.Type = Volume\n\tres.Source = anonVol.Mountpoint\n\treturn res, nil\n}\n\nfunc handleNamedVolumes(source string, volStore volumestore.VolumeStore) (volumeSpec, error) {\n\tvar res volumeSpec\n\tres.Name = source\n\n\t// Create returns an existing volume or creates a new one if necessary.\n\tvol, err := volStore.CreateWithoutLock(res.Name, nil)\n\tif err != nil {\n\t\treturn res, fmt.Errorf(\"failed to get volume %q: %w\", res.Name, err)\n\t}\n\t// src is now an absolute path\n\tres.Type = Volume\n\tres.Source = vol.Mountpoint\n\n\treturn res, nil\n}\n\nfunc getVolumeOptions(src string, vType string, rawOpts string) ([]string, []oci.SpecOpts, error) {\n\t// always call parseVolumeOptions for bind mount to allow the parser to add some default options\n\tvar err error\n\tvar specOpts []oci.SpecOpts\n\toptions, specOpts, err := parseVolumeOptions(vType, src, rawOpts)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to parse volume options (%q, %q, %q): %w\", vType, src, rawOpts, err)\n\t}\n\n\tspecOpts = append(specOpts, specOpts...)\n\treturn options, specOpts, nil\n}\n\nfunc createDirOnHost(src string, createDir bool) error {\n\t_, err := os.Stat(src)\n\tif err == nil {\n\t\treturn nil\n\t}\n\n\tif !createDir {\n\n\t\t/**\n\t\t* In pkg\\mountutil\\mountutil_linux.go:432, we disallow creating directories on host if not found\n\t\t* The user gets an error if the directory does not exist:\n\t\t*\t  error mounting \"/foo\" to rootfs at \"/foo\": stat /foo: no such file or directory: unknown.\n\t\t* We log this error to give the user a hint that they may need to create the directory on the host.\n\t\t* https://docs.docker.com/storage/bind-mounts/\n\t\t */\n\t\tif os.IsNotExist(err) {\n\t\t\tlog.L.Warnf(\"mount source %q does not exist. Please make sure to create the directory on the host.\", src)\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to stat %q: %w\", src, err)\n\t}\n\n\tif !os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"failed to stat %q: %w\", src, err)\n\t}\n\tif err := os.MkdirAll(src, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"failed to mkdir %q: %w\", src, err)\n\t}\n\treturn nil\n}\n\nfunc isNamedVolume(s string) bool {\n\terr := identifiers.ValidateDockerCompat(s)\n\n\t// If the volume name is invalid, we assume it is a path\n\treturn err == nil\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\nconst (\n\tDefaultMountType = \"\"\n\n\tDefaultPropagationMode = \"\"\n)\n\nfunc UnprivilegedMountFlags(path string) ([]string, error) {\n\tm := []string{}\n\treturn m, nil\n}\n\n// parseVolumeOptions parses specified optsRaw with using information of\n// the volume type and the src directory when necessary.\nfunc parseVolumeOptions(vType, src, optsRaw string) ([]string, []oci.SpecOpts, error) {\n\tvar writeModeRawOpts []string\n\tfor _, opt := range strings.Split(optsRaw, \",\") {\n\t\tswitch opt {\n\t\tcase \"rw\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"ro\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"\":\n\t\t\t// NOP\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"unsupported volume option %q\", opt)\n\t\t}\n\t}\n\tvar opts []string\n\tif len(writeModeRawOpts) > 1 {\n\t\treturn nil, nil, fmt.Errorf(\"duplicated read/write volume option: %+v\", writeModeRawOpts)\n\t} else if len(writeModeRawOpts) > 0 && writeModeRawOpts[0] == \"ro\" {\n\t\topts = append(opts, \"ro\")\n\t} // No need to return option when \"rw\"\n\treturn opts, nil, nil\n}\n\nfunc ProcessFlagTmpfs(s string) (*Processed, error) {\n\treturn nil, errdefs.ErrNotImplemented\n}\n\nfunc ProcessFlagMount(s string, volStore volumestore.VolumeStore) (*Processed, error) {\n\treturn nil, errdefs.ErrNotImplemented\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\nconst (\n\tDefaultMountType = \"nullfs\"\n\n\t// FreeBSD doesn't support bind mounts.\n\tDefaultPropagationMode = \"\"\n)\n\nfunc UnprivilegedMountFlags(path string) ([]string, error) {\n\tm := []string{}\n\treturn m, nil\n}\n\n// parseVolumeOptions parses specified optsRaw with using information of\n// the volume type and the src directory when necessary.\nfunc parseVolumeOptions(vType, src, optsRaw string) ([]string, []oci.SpecOpts, error) {\n\tvar writeModeRawOpts []string\n\tfor _, opt := range strings.Split(optsRaw, \",\") {\n\t\tswitch opt {\n\t\tcase \"rw\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"ro\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"\":\n\t\t\t// NOP\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"unsupported volume option %q\", opt)\n\t\t}\n\t}\n\tvar opts []string\n\tif len(writeModeRawOpts) > 1 {\n\t\treturn nil, nil, fmt.Errorf(\"duplicated read/write volume option: %+v\", writeModeRawOpts)\n\t} else if len(writeModeRawOpts) > 0 && writeModeRawOpts[0] == \"ro\" {\n\t\topts = append(opts, \"ro\")\n\t} // No need to return option when \"rw\"\n\treturn opts, nil, nil\n}\n\nfunc ProcessFlagTmpfs(s string) (*Processed, error) {\n\treturn nil, errdefs.ErrNotImplemented\n}\n\nfunc ProcessFlagMount(s string, volStore volumestore.VolumeStore) (*Processed, error) {\n\treturn nil, errdefs.ErrNotImplemented\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\tmobymount \"github.com/moby/sys/mount\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/containerd/containerd/v2/core/containers\"\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\n/*\n   Portions from https://github.com/moby/moby/blob/v20.10.5/daemon/oci_linux.go\n   Portions from https://github.com/moby/moby/blob/v20.10.5/volume/mounts/linux_parser.go\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.5/NOTICE\n*/\n\nconst (\n\tDefaultMountType = \"none\"\n\n\t// DefaultPropagationMode is the default propagation of mounts\n\t// where user doesn't specify mount propagation explicitly.\n\t// See also: https://github.com/moby/moby/blob/v20.10.7/volume/mounts/linux_parser.go#L145\n\tDefaultPropagationMode = \"rprivate\"\n)\n\n// UnprivilegedMountFlags is from https://github.com/moby/moby/blob/v20.10.5/daemon/oci_linux.go#L420-L450\n//\n// Get the set of mount flags that are set on the mount that contains the given\n// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that\n// bind-mounting \"with options\" will not fail with user namespaces, due to\n// kernel restrictions that require user namespace mounts to preserve\n// CL_UNPRIVILEGED locked flags.\nfunc UnprivilegedMountFlags(path string) ([]string, error) {\n\tvar statfs unix.Statfs_t\n\tif err := unix.Statfs(path, &statfs); err != nil {\n\t\treturn nil, &fs.PathError{Op: \"stat\", Path: path, Err: err}\n\t}\n\n\t// The set of keys come from https://github.com/torvalds/linux/blob/v4.13/fs/namespace.c#L1034-L1048.\n\tunprivilegedFlags := map[uint64]string{\n\t\tunix.MS_RDONLY:     \"ro\",\n\t\tunix.MS_NODEV:      \"nodev\",\n\t\tunix.MS_NOEXEC:     \"noexec\",\n\t\tunix.MS_NOSUID:     \"nosuid\",\n\t\tunix.MS_NOATIME:    \"noatime\",\n\t\tunix.MS_RELATIME:   \"relatime\",\n\t\tunix.MS_NODIRATIME: \"nodiratime\",\n\t}\n\n\tvar flags []string\n\tfor mask, flag := range unprivilegedFlags {\n\t\tif uint64(statfs.Flags)&mask == mask {\n\t\t\tflags = append(flags, flag)\n\t\t}\n\t}\n\n\treturn flags, nil\n}\n\n// parseVolumeOptions parses specified optsRaw with using information of\n// the volume type and the src directory when necessary.\nfunc parseVolumeOptions(vType, src, optsRaw string) ([]string, []oci.SpecOpts, error) {\n\treturn parseVolumeOptionsWithMountInfo(vType, src, optsRaw, getMountInfo)\n}\n\n// getMountInfo gets mount.Info of a directory.\nfunc getMountInfo(dir string) (mount.Info, error) {\n\tsourcePath, err := filepath.EvalSymlinks(dir)\n\tif err != nil {\n\t\treturn mount.Info{}, err\n\t}\n\treturn mount.Lookup(sourcePath)\n}\n\n// parseVolumeOptionsWithMountInfo is the testable implementation\n// of parseVolumeOptions.\nfunc parseVolumeOptionsWithMountInfo(vType, src, optsRaw string, getMountInfoFunc func(string) (mount.Info, error)) ([]string, []oci.SpecOpts, error) {\n\tvar (\n\t\twriteModeRawOpts   []string\n\t\tpropagationRawOpts []string\n\t\tbindOpts           []string\n\t)\n\tfor _, opt := range strings.Split(optsRaw, \",\") {\n\t\tswitch opt {\n\t\tcase \"rw\", \"ro\", \"rro\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"private\", \"rprivate\", \"shared\", \"rshared\", \"slave\", \"rslave\":\n\t\t\tpropagationRawOpts = append(propagationRawOpts, opt)\n\t\tcase \"bind\", \"rbind\":\n\t\t\t// bind means not recursively bind-mounted, rbind is the opposite\n\t\t\tbindOpts = append(bindOpts, opt)\n\t\tcase \"\":\n\t\t\t// NOP\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"unsupported volume option %q\", opt)\n\t\t}\n\t}\n\n\tvar opts []string\n\tvar specOpts []oci.SpecOpts\n\n\tif len(bindOpts) > 0 && vType != Bind {\n\t\treturn nil, nil, fmt.Errorf(\"volume bind/rbind option is only supported for bind mount: %+v\", bindOpts)\n\t} else if len(bindOpts) > 1 {\n\t\treturn nil, nil, fmt.Errorf(\"duplicated bind/rbind option: %+v\", bindOpts)\n\t} else if len(bindOpts) > 0 {\n\t\topts = append(opts, bindOpts[0])\n\t}\n\n\tif len(writeModeRawOpts) > 1 {\n\t\treturn nil, nil, fmt.Errorf(\"duplicated read/write volume option: %+v\", writeModeRawOpts)\n\t} else if len(writeModeRawOpts) > 0 {\n\t\tswitch writeModeRawOpts[0] {\n\t\tcase \"ro\":\n\t\t\topts = append(opts, \"ro\")\n\t\tcase \"rro\":\n\t\t\t// Mount option \"rro\" is supported since crun v1.4 / runc v1.1 (https://github.com/opencontainers/runc/pull/3272), with kernel >= 5.12.\n\t\t\t// Older version of runc just ignores \"rro\", so we have to add \"ro\" too, to our best effort.\n\t\t\topts = append(opts, \"ro\", \"rro\")\n\t\t\tif len(propagationRawOpts) != 1 || propagationRawOpts[0] != \"rprivate\" {\n\t\t\t\tlog.L.Warn(\"Mount option \\\"rro\\\" should be used in conjunction with \\\"rprivate\\\"\")\n\t\t\t}\n\t\tcase \"rw\":\n\t\t\t// NOP\n\t\tdefault:\n\t\t\t// NOTREACHED\n\t\t\treturn nil, nil, fmt.Errorf(\"unexpected writeModeRawOpts[0]=%q\", writeModeRawOpts[0])\n\t\t}\n\t}\n\n\tif len(propagationRawOpts) > 1 {\n\t\treturn nil, nil, fmt.Errorf(\"duplicated volume propagation option: %+v\", propagationRawOpts)\n\t} else if len(propagationRawOpts) > 0 && vType != Bind {\n\t\treturn nil, nil, fmt.Errorf(\"volume propagation option is only supported for bind mount: %+v\", propagationRawOpts)\n\t} else if vType == Bind {\n\t\tvar pFlag string\n\t\tvar got string\n\t\tif len(propagationRawOpts) > 0 {\n\t\t\tgot = propagationRawOpts[0]\n\t\t}\n\t\tswitch got {\n\t\tcase \"shared\", \"rshared\":\n\t\t\tpFlag = got\n\t\t\t// a bind mount can be shared from shared mount\n\t\t\tmi, err := getMountInfoFunc(src)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t\tif err := ensureMountOptionalValue(mi, \"shared:\"); err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\n\t\t\t// NOTE: Though OCI Runtime Spec doesn't explicitly describe, runc's default\n\t\t\t//       of RootfsPropagation is unix.MS_SLAVE | unix.MS_REC (i.e. runc applies\n\t\t\t//       \"slave\" to all mount points in the container recursively). This ends\n\t\t\t//       up marking the bind src directories \"slave\" and preventing it to shared\n\t\t\t//      with the host. So we set RootfsPropagation to \"shared\" here.\n\t\t\t//\n\t\t\t// See also:\n\t\t\t// - OCI Runtime Spec: https://github.com/opencontainers/runtime-spec/blob/v1.0.2/config-linux.md#rootfs-mount-propagation\n\t\t\t// - runc implementation: https://github.com/opencontainers/runc/blob/v1.0.0/libcontainer/rootfs_linux.go#L771-L777\n\t\t\tspecOpts = append(specOpts, func(ctx context.Context, cli oci.Client, c *containers.Container, s *oci.Spec) error {\n\t\t\t\tswitch s.Linux.RootfsPropagation {\n\t\t\t\tcase \"shared\", \"rshared\":\n\t\t\t\t\t// NOP\n\t\t\t\tdefault:\n\t\t\t\t\ts.Linux.RootfsPropagation = \"shared\"\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\tcase \"slave\", \"rslave\":\n\t\t\tpFlag = got\n\t\t\t// a bind mount can be a slave of shared or an existing slave mount\n\t\t\tmi, err := getMountInfoFunc(src)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t\tif err := ensureMountOptionalValue(mi, \"shared:\", \"master:\"); err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\n\t\t\t// See above comments about RootfsPropagation. Here we make sure that\n\t\t\t// the mountpoint can be a slave of the host mount.\n\t\t\tspecOpts = append(specOpts, func(ctx context.Context, cli oci.Client, c *containers.Container, s *oci.Spec) error {\n\t\t\t\tswitch s.Linux.RootfsPropagation {\n\t\t\t\tcase \"shared\", \"rshared\", \"slave\", \"rslave\":\n\t\t\t\t\t// NOP\n\t\t\t\tdefault:\n\t\t\t\t\ts.Linux.RootfsPropagation = \"rslave\"\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\tcase \"private\", \"rprivate\":\n\t\t\tpFlag = got\n\t\tdefault:\n\t\t\t// No propagation is specified to this bind mount.\n\t\t\t// NOTE: When RootfsPropagation is set (e.g. by other bind mount option), that\n\t\t\t//       propagation mode will be applied to this bind mount as well. So we need\n\t\t\t//       to set \"rprivate\" explicitly for preventing this bind mount from unexpectedly\n\t\t\t//       shared with the host. This behaviour is compatible to docker:\n\t\t\t//       https://github.com/moby/moby/blob/v20.10.7/volume/mounts/linux_parser.go#L320-L322\n\t\t\t//\n\t\t\t// TODO: directories managed by containerd (e.g. /var/lib/containerd, /run/containerd, ...)\n\t\t\t//       should be marked as \"rslave\" instead of \"rprivate\". This is because allowing\n\t\t\t//       containers to hold their private bind mounts will prevent containerd from remove\n\t\t\t//       them. See also: https://github.com/moby/moby/pull/36055.\n\t\t\t//       Unfortunately, containerd doesn't expose the locations of directories where it manages.\n\t\t\t//       Current workaround is explicitly add \"rshared\" or \"rslave\" option to these bind mounts.\n\t\t\tpFlag = DefaultPropagationMode\n\t\t}\n\t\topts = append(opts, pFlag)\n\t}\n\n\treturn opts, specOpts, nil\n}\n\n// ensure the mount of the specified directory has either of the specified\n// \"optional\" value in the entry in the /proc/<pid>/mountinfo file.\n//\n// For more details about \"optional\" field:\n// - https://github.com/moby/sys/blob/mountinfo/v0.4.1/mountinfo/mountinfo.go#L52-L56\nfunc ensureMountOptionalValue(mi mount.Info, vals ...string) error {\n\tvar hasValue bool\n\tfor _, opt := range strings.Split(mi.Optional, \" \") {\n\t\tfor _, mark := range vals {\n\t\t\tif strings.HasPrefix(opt, mark) {\n\t\t\t\thasValue = true\n\t\t\t}\n\t\t}\n\t}\n\tif !hasValue {\n\t\treturn fmt.Errorf(\"mountpoint %q doesn't have optional field neither of %+v\", mi.Mountpoint, vals)\n\t}\n\treturn nil\n}\n\nfunc ProcessFlagTmpfs(s string) (*Processed, error) {\n\tsplit := strings.SplitN(s, \":\", 2)\n\tdst := split[0]\n\toptions := []string{\"noexec\", \"nosuid\", \"nodev\"}\n\tif len(split) == 2 {\n\t\traw := append(options, strings.Split(split[1], \",\")...)\n\t\tvar err error\n\t\toptions, err = mobymount.MergeTmpfsOptions(raw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tres := &Processed{\n\t\tMount: specs.Mount{\n\t\t\tType:        \"tmpfs\",\n\t\t\tSource:      \"tmpfs\",\n\t\t\tDestination: dst,\n\t\t\tOptions:     options,\n\t\t},\n\t\tType: Tmpfs,\n\t\tMode: strings.Join(options, \",\"),\n\t}\n\treturn res, nil\n}\n\nfunc ProcessFlagMount(s string, volStore volumestore.VolumeStore) (*Processed, error) {\n\tfields := strings.Split(s, \",\")\n\tvar (\n\t\tmountType        string\n\t\tsrc              string\n\t\tdst              string\n\t\tbindPropagation  string\n\t\tbindNonRecursive bool\n\t\trwOption         string\n\t\ttmpfsSize        int64\n\t\ttmpfsMode        os.FileMode\n\t\terr              error\n\t)\n\n\t// set default values\n\tmountType = Volume\n\ttmpfsMode = os.FileMode(01777)\n\n\t// three types of mount(and examples):\n\t// --mount type=bind,source=\"$(pwd)\"/target,target=/app2,readonly,bind-propagation=shared\n\t// --mount type=tmpfs,destination=/app,tmpfs-mode=1770,tmpfs-size=1MB\n\t// --mount type=volume,src=vol-1,dst=/app,readonly\n\t// if type not specified, default will be set to volume\n\t// --mount src=`pwd`/tmp,target=/app\n\n\tfor _, field := range fields {\n\t\tparts := strings.SplitN(field, \"=\", 2)\n\t\tkey := strings.ToLower(parts[0])\n\n\t\tif len(parts) == 1 {\n\t\t\tswitch key {\n\t\t\tcase \"readonly\", \"ro\", \"rw\", \"rro\":\n\t\t\t\trwOption = key\n\t\t\t\tcontinue\n\t\t\tcase \"bind-nonrecursive\":\n\t\t\t\tbindNonRecursive = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid field '%s' must be a key=value pair\", field)\n\t\t}\n\n\t\tvalue := parts[1]\n\t\tswitch key {\n\t\tcase \"type\":\n\t\t\tswitch value {\n\t\t\tcase \"tmpfs\":\n\t\t\t\tmountType = Tmpfs\n\t\t\tcase \"bind\":\n\t\t\t\tmountType = Bind\n\t\t\tcase \"volume\":\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"invalid mount type '%s' must be a volume/bind/tmpfs\", value)\n\t\t\t}\n\t\tcase \"source\", \"src\":\n\t\t\tsrc = value\n\t\tcase \"target\", \"dst\", \"destination\":\n\t\t\tdst = value\n\t\tcase \"readonly\", \"ro\", \"rw\", \"rro\":\n\t\t\ttrueValue, err := strconv.ParseBool(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid value for %s: %s\", key, value)\n\t\t\t}\n\t\t\tif trueValue {\n\t\t\t\trwOption = key\n\t\t\t}\n\t\tcase \"bind-propagation\":\n\t\t\t// here don't validate the propagation value\n\t\t\t// parseVolumeOptions will do that.\n\t\t\tbindPropagation = value\n\t\tcase \"bind-nonrecursive\":\n\t\t\tbindNonRecursive, err = strconv.ParseBool(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid value for %s: %s\", key, value)\n\t\t\t}\n\t\tcase \"tmpfs-size\":\n\t\t\ttmpfsSize, err = units.RAMInBytes(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid value for %s: %s\", key, value)\n\t\t\t}\n\t\tcase \"tmpfs-mode\":\n\t\t\tui64, err := strconv.ParseUint(value, 8, 32)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid value for %s: %s\", key, value)\n\t\t\t}\n\t\t\ttmpfsMode = os.FileMode(ui64)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected key '%s' in '%s'\", key, field)\n\t\t}\n\t}\n\n\t// compose new fileds and join into a string\n\t// to call legacy ProcessFlagTmpfs or ProcessFlagV function\n\tfields = []string{}\n\toptions := []string{}\n\tif rwOption != \"\" {\n\t\tif rwOption == \"readonly\" {\n\t\t\trwOption = \"ro\"\n\t\t}\n\t\toptions = append(options, rwOption)\n\t}\n\n\tswitch mountType {\n\tcase Tmpfs:\n\t\tfields = []string{dst}\n\t\tif tmpfsMode != 0 {\n\t\t\toptions = append(options, fmt.Sprintf(\"mode=%o\", tmpfsMode))\n\t\t}\n\t\tif tmpfsSize > 0 {\n\t\t\toptions = append(options, getTmpfsSize(tmpfsSize))\n\t\t}\n\tcase Volume, Bind:\n\t\tfields = []string{src, dst}\n\t\tif bindPropagation != \"\" {\n\t\t\toptions = append(options, bindPropagation)\n\t\t}\n\t\tif mountType == Bind {\n\t\t\tif bindNonRecursive {\n\t\t\t\toptions = append(options, \"bind\")\n\t\t\t} else {\n\t\t\t\toptions = append(options, \"rbind\")\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(options) > 0 {\n\t\toptionsStr := strings.Join(options, \",\")\n\t\tfields = append(fields, optionsStr)\n\t}\n\tfieldsStr := strings.Join(fields, \":\")\n\n\tlog.L.Debugf(\"Call legacy %s process, spec: %s \", mountType, fieldsStr)\n\n\tswitch mountType {\n\tcase Tmpfs:\n\t\treturn ProcessFlagTmpfs(fieldsStr)\n\tcase Volume, Bind:\n\t\t// createDir=false for --mount option to disallow creating directories on host if not found\n\t\treturn ProcessFlagV(fieldsStr, volStore, false)\n\t}\n\treturn nil, fmt.Errorf(\"invalid mount type '%s' must be a volume/bind/tmpfs\", mountType)\n}\n\n// copy from https://github.com/moby/moby/blob/085c6a98d54720e70b28354ccec6da9b1b9e7fcf/volume/mounts/linux_parser.go#L375\nfunc getTmpfsSize(size int64) string {\n\t// calculate suffix here, making this linux specific, but that is\n\t// okay, since API is that way anyways.\n\n\t// we do this by finding the suffix that divides evenly into the\n\t// value, returning the value itself, with no suffix, if it fails.\n\t//\n\t// For the most part, we don't enforce any semantic to this values.\n\t// The operating system will usually align this and enforce minimum\n\t// and maximums.\n\tvar (\n\t\tsuffix string\n\t)\n\tfor _, r := range []struct {\n\t\tsuffix  string\n\t\tdivisor int64\n\t}{\n\t\t{\"g\", 1 << 30},\n\t\t{\"m\", 1 << 20},\n\t\t{\"k\", 1 << 10},\n\t} {\n\t\tif size%r.divisor == 0 {\n\t\t\tsize = size / r.divisor\n\t\t\tsuffix = r.suffix\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn fmt.Sprintf(\"size=%d%s\", size, suffix)\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"gotest.tools/v3/assert\"\n\tis \"gotest.tools/v3/assert/cmp\"\n\n\t\"github.com/containerd/containerd/v2/core/mount\"\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n)\n\n// TestParseVolumeOptions tests volume options are parsed as expected.\nfunc TestParseVolumeOptions(t *testing.T) {\n\ttests := []struct {\n\t\tname                     string\n\t\tvType                    string\n\t\tsrc                      string\n\t\toptsRaw                  string\n\t\tsrcOptional              []string\n\t\tinitialRootfsPropagation string\n\t\twants                    []string\n\t\twantRootfsPropagation    string\n\t\twantFail                 bool\n\t}{\n\t\t{\n\t\t\tname:    \"unknown option is ignored (with warning)\",\n\t\t\tvType:   \"volume\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"ro,undefined\",\n\t\t\twants:   []string{\"ro\"},\n\t\t},\n\n\t\t// tests for rw/ro flags\n\t\t{\n\t\t\tname:    \"read write\",\n\t\t\tvType:   \"bind\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"rw\",\n\t\t\twants:   []string{\"rprivate\"},\n\t\t},\n\t\t{\n\t\t\tname:    \"read only\",\n\t\t\tvType:   \"volume\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"ro\",\n\t\t\twants:   []string{\"ro\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"duplicated flags are not allowed\",\n\t\t\tvType:    \"bind\",\n\t\t\tsrc:      \"dummy\",\n\t\t\toptsRaw:  \"ro,rw\",\n\t\t\twantFail: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"duplicated flags (ro/ro) are not allowed\",\n\t\t\tvType:    \"volume\",\n\t\t\tsrc:      \"dummy\",\n\t\t\toptsRaw:  \"ro,ro\",\n\t\t\twantFail: true,\n\t\t},\n\n\t\t// tests for propagation flags\n\t\t{\n\t\t\tname:     \"volume doesn't accept propagation option\",\n\t\t\tvType:    \"volume\",\n\t\t\tsrc:      \"dummy\",\n\t\t\toptsRaw:  \"private\",\n\t\t\twantFail: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"duplicated propagation option is not allowed\",\n\t\t\tvType:    \"bind\",\n\t\t\tsrc:      \"dummy\",\n\t\t\toptsRaw:  \"private,shared\",\n\t\t\twantFail: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"default propagation type is rprivate\",\n\t\t\tvType: \"bind\",\n\t\t\tsrc:   \"dummy\",\n\t\t\twants: []string{\"rprivate\"},\n\t\t},\n\t\t{\n\t\t\tname:    \"make bind private\",\n\t\t\tvType:   \"bind\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"ro,private\",\n\t\t\twants:   []string{\"ro\", \"private\"},\n\t\t},\n\t\t{\n\t\t\tname:    \"make bind nonrecursive\",\n\t\t\tvType:   \"bind\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"bind\",\n\t\t\twants:   []string{\"bind\", \"rprivate\"},\n\t\t},\n\t\t{\n\t\t\tname:                  \"make bind shared\",\n\t\t\tvType:                 \"bind\",\n\t\t\tsrc:                   \"dummy\",\n\t\t\toptsRaw:               \"ro,rshared\",\n\t\t\tsrcOptional:           []string{\"shared:xxx\"},\n\t\t\twantRootfsPropagation: \"shared\",\n\t\t\twants:                 []string{\"ro\", \"rshared\"},\n\t\t},\n\t\t{\n\t\t\tname:                     \"make bind shared (unchange RootfsPropagation)\",\n\t\t\tvType:                    \"bind\",\n\t\t\tsrc:                      \"dummy\",\n\t\t\toptsRaw:                  \"ro,rshared\",\n\t\t\tsrcOptional:              []string{\"shared:xxx\"},\n\t\t\tinitialRootfsPropagation: \"rshared\",\n\t\t\twantRootfsPropagation:    \"rshared\",\n\t\t\twants:                    []string{\"ro\", \"rshared\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"shared propagation is not allowed if the src is not shared\",\n\t\t\tvType:       \"bind\",\n\t\t\tsrc:         \"dummy\",\n\t\t\toptsRaw:     \"ro,shared\",\n\t\t\tsrcOptional: nil,\n\t\t\twantFail:    true,\n\t\t},\n\t\t{\n\t\t\tname:                  \"make bind slave\",\n\t\t\tvType:                 \"bind\",\n\t\t\tsrc:                   \"dummy\",\n\t\t\toptsRaw:               \"ro,slave\",\n\t\t\tsrcOptional:           []string{\"master:xxx\"},\n\t\t\twantRootfsPropagation: \"rslave\",\n\t\t\twants:                 []string{\"ro\", \"slave\"},\n\t\t},\n\t\t{\n\t\t\tname:                     \"make bind slave (unchange RootfsPropagation)\",\n\t\t\tvType:                    \"bind\",\n\t\t\tsrc:                      \"dummy\",\n\t\t\toptsRaw:                  \"ro,slave\",\n\t\t\tsrcOptional:              []string{\"master:xxx\"},\n\t\t\tinitialRootfsPropagation: \"shared\",\n\t\t\twantRootfsPropagation:    \"shared\",\n\t\t\twants:                    []string{\"ro\", \"slave\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"slave propagation is not allowed if the src is not slave\",\n\t\t\tvType:       \"bind\",\n\t\t\tsrc:         \"dummy\",\n\t\t\toptsRaw:     \"ro,slave\",\n\t\t\tsrcOptional: nil,\n\t\t\twantFail:    true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\topts, specOpts, err := parseVolumeOptionsWithMountInfo(tt.vType, tt.src, tt.optsRaw, func(string) (mount.Info, error) {\n\t\t\t\treturn mount.Info{\n\t\t\t\t\tMountpoint: tt.src,\n\t\t\t\t\tOptional:   strings.Join(tt.srcOptional, \" \"),\n\t\t\t\t}, nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tif tt.wantFail {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"failed to parse option %q: %v\", tt.optsRaw, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts := oci.Spec{Linux: &specs.Linux{RootfsPropagation: tt.initialRootfsPropagation}}\n\t\t\tfor _, o := range specOpts {\n\t\t\t\tassert.NilError(t, o(context.Background(), nil, nil, &s))\n\t\t\t}\n\t\t\tassert.Equal(t, tt.wantRootfsPropagation, s.Linux.RootfsPropagation)\n\t\t\tassert.Equal(t, tt.wantFail, false)\n\t\t\tassert.Check(t, is.DeepEqual(tt.wants, opts))\n\t\t})\n\t}\n}\n\nfunc TestProcessTmpfs(t *testing.T) {\n\ttestCases := map[string][]string{\n\t\t\"/tmp\":               {\"noexec\", \"nosuid\", \"nodev\"},\n\t\t\"/tmp:size=64m,exec\": {\"nosuid\", \"nodev\", \"size=64m\", \"exec\"},\n\t}\n\tfor k, expected := range testCases {\n\t\tx, err := ProcessFlagTmpfs(k)\n\t\tassert.NilError(t, err)\n\t\tassert.DeepEqual(t, expected, x.Mount.Options)\n\t}\n}\n\nfunc TestProcessFlagV(t *testing.T) {\n\ttests := []struct {\n\t\trawSpec string\n\t\twants   *Processed\n\t\terr     string\n\t}{\n\t\t// Bind volumes: absolute path\n\t\t{\n\t\t\trawSpec: \"/mnt/foo:/mnt/foo:ro\",\n\t\t\twants: &Processed{\n\t\t\t\tType: \"bind\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"none\",\n\t\t\t\t\tDestination: `/mnt/foo`,\n\t\t\t\t\tSource:      `/mnt/foo`,\n\t\t\t\t\tOptions:     []string{\"ro\", \"rprivate\", \"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t// Bind volumes: relative path\n\t\t{\n\t\t\trawSpec: `./TestVolume/Path:/mnt/foo`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"bind\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"none\",\n\t\t\t\t\tSource:      \"\", // will not check source of relative paths\n\t\t\t\t\tDestination: `/mnt/foo`,\n\t\t\t\t\tOptions:     []string{\"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t// Named volumes\n\t\t{\n\t\t\trawSpec: `TestVolume:/mnt/foo`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"volume\",\n\t\t\t\tName: \"TestVolume\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"none\",\n\t\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\t\tDestination: `/mnt/foo`,\n\t\t\t\t\tOptions:     []string{\"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t{\n\t\t\trawSpec: `/mnt/foo:TestVolume`,\n\t\t\terr:     \"expected an absolute path, got \\\"TestVolume\\\"\",\n\t\t},\n\t\t{\n\t\t\trawSpec: `/mnt/foo:./foo`,\n\t\t\terr:     \"expected an absolute path, got \\\"./foo\\\"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.rawSpec, func(t *testing.T) {\n\t\t\tprocessedVolSpec, err := ProcessFlagV(tt.rawSpec, mockVolumeStore, false)\n\t\t\tif err != nil {\n\t\t\t\tassert.Error(t, err, tt.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Equal(t, processedVolSpec.Type, tt.wants.Type)\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Type, tt.wants.Mount.Type)\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Destination, tt.wants.Mount.Destination)\n\t\t\tassert.DeepEqual(t, processedVolSpec.Mount.Options, tt.wants.Mount.Options)\n\n\t\t\tif tt.wants.Name != \"\" {\n\t\t\t\tassert.Equal(t, processedVolSpec.Name, tt.wants.Name)\n\t\t\t}\n\t\t\tif tt.wants.Mount.Source != \"\" {\n\t\t\t\tassert.Equal(t, processedVolSpec.Mount.Source, tt.wants.Mount.Source)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestProcessFlagVAnonymousVolumes(t *testing.T) {\n\ttests := []struct {\n\t\trawSpec string\n\t\twants   *Processed\n\t\terr     string\n\t}{\n\t\t{\n\t\t\trawSpec: `/mnt/foo`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"volume\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"none\",\n\t\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\t\tDestination: `/mnt/foo`,\n\t\t\t\t}},\n\t\t},\n\t\t{\n\t\t\trawSpec: `./TestVolume/Path`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"volume\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"none\",\n\t\t\t\t\tSource:      \"\",                // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\t\tDestination: `TestVolume/Path`, // cleanpath() removes the leading \"./\". Since we are mocking the os.Stat() call, this is fine.\n\t\t\t\t}},\n\t\t},\n\t\t{\n\t\t\trawSpec: \"TestVolume\",\n\t\t\twants: &Processed{\n\t\t\t\tType: \"volume\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"none\",\n\t\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\t\tDestination: \"TestVolume\",\n\t\t\t\t}},\n\t\t},\n\t\t{\n\t\t\trawSpec: `/mnt/foo::ro`,\n\t\t\terr:     \"expected an absolute path, got \\\"\\\"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.rawSpec, func(t *testing.T) {\n\t\t\tprocessedVolSpec, err := ProcessFlagV(tt.rawSpec, mockVolumeStore, true)\n\t\t\tif err != nil {\n\t\t\t\tassert.ErrorContains(t, err, tt.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Equal(t, processedVolSpec.Type, tt.wants.Type)\n\t\t\tassert.Assert(t, processedVolSpec.AnonymousVolume != \"\")\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Type, tt.wants.Mount.Type)\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Destination, tt.wants.Mount.Destination)\n\n\t\t\tif tt.wants.Mount.Source != \"\" {\n\t\t\t\tassert.Equal(t, processedVolSpec.Mount.Source, tt.wants.Mount.Source)\n\t\t\t}\n\n\t\t\t// for anonymous volumes, we want to make sure that the source is not the same as the destination\n\t\t\tassert.Assert(t, processedVolSpec.Mount.Source != processedVolSpec.Mount.Destination)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"runtime\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\ntype MockVolumeStore struct {\n\tvolumestore.VolumeStore\n}\n\nfunc (mv *MockVolumeStore) CreateWithoutLock(name string, labels []string) (*native.Volume, error) {\n\tif runtime.GOOS == \"windows\" {\n\t\treturn &native.Volume{Name: \"test_volume\", Mountpoint: \"C:\\\\test\\\\directory\"}, nil\n\t}\n\treturn &native.Volume{Name: \"test_volume\", Mountpoint: \"/test/volume\"}, nil\n}\n\n//nolint:unused\nvar mockVolumeStore = &MockVolumeStore{}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\nfunc splitVolumeSpec(s string) ([]string, error) {\n\ts = strings.TrimLeft(s, \":\")\n\tsplit := strings.Split(s, \":\")\n\treturn split, nil\n}\n\nfunc handleVolumeToMount(source string, dst string, volStore volumestore.VolumeStore, createDir bool) (volumeSpec, error) {\n\tswitch {\n\t// Handle named volumes\n\tcase isNamedVolume(source):\n\t\treturn handleNamedVolumes(source, volStore)\n\n\t// Handle bind volumes (file paths)\n\tdefault:\n\t\treturn handleBindMounts(source, createDir)\n\t}\n}\n\nfunc cleanMount(p string) string {\n\treturn filepath.Clean(p)\n}\n\nfunc isValidPath(s string) (bool, error) {\n\tif filepath.IsAbs(s) {\n\t\treturn true, nil\n\t}\n\n\treturn false, fmt.Errorf(\"expected an absolute path, got %q\", s)\n}\n\n/*\nFor docker compatibility on non-Windows platforms:\nDocker allows anonymous named volumes, relative paths, and absolute paths\nto be mounted into a container.\n*/\nfunc validateAnonymousVolumeDestination(s string) (bool, error) {\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/moby/blob/f5c7673ff8fcbd359f75fb644b1365ca9d20f176/volume/mounts/windows_parser.go#L26\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/master/NOTICE\n*/\n\npackage mountutil\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/containerd/containerd/v2/pkg/oci\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/mountutil/volumestore\"\n)\n\nconst (\n\t// Defaults to an empty string\n\t// https://github.com/microsoft/hcsshim/blob/5c75f29c1f5cb4d3498d66228637d07477bcb6a1/internal/hcsoci/resources_wcow.go#L140\n\tDefaultMountType = \"\"\n\n\t// DefaultPropagationMode is the default propagation of mounts\n\t// where user doesn't specify mount propagation explicitly.\n\t// See also: https://github.com/moby/moby/blob/v20.10.7/volume/mounts/windows_parser.go#L440-L442\n\tDefaultPropagationMode = \"\"\n)\n\nfunc UnprivilegedMountFlags(path string) ([]string, error) {\n\tm := []string{}\n\treturn m, nil\n}\n\n// parseVolumeOptions parses specified optsRaw with using information of\n// the volume type and the src directory when necessary.\nfunc parseVolumeOptions(vType, src, optsRaw string) ([]string, []oci.SpecOpts, error) {\n\tvar writeModeRawOpts []string\n\tfor _, opt := range strings.Split(optsRaw, \",\") {\n\t\tswitch opt {\n\t\tcase \"rw\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"ro\":\n\t\t\twriteModeRawOpts = append(writeModeRawOpts, opt)\n\t\tcase \"\":\n\t\t\t// NOP\n\t\tdefault:\n\t\t\tlog.L.Warnf(\"unsupported volume option %q\", opt)\n\t\t}\n\t}\n\tvar opts []string\n\tif len(writeModeRawOpts) > 1 {\n\t\treturn nil, nil, fmt.Errorf(\"duplicated read/write volume option: %+v\", writeModeRawOpts)\n\t} else if len(writeModeRawOpts) > 0 && writeModeRawOpts[0] == \"ro\" {\n\t\topts = append(opts, \"ro\")\n\t} // No need to return option when \"rw\"\n\treturn opts, nil, nil\n}\n\nfunc ProcessFlagTmpfs(s string) (*Processed, error) {\n\treturn nil, errdefs.ErrNotImplemented\n}\n\nfunc ProcessFlagMount(s string, volStore volumestore.VolumeStore) (*Processed, error) {\n\treturn nil, errdefs.ErrNotImplemented\n}\n\nfunc handleVolumeToMount(source string, dst string, volStore volumestore.VolumeStore, createDir bool) (volumeSpec, error) {\n\t// Validate source and destination types\n\tif _, err := (validateNamedPipeSpec(source, dst)); err != nil {\n\t\treturn volumeSpec{}, err\n\t}\n\n\tswitch {\n\t// Handle named volumes\n\tcase isNamedVolume(source):\n\t\treturn handleNamedVolumes(source, volStore)\n\n\t// Handle named pipes\n\tcase isNamedPipe(source):\n\t\treturn handleNpipeToMount(source)\n\n\t// Handle bind volumes (file paths)\n\tdefault:\n\t\treturn handleBindMounts(source, createDir)\n\t}\n}\n\nfunc handleNpipeToMount(source string) (volumeSpec, error) {\n\tres := volumeSpec{\n\t\tType:   Npipe,\n\t\tSource: source,\n\t}\n\treturn res, nil\n}\n\nfunc splitVolumeSpec(raw string) ([]string, error) {\n\traw = strings.TrimSpace(raw)\n\traw = strings.TrimLeft(raw, \":\")\n\tif raw == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid empty volume specification\")\n\t}\n\n\tconst (\n\t\t// Root drive or relative paths starting with .\n\t\trxHostDir = `(?:[a-zA-Z]:|\\.)[\\/\\\\]`\n\n\t\t// https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats\n\t\t// Windows UNC paths and DOS device paths (and namde pipes)\n\t\trxUNC  = `(?:\\\\{2}[a-zA-Z0-9_\\-\\.\\?]+\\\\{1}[^\\\\*?\"|\\r\\n]+)\\\\`\n\t\trxName = `[^\\/\\\\:*?\"<>|\\r\\n]+`\n\n\t\trxSource      = `((?P<source>((` + rxHostDir + `|` + rxUNC + `)` + `(` + rxName + `[\\/\\\\]?)+` + `|` + rxName + `)):)?`\n\t\trxDestination = `(?P<destination>(` + rxHostDir + `|` + rxUNC + `)` + `(` + rxName + `[\\/\\\\]?)+` + `|` + rxName + `)`\n\t\trxMode        = `(?::(?P<mode>(?i)\\w+(,\\w+)?))`\n\n\t\trxWindows = `^` + rxSource + rxDestination + `(?:` + rxMode + `)?$`\n\t)\n\n\tcompiledRegex, err := regexp.Compile(rxWindows)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error compiling regex: %w\", err)\n\t}\n\treturn splitRawSpec(raw, compiledRegex)\n}\n\nfunc isNamedPipe(s string) bool {\n\tpattern := `^\\\\{2}.\\\\pipe\\\\[^\\/\\\\:*?\"<>|\\r\\n]+$`\n\tmatches, err := regexp.MatchString(pattern, s)\n\tif err != nil {\n\t\tlog.L.Errorf(\"Invalid pattern %s\", pattern)\n\t}\n\n\treturn matches\n}\n\nfunc cleanMount(p string) string {\n\tif isNamedPipe(p) {\n\t\treturn p\n\t}\n\treturn filepath.Clean(p)\n}\n\nfunc isValidPath(s string) (bool, error) {\n\tif isNamedPipe(s) || filepath.IsAbs(s) {\n\t\treturn true, nil\n\t}\n\n\treturn false, fmt.Errorf(\"expected an absolute path or a named pipe, got %q\", s)\n}\n\n/*\nFor docker compatibility on Windows platforms:\nDocker only allows for absolute paths as anonymous volumes.\nDocker does not allows anonymous named volumes or anonymous named piped\nto be mounted into a container.\n*/\nfunc validateAnonymousVolumeDestination(s string) (bool, error) {\n\tif isNamedPipe(s) || isNamedVolume(s) {\n\t\treturn false, fmt.Errorf(\"invalid volume specification: %q. only directories can be mapped as anonymous volumes\", s)\n\t}\n\n\tif filepath.IsAbs(s) {\n\t\treturn true, nil\n\t}\n\n\treturn false, fmt.Errorf(\"expected an absolute path, got %q\", s)\n}\n\nfunc splitRawSpec(raw string, splitRegexp *regexp.Regexp) ([]string, error) {\n\tmatch := splitRegexp.FindStringSubmatch(raw)\n\tif len(match) == 0 {\n\t\treturn nil, fmt.Errorf(\"invalid volume specification: '%s'\", raw)\n\t}\n\n\tvar split []string\n\tmatchgroups := make(map[string]string)\n\t// Pull out the sub expressions from the named capture groups\n\tfor i, name := range splitRegexp.SubexpNames() {\n\t\tmatchgroups[name] = match[i]\n\t}\n\tif source, exists := matchgroups[\"source\"]; exists {\n\t\tif source == \".\" {\n\t\t\treturn nil, fmt.Errorf(\"invalid volume specification: %q\", raw)\n\t\t}\n\n\t\tif source != \"\" {\n\t\t\tsplit = append(split, source)\n\t\t}\n\t}\n\n\tmode, modExists := matchgroups[\"mode\"]\n\n\tif destination, exists := matchgroups[\"destination\"]; exists {\n\t\tif destination == \".\" {\n\t\t\treturn nil, fmt.Errorf(\"invalid volume specification: %q\", raw)\n\t\t}\n\n\t\t// If mode exists and destination is empty, set destination to an empty string\n\t\t// source::ro\n\t\tif destination != \"\" || modExists && mode != \"\" {\n\t\t\tsplit = append(split, destination)\n\t\t}\n\t}\n\n\tif mode, exists := matchgroups[\"mode\"]; exists {\n\t\tif mode != \"\" {\n\t\t\tsplit = append(split, mode)\n\t\t}\n\t}\n\treturn split, nil\n}\n\n// Function to parse the source type\nfunc parseSourceType(source string) string {\n\tswitch {\n\tcase isNamedVolume(source):\n\t\treturn Volume\n\tcase isNamedPipe(source):\n\t\treturn Npipe\n\t// Add more cases for different source types as needed\n\tdefault:\n\t\treturn Bind\n\t}\n}\n\nfunc validateNamedPipeSpec(source string, dst string) (bool, error) {\n\t// Validate source and destination types\n\tsourceType := parseSourceType(source)\n\tdestType := parseSourceType(dst)\n\n\tif (destType == Npipe && sourceType != Npipe) || (sourceType == Npipe && destType != Npipe) {\n\t\treturn false, fmt.Errorf(\"invalid volume specification. named pipes can only be mapped to named pipes\")\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/mountutil/mountutil_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage mountutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\t\"gotest.tools/v3/assert\"\n\tis \"gotest.tools/v3/assert/cmp\"\n)\n\nfunc TestParseVolumeOptions(t *testing.T) {\n\ttests := []struct {\n\t\tvType    string\n\t\tsrc      string\n\t\toptsRaw  string\n\t\twants    []string\n\t\twantFail bool\n\t}{\n\t\t{\n\t\t\tvType:   \"bind\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"rw\",\n\t\t\twants:   nil,\n\t\t},\n\t\t{\n\t\t\tvType:   \"volume\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"ro\",\n\t\t\twants:   []string{\"ro\"},\n\t\t},\n\t\t{\n\t\t\tvType:   \"volume\",\n\t\t\tsrc:     \"dummy\",\n\t\t\toptsRaw: \"ro,undefined\",\n\t\t\twants:   []string{\"ro\"},\n\t\t},\n\t\t{\n\t\t\tvType:    \"bind\",\n\t\t\tsrc:      \"dummy\",\n\t\t\toptsRaw:  \"ro,rw\",\n\t\t\twantFail: true,\n\t\t},\n\t\t{\n\t\t\tvType:    \"volume\",\n\t\t\tsrc:      \"dummy\",\n\t\t\toptsRaw:  \"ro,ro\",\n\t\t\twantFail: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(strings.Join([]string{tt.vType, tt.src, tt.optsRaw}, \"-\"), func(t *testing.T) {\n\t\t\topts, _, err := parseVolumeOptions(tt.vType, tt.src, tt.optsRaw)\n\t\t\tif err != nil {\n\t\t\t\tif tt.wantFail {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"failed to parse option %q: %v\", tt.optsRaw, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Equal(t, tt.wantFail, false)\n\t\t\tassert.Check(t, is.DeepEqual(tt.wants, opts))\n\t\t})\n\t}\n}\n\nfunc TestSplitRawSpec(t *testing.T) {\n\ttests := []struct {\n\t\trawSpec string\n\t\twants   []string\n\t}{\n\t\t// Absolute paths\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path:C:\\TestVolume\\Path:ro`,\n\t\t\twants:   []string{`C:\\TestVolume\\Path`, `C:\\TestVolume\\Path`, \"ro\"},\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path:C:\\TestVolume\\Path:ro,rw`,\n\t\t\twants:   []string{`C:\\TestVolume\\Path`, `C:\\TestVolume\\Path`, \"ro,rw\"},\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path:C:\\TestVolume\\Path:ro,undefined`,\n\t\t\twants:   []string{`C:\\TestVolume\\Path`, `C:\\TestVolume\\Path`, \"ro,undefined\"},\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path:C:\\TestVolume\\Path`,\n\t\t\twants:   []string{`C:\\TestVolume\\Path`, `C:\\TestVolume\\Path`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path`,\n\t\t\twants:   []string{`C:\\TestVolume\\Path`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\Test Volume\\Path`, // space in path\n\t\t\twants:   []string{`C:\\Test Volume\\Path`},\n\t\t},\n\n\t\t// Relative paths\n\t\t{\n\t\t\trawSpec: `.\\ContainerVolumes:C:\\TestVolumes`,\n\t\t\twants:   []string{`.\\ContainerVolumes`, `C:\\TestVolumes`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `.\\ContainerVolumes:.\\ContainerVolumes`,\n\t\t\twants:   []string{`.\\ContainerVolumes`, `.\\ContainerVolumes`},\n\t\t},\n\n\t\t// Anonymous volumes\n\t\t{\n\t\t\trawSpec: `.\\ContainerVolumes`,\n\t\t\twants:   []string{`.\\ContainerVolumes`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `TestVolume`,\n\t\t\twants:   []string{`TestVolume`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `:TestVolume`,\n\t\t\twants:   []string{`TestVolume`},\n\t\t},\n\n\t\t// UNC paths\n\t\t{\n\t\t\trawSpec: `\\\\?\\UNC\\server\\share\\path:.\\ContainerVolumesto`,\n\t\t\twants:   []string{`\\\\?\\UNC\\server\\share\\path`, `.\\ContainerVolumesto`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `\\\\.\\Volume{b75e2c83-0000-0000-0000-602f00000000}\\Test`,\n\t\t\twants:   []string{`\\\\.\\Volume{b75e2c83-0000-0000-0000-602f00000000}\\Test`},\n\t\t},\n\n\t\t// Named pipes\n\t\t{\n\t\t\trawSpec: `\\\\.\\pipe\\containerd-containerd`,\n\t\t\twants:   []string{`\\\\.\\pipe\\containerd-containerd`},\n\t\t},\n\t\t{\n\t\t\trawSpec: `\\\\.\\pipe\\containerd-containerd:\\\\.\\pipe\\containerd-containerd`,\n\t\t\twants:   []string{`\\\\.\\pipe\\containerd-containerd`, `\\\\.\\pipe\\containerd-containerd`},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.rawSpec, func(t *testing.T) {\n\t\t\tactual, err := splitVolumeSpec(tt.rawSpec)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"failed to split raw spec %q: %v\", tt.rawSpec, err)\n\t\t\t\treturn\n\n\t\t\t}\n\t\t\tassert.Check(t, is.DeepEqual(tt.wants, actual))\n\t\t})\n\t}\n}\n\nfunc TestSplitRawSpecInvalid(t *testing.T) {\n\ttests := []string{\n\t\t\"\",                                     // Empty string\n\t\t\"   \",                                  // Empty string\n\t\t`.`,                                    // Invalid relative path\n\t\t`./`,                                   // Invalid relative path\n\t\t`../`,                                  // Invalid relative path\n\t\t`C:\\`,                                  // Cannot mount root directory\n\t\t`~\\TestVolume`,                         // Invalid relative path\n\t\t`..\\TestVolume`,                        // Invalid relative path\n\t\t`ABC:\\ContainerVolumes:C:\\TestVolumes`, // Invalid drive letter\n\t\t`UNC\\server\\share\\path`,                // Invalid path\n\t}\n\n\tfor _, path := range tests {\n\t\tt.Run(path, func(t *testing.T) {\n\t\t\t_, err := splitVolumeSpec(path)\n\t\t\tif strings.TrimSpace(path) == \"\" {\n\t\t\t\tassert.Error(t, err, \"invalid empty volume specification\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif path == \".\" {\n\t\t\t\tassert.Error(t, err, \"invalid volume specification: \\\".\\\"\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Error(t, err, fmt.Sprintf(\"invalid volume specification: '%s'\", path))\n\t\t})\n\t}\n}\n\nfunc TestProcessFlagV(t *testing.T) {\n\ttests := []struct {\n\t\trawSpec string\n\t\twants   *Processed\n\t\terr     string\n\t}{\n\t\t// Bind volumes: absolute path\n\t\t{\n\t\t\trawSpec: \"C:/TestVolume/Path:C:/TestVolume/Path:ro\",\n\t\t\twants: &Processed{\n\t\t\t\tType: \"bind\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"\",\n\t\t\t\t\tDestination: `C:\\TestVolume\\Path`,\n\t\t\t\t\tSource:      `C:\\TestVolume\\Path`,\n\t\t\t\t\tOptions:     []string{\"ro\", \"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t// Bind volumes: relative path\n\t\t{\n\t\t\trawSpec: `.\\TestVolume\\Path:C:\\TestVolume\\Path`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"bind\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"\",\n\t\t\t\t\tSource:      \"\", // will not check source of relative paths\n\t\t\t\t\tDestination: `C:\\TestVolume\\Path`,\n\t\t\t\t\tOptions:     []string{\"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t// Named volumes\n\t\t{\n\t\t\trawSpec: `TestVolume:C:\\TestVolume\\Path`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"volume\",\n\t\t\t\tName: \"TestVolume\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"\",\n\t\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\t\tDestination: `C:\\TestVolume\\Path`,\n\t\t\t\t\tOptions:     []string{\"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t// Named pipes\n\t\t{\n\t\t\trawSpec: `\\\\.\\pipe\\containerd-containerd:\\\\.\\pipe\\containerd-containerd`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"npipe\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"\",\n\t\t\t\t\tSource:      `\\\\.\\pipe\\containerd-containerd`,\n\t\t\t\t\tDestination: `\\\\.\\pipe\\containerd-containerd`,\n\t\t\t\t\tOptions:     []string{\"rbind\"},\n\t\t\t\t}},\n\t\t},\n\t\t{\n\t\t\trawSpec: `\\\\.\\pipe\\containerd-containerd:C:\\TestVolume\\Path`,\n\t\t\terr:     \"invalid volume specification. named pipes can only be mapped to named pipes\",\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path:TestVolume`,\n\t\t\terr:     \"expected an absolute path or a named pipe, got \\\"TestVolume\\\"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.rawSpec, func(t *testing.T) {\n\t\t\tprocessedVolSpec, err := ProcessFlagV(tt.rawSpec, mockVolumeStore, true)\n\t\t\tif err != nil {\n\t\t\t\tassert.Error(t, err, tt.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Equal(t, processedVolSpec.Type, tt.wants.Type)\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Type, tt.wants.Mount.Type)\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Destination, tt.wants.Mount.Destination)\n\t\t\tassert.DeepEqual(t, processedVolSpec.Mount.Options, tt.wants.Mount.Options)\n\n\t\t\tif tt.wants.Name != \"\" {\n\t\t\t\tassert.Equal(t, processedVolSpec.Name, tt.wants.Name)\n\t\t\t}\n\t\t\tif tt.wants.Mount.Source != \"\" {\n\t\t\t\tassert.Equal(t, processedVolSpec.Mount.Source, tt.wants.Mount.Source)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestProcessFlagVAnonymousVolumes(t *testing.T) {\n\ttests := []struct {\n\t\trawSpec string\n\t\twants   *Processed\n\t\terr     string\n\t}{\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path`,\n\t\t\twants: &Processed{\n\t\t\t\tType: \"volume\",\n\t\t\t\tMount: specs.Mount{\n\t\t\t\t\tType:        \"\",\n\t\t\t\t\tSource:      \"\", // source of anonymous volume is a generated path, so here will not check it.\n\t\t\t\t\tDestination: `C:\\TestVolume\\Path`,\n\t\t\t\t}},\n\t\t},\n\t\t{\n\t\t\trawSpec: `.\\TestVolume\\Path`,\n\t\t\terr:     \"expected an absolute path\",\n\t\t},\n\t\t{\n\t\t\trawSpec: `TestVolume`,\n\t\t\terr:     \"only directories can be mapped as anonymous volumes\",\n\t\t},\n\t\t{\n\t\t\trawSpec: `C:\\TestVolume\\Path::ro`,\n\t\t\terr:     \"failed to split volume mount specification\",\n\t\t},\n\t\t{\n\t\t\trawSpec: `\\\\.\\pipe\\containerd-containerd`,\n\t\t\terr:     \"only directories can be mapped as anonymous volumes\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.rawSpec, func(t *testing.T) {\n\t\t\tprocessedVolSpec, err := ProcessFlagV(tt.rawSpec, mockVolumeStore, true)\n\t\t\tif err != nil {\n\t\t\t\tassert.ErrorContains(t, err, tt.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Equal(t, processedVolSpec.Type, tt.wants.Type)\n\t\t\tassert.Assert(t, processedVolSpec.AnonymousVolume != \"\")\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Type, tt.wants.Mount.Type)\n\t\t\tassert.Equal(t, processedVolSpec.Mount.Destination, tt.wants.Mount.Destination)\n\n\t\t\tif tt.wants.Mount.Source != \"\" {\n\t\t\t\tassert.Equal(t, processedVolSpec.Mount.Source, tt.wants.Mount.Source)\n\t\t\t}\n\n\t\t\t// for anonymous volumes, we want to make sure that the source is not the same as the destination\n\t\t\tassert.Assert(t, processedVolSpec.Mount.Source != processedVolSpec.Mount.Destination)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/mountutil/volumestore/volumestore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package volumestore allows manipulating containers' volumes.\n// All methods are safe to use concurrently (and perform atomic writes), except CreateWithoutLock, which is specifically\n// meant to be used multiple times, inside a Lock-ed section.\npackage volumestore\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\nconst (\n\tvolumeDirBasename  = \"volumes\"\n\tdataDirName        = \"_data\"\n\tvolumeJSONFileName = \"volume.json\"\n)\n\n// ErrVolumeStore will wrap all errors here\nvar ErrVolumeStore = errors.New(\"volume-store error\")\n\ntype VolumeStore interface {\n\t// Exists checks if a given volume exists\n\tExists(name string) (bool, error)\n\t// Get returns an existing volume\n\tGet(name string, size bool) (*native.Volume, error)\n\t// Create will either return an existing volume, or create a new one\n\t// NOTE that different labels will NOT create a new volume if there is one by that name already,\n\t// but instead return the existing one with the (possibly different) labels\n\tCreate(name string, labels []string) (vol *native.Volume, err error)\n\t// List returns all existing volumes.\n\t// Note that list is expensive as it reads all volumes individual info\n\tList(size bool) (map[string]native.Volume, error)\n\t// Remove one of more volumes\n\tRemove(generator func() ([]string, []error, error)) (removed []string, warns []error, err error)\n\t// Prune will call a filtering function expected to return the volumes name to delete\n\tPrune(filter func(volumes []*native.Volume) ([]string, error)) (err error)\n\t// Count returns the number of volumes\n\tCount() (count int, err error)\n\n\t// Lock: see store implementation\n\tLock() error\n\t// CreateWithoutLock will create a volume (or return an existing one).\n\t// This method does NOT lock (unlike Create).\n\t// It is meant to be used between `Lock` and `Release`, and is specifically useful when multiple different volume\n\t// creation will have to happen in different method calls (eg: container create).\n\tCreateWithoutLock(name string, labels []string) (*native.Volume, error)\n\t// Release: see store implementation\n\tRelease() error\n}\n\n// New returns a VolumeStore\nfunc New(dataStore, namespace string) (volStore VolumeStore, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\tif dataStore == \"\" || namespace == \"\" {\n\t\treturn nil, store.ErrInvalidArgument\n\t}\n\n\tst, err := store.New(filepath.Join(dataStore, volumeDirBasename, namespace), 0, 0o600)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &volumeStore{\n\t\tLocker:  st,\n\t\tmanager: st,\n\t}, nil\n}\n\ntype volumeStore struct {\n\t// Expose the lock primitives directly to satisfy interface for Lock and Release\n\tstore.Locker\n\n\tmanager store.Manager\n}\n\n// Exists checks if a volume exists in the store\nfunc (vs *volumeStore) Exists(name string) (doesExist bool, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn false, err\n\t}\n\n\t// No need for a lock here, the operation is atomic\n\treturn vs.manager.Exists(name)\n}\n\n// Get retrieves a native volume from the store, optionally with its size\nfunc (vs *volumeStore) Get(name string, size bool) (vol *native.Volume, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If we require the size, this is no longer atomic, so, we need to lock\n\terr = vs.WithLock(func() error {\n\t\tvol, err = vs.rawGet(name, size)\n\t\treturn err\n\t})\n\n\treturn vol, err\n}\n\n// CreateWithoutLock will create a new volume, or return an existing one if there is one already by that name\n// It does NOT lock for you - unlike all the other methods - though it *will* error if you do not lock.\n// This is on purpose as volume creation in most cases are done during container creation,\n// and implies an extended period of time for locking.\n// To use:\n// volStore.Lock()\n// defer volStore.Release()\n// volStore.CreateWithoutLock(...)\nfunc (vs *volumeStore) CreateWithoutLock(name string, labels []string) (vol *native.Volume, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vs.rawCreate(name, labels)\n}\n\nfunc (vs *volumeStore) Create(name string, labels []string) (vol *native.Volume, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = vs.Locker.WithLock(func() error {\n\t\tvol, err = vs.rawCreate(name, labels)\n\t\treturn err\n\t})\n\n\treturn vol, err\n}\n\nfunc (vs *volumeStore) Count() (count int, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\terr = vs.Locker.WithLock(func() error {\n\t\tnames, err := vs.manager.List()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcount = len(names)\n\t\treturn nil\n\t})\n\n\treturn count, err\n}\n\nfunc (vs *volumeStore) List(size bool) (res map[string]native.Volume, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\tres = make(map[string]native.Volume)\n\n\terr = vs.Locker.WithLock(func() error {\n\t\tnames, err := vs.manager.List()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, name := range names {\n\t\t\tvol, err := vs.rawGet(name, size)\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"something is wrong with %q\", name)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres[name] = *vol\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn res, err\n}\n\n// Remove will remove one or more containers\nfunc (vs *volumeStore) Remove(generator func() ([]string, []error, error)) (removed []string, warns []error, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\terr = vs.Locker.WithLock(func() error {\n\t\tvar names []string\n\t\tnames, warns, err = generator()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, name := range names {\n\t\t\t// Invalid name, soft error\n\t\t\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\t\t\t// TODO: we are clearly mixing presentation concerns here\n\t\t\t\t// This should be handled by the cli, not here\n\t\t\t\twarns = append(warns, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Erroring on Exists is a hard error\n\t\t\t// !doesExist is a soft error\n\t\t\t// Inability to delete is a hard error\n\t\t\tif doesExist, err := vs.manager.Exists(name); err != nil {\n\t\t\t\treturn err\n\t\t\t} else if !doesExist {\n\t\t\t\t// TODO: see above\n\t\t\t\twarns = append(warns, fmt.Errorf(\"volume %q: %w\", name, store.ErrNotFound))\n\t\t\t\tcontinue\n\t\t\t} else if err = vs.manager.Delete(name); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Otherwise, add it the list of successfully removed\n\t\t\tremoved = append(removed, name)\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn removed, warns, err\n}\n\nfunc (vs *volumeStore) Prune(filter func(vol []*native.Volume) ([]string, error)) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrVolumeStore, err)\n\t\t}\n\t}()\n\n\treturn vs.Locker.WithLock(func() error {\n\t\tnames, err := vs.manager.List()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tres := []*native.Volume{}\n\t\tfor _, name := range names {\n\t\t\tvol, err := vs.rawGet(name, false)\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Errorf(\"something is wrong with %q\", name)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres = append(res, vol)\n\t\t}\n\n\t\ttoDelete, err := filter(res)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, name := range toDelete {\n\t\t\terr = vs.manager.Delete(name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (vs *volumeStore) rawGet(name string, size bool) (vol *native.Volume, err error) {\n\tcontent, err := vs.manager.Get(name, volumeJSONFileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvol = &native.Volume{\n\t\tName:   name,\n\t\tLabels: labels(content),\n\t}\n\n\tvol.Mountpoint, err = vs.manager.Location(name, dataDirName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif size {\n\t\tvol.Size, err = vs.manager.GroupSize(name, dataDirName)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Join(fmt.Errorf(\"failed reading volume size for %q\", name), err)\n\t\t}\n\t}\n\n\treturn vol, nil\n}\n\nfunc (vs *volumeStore) rawCreate(name string, labels []string) (vol *native.Volume, err error) {\n\tvolOpts := struct {\n\t\tLabels map[string]string `json:\"labels\"`\n\t}{}\n\n\tif len(labels) > 0 {\n\t\tvolOpts.Labels = strutil.ConvertKVStringsToMap(labels)\n\t}\n\n\t// Failure here must exit, no need to clean-up\n\tlabelsJSON, err := json.MarshalIndent(volOpts, \"\", \"    \")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif doesExist, err := vs.manager.Exists(name, volumeJSONFileName); err != nil {\n\t\treturn nil, err\n\t} else if !doesExist {\n\t\tif err = vs.manager.Set(labelsJSON, name, volumeJSONFileName); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tlog.L.Warnf(\"volume %q already exists and will be returned as-is\", name)\n\t\t// FIXME: we do not check if the existing volume has the same labels as requested - should we?\n\t}\n\n\t// At this point, we either have an existing volume, or created a new one successfully\n\tvol = &native.Volume{\n\t\tName: name,\n\t}\n\n\tif err = vs.manager.GroupEnsure(name, dataDirName); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif vol.Mountpoint, err = vs.manager.Location(name, dataDirName); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vol, nil\n}\n\n// Private helpers\nfunc labels(b []byte) *map[string]string {\n\ttype volumeOpts struct {\n\t\tLabels *map[string]string `json:\"labels,omitempty\"`\n\t}\n\tvar vo volumeOpts\n\tif err := json.Unmarshal(b, &vo); err != nil {\n\t\treturn nil\n\t}\n\treturn vo.Labels\n}\n"
  },
  {
    "path": "pkg/namestore/namestore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package namestore provides a simple store for containers to exclusively acquire and release names.\n// All methods are safe to use concurrently.\n// Note that locking of the store is done at the namespace level.\n// The namestore is currently used by container create, remove, rename, and as part of the ocihook events cycle.\npackage namestore\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/identifiers\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\n// ErrNameStore will wrap all errors here\nvar ErrNameStore = errors.New(\"name-store error\")\n\n// New will return a NameStore for a given namespace.\nfunc New(stateDir, namespace string) (NameStore, error) {\n\tif namespace == \"\" {\n\t\treturn nil, errors.Join(ErrNameStore, store.ErrInvalidArgument)\n\t}\n\n\tst, err := store.New(filepath.Join(stateDir, \"names\", namespace), 0, 0)\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrNameStore, err)\n\t}\n\n\treturn &nameStore{\n\t\tsafeStore: st,\n\t}, nil\n}\n\n// NameStore allows acquiring, releasing and renaming.\n// \"names\" must abide by identifiers.ValidateDockerCompat\n// A container cannot release or rename a name it does not own.\n// A container cannot acquire a name that is already owned by another container.\n// Re-acquiring a name does not error and is a no-op.\n// Double releasing a name will error.\n// Note that technically a given container may acquire multiple different names, although this is not\n// something we do in the codebase.\ntype NameStore interface {\n\t// Acquire exclusively grants `name` to container with `id`.\n\tAcquire(name, id string) error\n\t// Acquire allows the container owning a specific name to release it\n\tRelease(name, id string) error\n\t// Rename allows the container owning a specific name to change it to newName (if available)\n\tRename(oldName, id, newName string) error\n}\n\ntype nameStore struct {\n\tsafeStore store.Store\n}\n\nfunc (x *nameStore) Acquire(name, id string) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrNameStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn err\n\t}\n\n\treturn x.safeStore.WithLock(func() error {\n\t\tvar previousID []byte\n\t\tpreviousID, err = x.safeStore.Get(name)\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, store.ErrNotFound) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if string(previousID) == \"\" {\n\t\t\t// This has happened in the past, probably following some other error condition of OS restart\n\t\t\t// We do warn about it, but do not hard-error and let the new container acquire the name\n\t\t\tlog.L.Warnf(\"name %q was locked by an empty id - this is abnormal and should be reported\", name)\n\t\t} else if string(previousID) != id {\n\t\t\t// If the name is already used by another container, that is a hard error\n\t\t\treturn fmt.Errorf(\"name %q is already used by ID %q\", name, previousID)\n\t\t}\n\n\t\t// If the id was the same, we are \"re-acquiring\".\n\t\t// Maybe containerd was bounced, so previously running containers that would get restarted will go again through\n\t\t// onCreateRuntime (unlike in a \"normal\" stop/start flow), without ever had gone through onPostStop.\n\t\t// As such, reacquiring by the same id is not a bug...\n\t\t// See: https://github.com/containerd/nerdctl/issues/3354\n\t\treturn x.safeStore.Set([]byte(id), name)\n\t})\n}\n\nfunc (x *nameStore) Release(name, id string) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrNameStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(name); err != nil {\n\t\treturn err\n\t}\n\n\treturn x.safeStore.WithLock(func() error {\n\t\tvar content []byte\n\t\tcontent, err = x.safeStore.Get(name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif string(content) != id {\n\t\t\t// Never seen this, but technically possible if downstream code is messed-up\n\t\t\treturn fmt.Errorf(\"cannot release name %q (used by ID %q, not by %q)\", name, content, id)\n\t\t}\n\n\t\treturn x.safeStore.Delete(name)\n\t})\n}\n\nfunc (x *nameStore) Rename(oldName, id, newName string) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrNameStore, err)\n\t\t}\n\t}()\n\n\tif err = identifiers.ValidateDockerCompat(newName); err != nil {\n\t\treturn err\n\t}\n\n\treturn x.safeStore.WithLock(func() error {\n\t\tvar doesExist bool\n\t\tvar content []byte\n\t\tdoesExist, err = x.safeStore.Exists(newName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif doesExist {\n\t\t\tcontent, err = x.safeStore.Get(newName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"name %q is already used by ID %q\", newName, string(content))\n\t\t}\n\n\t\tcontent, err = x.safeStore.Get(oldName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif string(content) != id {\n\t\t\treturn fmt.Errorf(\"name %q is used by ID %q, not by %q\", oldName, content, id)\n\t\t}\n\n\t\terr = x.safeStore.Set(content, newName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn x.safeStore.Delete(oldName)\n\t})\n}\n"
  },
  {
    "path": "pkg/namestore/namestore_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage namestore\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nfunc TestNamestoreNew(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\ttests := []struct {\n\t\tname      string\n\t\tnamespace string\n\t\twantErr   bool\n\t\terrChecks []error\n\t}{\n\t\t{\n\t\t\tname:      \"empty namespace\",\n\t\t\tnamespace: \"\",\n\t\t\twantErr:   true,\n\t\t\terrChecks: []error{ErrNameStore, store.ErrInvalidArgument},\n\t\t},\n\t\t{\n\t\t\tname:      \"valid namespace\",\n\t\t\tnamespace: \"testnamespace\",\n\t\t\twantErr:   false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tns, err := New(tempDir, tt.namespace)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Assert(t, err != nil, \"New should return an error for %s\", tt.name)\n\t\t\t\tfor _, errCheck := range tt.errChecks {\n\t\t\t\t\tassert.ErrorIs(t, err, errCheck, \"Error should contain %v for %s\", errCheck, tt.name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tassert.NilError(t, err, \"New should succeed for %s\", tt.name)\n\t\t\t\tassert.Assert(t, ns != nil, \"New should return a non-nil NameStore for %s\", tt.name)\n\n\t\t\t\t// Check that the directory is created in the correct path\n\t\t\t\texpectedDir := filepath.Join(tempDir, \"names\", tt.namespace)\n\t\t\t\t_, err = os.Stat(expectedDir)\n\t\t\t\tassert.NilError(t, err, \"Directory should be created at the correct path for %s\", tt.name)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/netutil/cni_plugin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\ntype CNIPlugin interface {\n\tGetPluginType() string\n}\n\ntype IPAMRange struct {\n\tSubnet     string `json:\"subnet\"`\n\tRangeStart string `json:\"rangeStart,omitempty\"`\n\tRangeEnd   string `json:\"rangeEnd,omitempty\"`\n\tGateway    string `json:\"gateway,omitempty\"`\n\tIPRange    string `json:\"ipRange,omitempty\"`\n}\n\ntype IPAMRoute struct {\n\tDst     string `json:\"dst,omitempty\"`\n\tGW      string `json:\"gw,omitempty\"`\n\tGateway string `json:\"gateway,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/netutil/cni_plugin_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport \"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\n// bridgeConfig describes the bridge plugin\ntype bridgeConfig struct {\n\tPluginType   string                 `json:\"type\"`\n\tBrName       string                 `json:\"bridge,omitempty\"`\n\tIsGW         bool                   `json:\"isGateway,omitempty\"`\n\tIsDefaultGW  bool                   `json:\"isDefaultGateway,omitempty\"`\n\tForceAddress bool                   `json:\"forceAddress,omitempty\"`\n\tIPMasq       bool                   `json:\"ipMasq,omitempty\"`\n\tMTU          int                    `json:\"mtu,omitempty\"`\n\tHairpinMode  bool                   `json:\"hairpinMode,omitempty\"`\n\tPromiscMode  bool                   `json:\"promiscMode,omitempty\"`\n\tVlan         int                    `json:\"vlan,omitempty\"`\n\tIPAM         map[string]interface{} `json:\"ipam\"`\n\tCapabilities map[string]bool        `json:\"capabilities,omitempty\"`\n}\n\nfunc newBridgePlugin(bridgeName string) *bridgeConfig {\n\treturn &bridgeConfig{\n\t\tPluginType:   \"bridge\",\n\t\tBrName:       bridgeName,\n\t\tCapabilities: map[string]bool{},\n\t}\n}\n\nfunc (*bridgeConfig) GetPluginType() string {\n\treturn \"bridge\"\n}\n\n// vlanConfig describes the macvlan/ipvlan config\ntype vlanConfig struct {\n\tPluginType   string                 `json:\"type\"`\n\tMaster       string                 `json:\"master\"`\n\tMode         string                 `json:\"mode,omitempty\"`\n\tMTU          int                    `json:\"mtu,omitempty\"`\n\tIPAM         map[string]interface{} `json:\"ipam\"`\n\tCapabilities map[string]bool        `json:\"capabilities,omitempty\"`\n}\n\nfunc newVLANPlugin(pluginType string) *vlanConfig {\n\treturn &vlanConfig{\n\t\tPluginType:   pluginType,\n\t\tCapabilities: map[string]bool{},\n\t}\n}\n\nfunc (c *vlanConfig) GetPluginType() string {\n\treturn c.PluginType\n}\n\n// portMapConfig describes the portmapping plugin\ntype portMapConfig struct {\n\tPluginType   string          `json:\"type\"`\n\tCapabilities map[string]bool `json:\"capabilities\"`\n}\n\nfunc newPortMapPlugin() *portMapConfig {\n\treturn &portMapConfig{\n\t\tPluginType: \"portmap\",\n\t\tCapabilities: map[string]bool{\n\t\t\t\"portMappings\": true,\n\t\t},\n\t}\n}\n\nfunc (*portMapConfig) GetPluginType() string {\n\treturn \"portmap\"\n}\n\n// firewallConfig describes the firewall plugin\ntype firewallConfig struct {\n\tPluginType string `json:\"type\"`\n\tBackend    string `json:\"backend,omitempty\"`\n\n\t// IngressPolicy is supported since firewall plugin v1.1.0.\n\t// \"same-bridge\" mode replaces the deprecated \"isolation\" plugin.\n\t// \"isolated\" mode has been added since firewall plugin v1.7.1\n\tIngressPolicy string `json:\"ingressPolicy,omitempty\"`\n}\n\nfunc newFirewallPlugin(ingressPolicy string) *firewallConfig {\n\tif ingressPolicy != \"same-bridge\" && ingressPolicy != \"isolated\" {\n\t\tingressPolicy = \"same-bridge\" // Default to \"same-bridge\" if invalid value provided\n\t}\n\n\tc := &firewallConfig{\n\t\tPluginType:    \"firewall\",\n\t\tIngressPolicy: ingressPolicy,\n\t}\n\tif rootlessutil.IsRootless() {\n\t\t// https://github.com/containerd/nerdctl/issues/2818\n\t\tc.Backend = \"iptables\"\n\t}\n\treturn c\n}\n\nfunc (*firewallConfig) GetPluginType() string {\n\treturn \"firewall\"\n}\n\n// tuningConfig describes the tuning plugin\ntype tuningConfig struct {\n\tPluginType string `json:\"type\"`\n}\n\nfunc newTuningPlugin() *tuningConfig {\n\treturn &tuningConfig{\n\t\tPluginType: \"tuning\",\n\t}\n}\n\nfunc (*tuningConfig) GetPluginType() string {\n\treturn \"tuning\"\n}\n\n// https://github.com/containernetworking/plugins/blob/v1.0.1/plugins/ipam/host-local/backend/allocator/config.go#L47-L56\ntype hostLocalIPAMConfig struct {\n\tType        string        `json:\"type\"`\n\tRoutes      []IPAMRoute   `json:\"routes,omitempty\"`\n\tResolveConf string        `json:\"resolveConf,omitempty\"`\n\tDataDir     string        `json:\"dataDir,omitempty\"`\n\tRanges      [][]IPAMRange `json:\"ranges,omitempty\"`\n}\n\nfunc newHostLocalIPAMConfig() *hostLocalIPAMConfig {\n\treturn &hostLocalIPAMConfig{\n\t\tType: \"host-local\",\n\t}\n}\n\n// https://github.com/containernetworking/plugins/blob/v1.4.1/plugins/ipam/dhcp/main.go#L43-L54\ntype dhcpIPAMConfig struct {\n\tType             string          `json:\"type\"`\n\tDaemonSocketPath string          `json:\"daemonSocketPath\"`\n\tProvideOptions   []provideOption `json:\"provide,omitempty\"`\n\tRequestOptions   []requestOption `json:\"request,omitempty\"`\n}\n\ntype provideOption struct {\n\tOption string `json:\"option\"`\n\n\tValue           string `json:\"value\"`\n\tValueFromCNIArg string `json:\"fromArg\"`\n}\n\ntype requestOption struct {\n\tSkipDefault bool `json:\"skipDefault\"`\n\n\tOption string `json:\"option\"`\n}\n\nfunc newDHCPIPAMConfig() *dhcpIPAMConfig {\n\treturn &dhcpIPAMConfig{\n\t\tType: \"dhcp\",\n\t}\n}\n"
  },
  {
    "path": "pkg/netutil/cni_plugin_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\ntype natConfig struct {\n\tPluginType string                 `json:\"type\"`\n\tMaster     string                 `json:\"master,omitempty\"`\n\tIPAM       map[string]interface{} `json:\"ipam\"`\n}\n\nfunc (*natConfig) GetPluginType() string {\n\treturn \"nat\"\n}\n\nfunc newNatPlugin(master string) *natConfig {\n\treturn &natConfig{\n\t\tPluginType: \"nat\",\n\t\tMaster:     master,\n\t}\n}\n\n// https://github.com/microsoft/windows-container-networking/blob/v0.2.0/cni/cni.go#L55-L63\ntype windowsIpamConfig struct {\n\tType          string      `json:\"type\"`\n\tEnvironment   string      `json:\"environment,omitempty\"`\n\tAddrSpace     string      `json:\"addressSpace,omitempty\"`\n\tSubnet        string      `json:\"subnet,omitempty\"`\n\tAddress       string      `json:\"ipAddress,omitempty\"`\n\tQueryInterval string      `json:\"queryInterval,omitempty\"`\n\tRoutes        []IPAMRoute `json:\"routes,omitempty\"`\n}\n\nfunc newWindowsIPAMConfig() *windowsIpamConfig {\n\treturn &windowsIpamConfig{}\n}\n"
  },
  {
    "path": "pkg/netutil/nettype/nettype.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nettype\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Type int\n\nconst (\n\tInvalid Type = iota\n\tNone\n\tHost\n\tCNI\n\tContainer\n\tNamespace\n)\n\nvar netTypeToName = map[interface{}]string{\n\tInvalid:   \"invalid\",\n\tNone:      \"none\",\n\tHost:      \"host\",\n\tCNI:       \"cni\",\n\tContainer: \"container\",\n\tNamespace: \"ns\",\n}\n\nfunc Detect(names []string) (Type, error) {\n\tvar res Type\n\n\tfor _, name := range names {\n\t\tvar tmp Type\n\n\t\t// In case of using --network=container:<container> to share the network namespace\n\t\tnetworkName := strings.SplitN(name, \":\", 2)[0]\n\t\tswitch networkName {\n\t\tcase \"none\":\n\t\t\ttmp = None\n\t\tcase \"host\":\n\t\t\ttmp = Host\n\t\tcase \"container\":\n\t\t\ttmp = Container\n\t\tcase \"ns\":\n\t\t\ttmp = Namespace\n\t\tdefault:\n\t\t\ttmp = CNI\n\t\t}\n\t\tif res != Invalid && res != tmp {\n\t\t\treturn Invalid, fmt.Errorf(\"mixed network types: %v and %v\", netTypeToName[res], netTypeToName[tmp])\n\t\t}\n\t\tres = tmp\n\t}\n\n\t// defaults to CNI\n\tif res == Invalid {\n\t\tres = CNI\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "pkg/netutil/nettype/nettype_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nettype\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestDetect(t *testing.T) {\n\ttype testCase struct {\n\t\tnames    []string\n\t\texpected Type\n\t\terr      string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tnames:    nil,\n\t\t\texpected: CNI,\n\t\t},\n\t\t{\n\t\t\tnames:    []string{\"none\"},\n\t\t\texpected: None,\n\t\t},\n\t\t{\n\t\t\tnames:    []string{\"host\"},\n\t\t\texpected: Host,\n\t\t},\n\t\t{\n\t\t\tnames:    []string{\"bridge\"},\n\t\t\texpected: CNI,\n\t\t},\n\t\t{\n\t\t\tnames:    []string{\"foo\", \"bar\"},\n\t\t\texpected: CNI,\n\t\t},\n\t\t{\n\t\t\tnames:    []string{\"foo\", \"bar\", \"bridge\"},\n\t\t\texpected: CNI,\n\t\t},\n\t\t{\n\t\t\tnames: []string{\"none\", \"host\"},\n\t\t\terr:   \"mixed network types\",\n\t\t},\n\t\t{\n\t\t\tnames: []string{\"none\", \"bridge\"},\n\t\t\terr:   \"mixed network types\",\n\t\t},\n\t\t{\n\t\t\tnames: []string{\"host\", \"foo\"},\n\t\t\terr:   \"mixed network types\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tgot, err := Detect(tc.names)\n\t\tif tc.err == \"\" {\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, tc.expected, got)\n\t\t} else {\n\t\t\tassert.ErrorContains(t, err, tc.err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/netutil/netutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strconv\"\n\n\t\"github.com/containernetworking/cni/libcni\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/pkg/namespaces\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/nettype\"\n\tsubnetutil \"github.com/containerd/nerdctl/v2/pkg/netutil/subnet\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\ntype CNIEnv struct {\n\tPath        string\n\tNetconfPath string\n\tNamespace   string\n}\n\ntype CNIEnvOpt func(e *CNIEnv) error\n\nfunc (e *CNIEnv) ListNetworksMatch(reqs []string, allowPseudoNetwork bool) (list map[string][]*NetworkConfig, errs []error) {\n\tnetworkConfigs, err := fsRead(e)\n\tif err != nil {\n\t\treturn nil, []error{err}\n\t}\n\n\tlist = make(map[string][]*NetworkConfig)\n\tfor _, req := range reqs {\n\t\tif !allowPseudoNetwork && (req == \"host\" || req == \"none\") {\n\t\t\terrs = append(errs, fmt.Errorf(\"pseudo network not allowed: %s\", req))\n\t\t\tcontinue\n\t\t}\n\n\t\tresult := []*NetworkConfig{}\n\t\t// First match by name\n\t\tfor _, networkConfig := range networkConfigs {\n\t\t\tif networkConfig.Name == req {\n\t\t\t\tresult = append(result, networkConfig)\n\t\t\t}\n\t\t}\n\t\t// If nothing, try to match the id\n\t\tif len(result) == 0 {\n\t\t\tfor _, networkConfig := range networkConfigs {\n\t\t\t\tif networkConfig.NerdctlID != nil {\n\t\t\t\t\tif len(req) <= len((*networkConfig.NerdctlID)) && (*networkConfig.NerdctlID)[0:len(req)] == req {\n\t\t\t\t\t\tresult = append(result, networkConfig)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlist[req] = result\n\t}\n\n\treturn list, errs\n}\n\nfunc UsedNetworks(ctx context.Context, client *containerd.Client) (map[string][]string, error) {\n\tnsService := client.NamespaceService()\n\tnsList, err := nsService.List(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tused := make(map[string][]string)\n\tfor _, ns := range nsList {\n\t\tnsCtx := namespaces.WithNamespace(ctx, ns)\n\t\tcontainers, err := client.Containers(nsCtx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnsUsedN, err := namespaceUsedNetworks(nsCtx, containers)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// merge\n\t\tfor k, v := range nsUsedN {\n\t\t\tif value, ok := used[k]; ok {\n\t\t\t\tused[k] = append(value, v...)\n\t\t\t} else {\n\t\t\t\tused[k] = v\n\t\t\t}\n\t\t}\n\t}\n\treturn used, nil\n}\n\nfunc namespaceUsedNetworks(ctx context.Context, containers []containerd.Container) (map[string][]string, error) {\n\tused := make(map[string][]string)\n\tfor _, c := range containers {\n\t\t// Only tasks under the ctx namespace can be obtained here\n\t\ttask, err := c.Task(ctx, nil)\n\t\tif err != nil {\n\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\tlog.G(ctx).Debugf(\"task not found - likely container %q was removed\", c.ID())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tstatus, err := task.Status(ctx)\n\t\tif err != nil {\n\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\tlog.G(ctx).Debugf(\"task not found - likely container %q was removed\", c.ID())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch status.Status {\n\t\tcase containerd.Paused, containerd.Running:\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tl, err := c.Labels(ctx)\n\t\tif err != nil {\n\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\tlog.G(ctx).Debugf(\"container %q is gone\", c.ID())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tnetworkJSON, ok := l[labels.Networks]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tvar networks []string\n\t\tif err := json.Unmarshal([]byte(networkJSON), &networks); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnetType, err := nettype.Detect(networks)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif netType != nettype.CNI {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, n := range networks {\n\t\t\tused[n] = append(used[n], c.ID())\n\t\t}\n\t}\n\treturn used, nil\n}\n\nfunc WithDefaultNetwork(bridgeIP string) CNIEnvOpt {\n\treturn func(e *CNIEnv) error {\n\t\treturn e.ensureDefaultNetworkConfig(bridgeIP)\n\t}\n}\n\nfunc WithNamespace(namespace string) CNIEnvOpt {\n\treturn func(e *CNIEnv) error {\n\t\terr := fsEnsureRoot(e, namespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\te.Namespace = namespace\n\t\treturn nil\n\t}\n}\n\nfunc NewCNIEnv(cniPath, cniConfPath string, opts ...CNIEnvOpt) (*CNIEnv, error) {\n\te := CNIEnv{\n\t\tPath:        cniPath,\n\t\tNetconfPath: cniConfPath,\n\t}\n\n\tif err := fsEnsureRoot(&e, \"\"); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, o := range opts {\n\t\tif err := o(&e); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &e, nil\n}\n\nfunc (e *CNIEnv) NetworkList() ([]*NetworkConfig, error) {\n\treturn fsRead(e)\n}\n\nfunc (e *CNIEnv) NetworkMap() (map[string]*NetworkConfig, error) { //nolint:revive\n\tnetConfigList, err := fsRead(e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tm := make(map[string]*NetworkConfig, len(netConfigList))\n\tfor _, n := range netConfigList {\n\t\tif original, exists := m[n.Name]; exists {\n\t\t\tlog.L.Warnf(\"duplicate network name %q, %#v will get superseded by %#v\", n.Name, original, n)\n\t\t}\n\t\tm[n.Name] = n\n\t}\n\treturn m, nil\n}\n\nfunc (e *CNIEnv) NetworkByNameOrID(key string) (*NetworkConfig, error) {\n\tnetConfigList, err := fsRead(e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, n := range netConfigList {\n\t\tif n.Name == key {\n\t\t\treturn n, nil\n\t\t}\n\t\tif n.NerdctlID != nil && (*n.NerdctlID == key || (*n.NerdctlID)[0:12] == key) {\n\t\t\treturn n, nil\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"no such network: %q\", key)\n}\n\nfunc (e *CNIEnv) filterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkConfig, error) {\n\tnetConfigList, err := fsRead(e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult := []*NetworkConfig{}\n\tfor _, networkConfig := range netConfigList {\n\t\tif filterf(networkConfig) {\n\t\t\tresult = append(result, networkConfig)\n\t\t}\n\t}\n\treturn result, nil\n}\n\nfunc (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) {\n\tusedSubnets, err := subnetutil.GetLiveNetworkSubnets()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnetConfigList, err := fsRead(e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, netConf := range netConfigList {\n\t\tusedSubnets = append(usedSubnets, netConf.subnets()...)\n\t}\n\treturn usedSubnets, nil\n}\n\ntype NetworkConfig struct {\n\t*libcni.NetworkConfigList\n\tNerdctlID     *string\n\tNerdctlLabels *map[string]string\n\tFile          string\n}\n\ntype cniNetworkConfig struct {\n\tCNIVersion string            `json:\"cniVersion\"`\n\tName       string            `json:\"name\"`\n\tID         string            `json:\"nerdctlID\"`\n\tLabels     map[string]string `json:\"nerdctlLabels\"`\n\tPlugins    []CNIPlugin       `json:\"plugins\"`\n}\n\nfunc (e *CNIEnv) CreateNetwork(opts types.NetworkCreateOptions) (*NetworkConfig, error) { //nolint:revive\n\tvar netConf *NetworkConfig\n\n\tnetMap, err := e.NetworkMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// See note in fsWrite. Just because it does not exist now does not guarantee it will still not exist later.\n\t// This is more a perf optimization at this point than a true check.\n\tif _, ok := netMap[opts.Name]; ok {\n\t\treturn nil, errdefs.ErrAlreadyExists\n\t}\n\tipam, err := e.generateIPAM(opts.IPAMDriver, opts.Subnets, opts.Gateway, opts.IPRange, opts.IPAMOptions, opts.IPv6, opts.Internal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tplugins, err := e.generateCNIPlugins(opts.Driver, opts.Name, ipam, opts.Options, opts.IPv6, opts.Internal)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetConf, err = e.generateNetworkConfig(opts.Name, opts.Labels, plugins)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = fsWrite(e, netConf)\n\n\t// See note above. If it exists, we got raced out by another process. Consider this to NOT be a hard error.\n\tif err != nil && !errdefs.IsAlreadyExists(err) {\n\t\treturn nil, err\n\t}\n\treturn netConf, nil\n}\n\nfunc (e *CNIEnv) RemoveNetwork(net *NetworkConfig) error {\n\treturn fsRemove(e, net)\n}\n\n// GetDefaultNetworkConfig checks whether the default network exists\n// by first searching for if any network bears the `labels.NerdctlDefaultNetwork`\n// label, or falls back to checking whether any network bears the\n// `DefaultNetworkName` name.\nfunc (e *CNIEnv) GetDefaultNetworkConfig() (*NetworkConfig, error) {\n\t// Search for networks bearing the `labels.NerdctlDefaultNetwork` label.\n\tdefaultLabelFilterF := func(nc *NetworkConfig) bool {\n\t\tif nc.NerdctlLabels == nil {\n\t\t\treturn false\n\t\t} else if _, ok := (*nc.NerdctlLabels)[labels.NerdctlDefaultNetwork]; ok {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\tlabelMatches, err := e.filterNetworks(defaultLabelFilterF)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(labelMatches) >= 1 {\n\t\tif len(labelMatches) > 1 {\n\t\t\tlog.L.Warnf(\"returning the first network bearing the %q label out of the multiple found: %#v\", labels.NerdctlDefaultNetwork, labelMatches)\n\t\t}\n\t\treturn labelMatches[0], nil\n\t}\n\n\t// Search for networks bearing the DefaultNetworkName.\n\tdefaultNameFilterF := func(nc *NetworkConfig) bool {\n\t\treturn nc.Name == DefaultNetworkName\n\t}\n\tnameMatches, err := e.filterNetworks(defaultNameFilterF)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nameMatches) >= 1 {\n\t\tif len(nameMatches) > 1 {\n\t\t\tlog.L.Warnf(\"returning the first network bearing the %q default network name out of the multiple found: %#v\", DefaultNetworkName, nameMatches)\n\t\t}\n\n\t\t// Warn the user if the default network was not created by nerdctl.\n\t\tmatch := nameMatches[0]\n\t\texists, statErr := fsExists(e, DefaultNetworkName)\n\t\tif match.NerdctlID == nil || statErr != nil || !exists {\n\t\t\tlog.L.Warnf(\"default network named %q does not have an internal nerdctl ID or nerdctl-managed config file, it was most likely NOT created by nerdctl\", DefaultNetworkName)\n\t\t}\n\n\t\treturn nameMatches[0], nil\n\t}\n\n\treturn nil, nil\n}\n\nfunc (e *CNIEnv) ensureDefaultNetworkConfig(bridgeIP string) error {\n\tdefaultNet, err := e.GetDefaultNetworkConfig()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check for default network: %w\", err)\n\t}\n\tif defaultNet == nil {\n\t\tif err := e.createDefaultNetworkConfig(bridgeIP); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create default network: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (e *CNIEnv) createDefaultNetworkConfig(bridgeIP string) error {\n\texist, err := fsExists(e, DefaultNetworkName)\n\tif err != nil && !os.IsNotExist(err) {\n\t\treturn err\n\t}\n\tif exist {\n\t\treturn fmt.Errorf(\"already found existing network config, cannot create new network named %q\", DefaultNetworkName)\n\t}\n\n\tbridgeCIDR := DefaultCIDR\n\tbridgeGatewayIP := \"\"\n\tif bridgeIP != \"\" {\n\t\tbIP, bCIDR, err := net.ParseCIDR(bridgeIP)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid bridge ip %s: %w\", bridgeIP, err)\n\t\t}\n\t\tbridgeGatewayIP = bIP.String()\n\t\tbridgeCIDR = bCIDR.String()\n\t}\n\topts := types.NetworkCreateOptions{\n\t\tName:       DefaultNetworkName,\n\t\tDriver:     DefaultNetworkName,\n\t\tSubnets:    []string{bridgeCIDR},\n\t\tGateway:    bridgeGatewayIP,\n\t\tIPAMDriver: \"default\",\n\t\tLabels:     []string{fmt.Sprintf(\"%s=true\", labels.NerdctlDefaultNetwork)},\n\t}\n\n\t_, err = e.CreateNetwork(opts)\n\tif err != nil && !errdefs.IsAlreadyExists(err) {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// generateNetworkConfig creates NetworkConfig.\n// generateNetworkConfig does not fill \"File\" field.\nfunc (e *CNIEnv) generateNetworkConfig(name string, labels []string, plugins []CNIPlugin) (*NetworkConfig, error) {\n\tif name == \"\" || len(plugins) == 0 {\n\t\treturn nil, errdefs.ErrInvalidArgument\n\t}\n\tfor _, f := range plugins {\n\t\tp := filepath.Join(e.Path, f.GetPluginType())\n\t\tif _, err := exec.LookPath(p); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"needs CNI plugin %q to be installed in CNI_PATH (%q), see https://github.com/containernetworking/plugins/releases: %w\", f.GetPluginType(), e.Path, err)\n\t\t}\n\t}\n\tid := networkID(name)\n\tlabelsMap := strutil.ConvertKVStringsToMap(labels)\n\n\tconf := &cniNetworkConfig{\n\t\tCNIVersion: \"1.0.0\",\n\t\tName:       name,\n\t\tID:         id,\n\t\tLabels:     labelsMap,\n\t\tPlugins:    plugins,\n\t}\n\n\tconfJSON, err := json.MarshalIndent(conf, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tl, err := libcni.ConfListFromBytes(confJSON)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &NetworkConfig{\n\t\tNetworkConfigList: l,\n\t\tNerdctlID:         &id,\n\t\tNerdctlLabels:     &labelsMap,\n\t\tFile:              \"\",\n\t}, nil\n}\n\nfunc wrapCNIError(fileName string, err error) error {\n\treturn fmt.Errorf(\"failed marshalling json out of network configuration file %q: %w\\n\"+\n\t\t\"For details on the schema, see https://pkg.go.dev/github.com/containernetworking/cni/libcni#NetworkConfigList\", fileName, err)\n}\n\nfunc cniLoad(fileNames []string) (configList []*NetworkConfig, err error) {\n\tvar fileName string\n\n\tsort.Strings(fileNames)\n\n\tfor _, fileName = range fileNames {\n\t\tvar bytes []byte\n\t\tbytes, err = filesystem.ReadFile(fileName)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading %s: %w\", fileName, err)\n\t\t}\n\n\t\tvar netConfigList *libcni.NetworkConfigList\n\t\tnetConfigList, err = libcni.NetworkConfFromBytes(bytes)\n\t\tif err != nil {\n\t\t\treturn nil, wrapCNIError(fileName, err)\n\t\t}\n\t\tid, nerdctlLabels := nerdctlIDLabels(netConfigList.Bytes)\n\t\tconfigList = append(configList, &NetworkConfig{\n\t\t\tNetworkConfigList: netConfigList,\n\t\t\tNerdctlID:         id,\n\t\t\tNerdctlLabels:     nerdctlLabels,\n\t\t\tFile:              fileName,\n\t\t})\n\t}\n\n\treturn configList, nil\n}\n\nfunc nerdctlIDLabels(b []byte) (*string, *map[string]string) {\n\ttype idLabels struct {\n\t\tID     *string            `json:\"nerdctlID,omitempty\"`\n\t\tLabels *map[string]string `json:\"nerdctlLabels,omitempty\"`\n\t}\n\tvar idl idLabels\n\tif err := json.Unmarshal(b, &idl); err != nil {\n\t\treturn nil, nil\n\t}\n\treturn idl.ID, idl.Labels\n}\n\nfunc networkID(name string) string {\n\thash := sha256.Sum256([]byte(name))\n\treturn hex.EncodeToString(hash[:])\n}\n\nfunc (e *CNIEnv) parseSubnet(subnetStr string) (*net.IPNet, error) {\n\tusedSubnets, err := e.usedSubnets()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif subnetStr == \"\" {\n\t\t_, defaultSubnet, _ := net.ParseCIDR(StartingCIDR)\n\t\tsubnet, err := subnetutil.GetFreeSubnet(defaultSubnet, usedSubnets)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn subnet, nil\n\t}\n\n\tsubnetIP, subnet, err := net.ParseCIDR(subnetStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse subnet %q\", subnetStr)\n\t}\n\tif !subnet.IP.Equal(subnetIP) {\n\t\treturn nil, fmt.Errorf(\"unexpected subnet %q, maybe you meant %q?\", subnetStr, subnet.String())\n\t}\n\tif subnetutil.IntersectsWithNetworks(subnet, usedSubnets) {\n\t\treturn nil, fmt.Errorf(\"subnet %s overlaps with other one on this address space\", subnetStr)\n\t}\n\treturn subnet, nil\n}\n\nfunc parseIPAMRange(subnet *net.IPNet, gatewayStr, ipRangeStr string) (*IPAMRange, error) {\n\tvar gateway, rangeStart, rangeEnd net.IP\n\tif gatewayStr != \"\" {\n\t\tgatewayIP := net.ParseIP(gatewayStr)\n\t\tif gatewayIP == nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse gateway %q\", gatewayStr)\n\t\t}\n\t\tif !subnet.Contains(gatewayIP) {\n\t\t\treturn nil, fmt.Errorf(\"no matching subnet %q for gateway %q\", subnet, gatewayStr)\n\t\t}\n\t\tgateway = gatewayIP\n\t} else {\n\t\tgateway, _ = subnetutil.FirstIPInSubnet(subnet)\n\t}\n\n\tres := &IPAMRange{\n\t\tSubnet:  subnet.String(),\n\t\tGateway: gateway.String(),\n\t}\n\n\tif ipRangeStr != \"\" {\n\t\t_, ipRange, err := net.ParseCIDR(ipRangeStr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse ip-range %q\", ipRangeStr)\n\t\t}\n\t\trangeStart, _ = subnetutil.FirstIPInSubnet(ipRange)\n\t\trangeEnd, _ = subnetutil.LastIPInSubnet(ipRange)\n\t\tif !subnet.Contains(rangeStart) || !subnet.Contains(rangeEnd) {\n\t\t\treturn nil, fmt.Errorf(\"no matching subnet %q for ip-range %q\", subnet, ipRangeStr)\n\t\t}\n\t\tres.RangeStart = rangeStart.String()\n\t\tres.RangeEnd = rangeEnd.String()\n\t\tres.IPRange = ipRangeStr\n\t}\n\n\treturn res, nil\n}\n\n// convert the struct to a map\nfunc structToMap(in interface{}) (map[string]interface{}, error) {\n\tout := make(map[string]interface{})\n\tdata, err := json.Marshal(in)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := json.Unmarshal(data, &out); err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ParseMTU parses the mtu option\n// nolint:unused\nfunc parseMTU(mtu string) (int, error) {\n\tif mtu == \"\" {\n\t\treturn 0, nil // default\n\t}\n\tm, err := strconv.Atoi(mtu)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif m < 0 {\n\t\treturn 0, fmt.Errorf(\"mtu %d is less than zero\", m)\n\t}\n\treturn m, nil\n}\n"
  },
  {
    "path": "pkg/netutil/netutil_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\n// Tests whether nerdctl properly creates the default network when required.\n// On Linux, the default driver used will be \"bridge\". (netutil.DefaultNetworkName)\nfunc TestDefaultNetworkCreation(t *testing.T) {\n\tif rootlessutil.IsRootless() {\n\t\tt.Skip(\"must be superuser to create default network for this test\")\n\t}\n\n\ttestDefaultNetworkCreation(t)\n\ttestDefaultNetworkCreationWithBridgeIP(t)\n}\n"
  },
  {
    "path": "pkg/netutil/netutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"testing\"\n\t\"text/template\"\n\n\t\"gotest.tools/v3/assert\"\n\n\tncdefaults \"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nconst testBridgeIP = \"10.42.100.1/24\" // nolint:unused\n\nconst preExistingNetworkConfigTemplate = `\n{\n    \"cniVersion\": \"0.2.0\",\n    \"name\": \"{{ .network_name }}\",\n    \"type\": \"nat\",\n    \"master\": \"Ethernet\",\n    \"ipam\": {\n        \"subnet\": \"{{ .subnet }}\",\n        \"routes\": [\n            {\n                \"GW\": \"{{ .gateway }}\"\n            }\n        ]\n    },\n    \"capabilities\": {\n        \"portMappings\": true,\n        \"dns\": true\n    }\n}\n`\n\nfunc TestParseIPAMRange(t *testing.T) {\n\tt.Parallel()\n\ttype testCase struct {\n\t\tsubnet   string\n\t\tgateway  string\n\t\tiprange  string\n\t\texpected *IPAMRange\n\t\terr      string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tsubnet: \"10.1.100.0/24\",\n\t\t\texpected: &IPAMRange{\n\t\t\t\tSubnet:  \"10.1.100.0/24\",\n\t\t\t\tGateway: \"10.1.100.1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsubnet:  \"10.1.100.0/24\",\n\t\t\tgateway: \"10.1.10.100\",\n\t\t\terr:     \"no matching subnet\",\n\t\t},\n\t\t{\n\t\t\tsubnet:  \"10.1.100.0/24\",\n\t\t\tgateway: \"10.1.100.100\",\n\t\t\texpected: &IPAMRange{\n\t\t\t\tSubnet:  \"10.1.100.0/24\",\n\t\t\t\tGateway: \"10.1.100.100\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsubnet:  \"10.1.100.0/23\",\n\t\t\tgateway: \"10.1.102.1\",\n\t\t\terr:     \"no matching subnet\",\n\t\t},\n\t\t{\n\t\t\tsubnet:  \"10.1.0.0/16\",\n\t\t\tiprange: \"10.10.10.0/24\",\n\t\t\terr:     \"no matching subnet\",\n\t\t},\n\t\t{\n\t\t\tsubnet:  \"10.1.0.0/16\",\n\t\t\tiprange: \"10.1.100.0/24\",\n\t\t\texpected: &IPAMRange{\n\t\t\t\tSubnet:     \"10.1.0.0/16\",\n\t\t\t\tGateway:    \"10.1.0.1\",\n\t\t\t\tIPRange:    \"10.1.100.0/24\",\n\t\t\t\tRangeStart: \"10.1.100.1\",\n\t\t\t\tRangeEnd:   \"10.1.100.255\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsubnet:  \"10.1.100.0/23\",\n\t\t\tiprange: \"10.1.100.0/25\",\n\t\t\texpected: &IPAMRange{\n\t\t\t\tSubnet:     \"10.1.100.0/23\",\n\t\t\t\tGateway:    \"10.1.100.1\",\n\t\t\t\tIPRange:    \"10.1.100.0/25\",\n\t\t\t\tRangeStart: \"10.1.100.1\",\n\t\t\t\tRangeEnd:   \"10.1.100.127\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\t_, subnet, _ := net.ParseCIDR(tc.subnet)\n\t\tgot, err := parseIPAMRange(subnet, tc.gateway, tc.iprange)\n\t\tif tc.err != \"\" {\n\t\t\tassert.ErrorContains(t, err, tc.err)\n\t\t} else {\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, *tc.expected, *got)\n\t\t}\n\t}\n}\n\n// Tests whether nerdctl properly creates the default network when required.\n// Note that this test will require a CNI driver bearing the same name as\n// the type of the default network. (denoted by netutil.DefaultNetworkName,\n// which is used as both the name of the default network and its Driver)\n// nolint:unused\nfunc testDefaultNetworkCreation(t *testing.T) {\n\t// To prevent subnet collisions when attempting to recreate the default network\n\t// in the isolated CNI config dir we'll be using, we must first delete\n\t// the network in the default CNI config dir.\n\tdefaultCniEnv := CNIEnv{\n\t\tPath:        ncdefaults.CNIPath(),\n\t\tNetconfPath: ncdefaults.CNINetConfPath(),\n\t}\n\tdefaultNet, err := defaultCniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tif defaultNet != nil {\n\t\tassert.NilError(t, defaultCniEnv.RemoveNetwork(defaultNet))\n\t}\n\n\t// We create a tempdir for the CNI conf path to ensure an empty env for this test.\n\tcniConfTestDir := t.TempDir()\n\tcniEnv := CNIEnv{\n\t\tPath:        ncdefaults.CNIPath(),\n\t\tNetconfPath: cniConfTestDir,\n\t}\n\t// Ensure no default network config is not present.\n\tdefaultNetConf, err := cniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tassert.Assert(t, defaultNetConf == nil)\n\n\t// Attempt to create the default network.\n\terr = cniEnv.ensureDefaultNetworkConfig(\"\")\n\tassert.NilError(t, err)\n\n\t// Ensure default network config is present now.\n\tdefaultNetConf, err = cniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tassert.Assert(t, defaultNetConf != nil)\n\n\t// Check network config file present.\n\tstat, err := os.Stat(defaultNetConf.File)\n\tassert.NilError(t, err)\n\tfirstConfigModTime := stat.ModTime()\n\n\t// Check default network label present.\n\tassert.Assert(t, defaultNetConf.NerdctlLabels != nil)\n\tlstr, ok := (*defaultNetConf.NerdctlLabels)[labels.NerdctlDefaultNetwork]\n\tassert.Assert(t, ok)\n\tboolv, err := strconv.ParseBool(lstr)\n\tassert.NilError(t, err)\n\tassert.Assert(t, boolv)\n\n\t// Ensure network isn't created twice or accidentally re-created.\n\terr = cniEnv.ensureDefaultNetworkConfig(\"\")\n\tassert.NilError(t, err)\n\n\t// Check for any other network config files.\n\tfiles := []os.FileInfo{}\n\twalkF := func(p string, info os.FileInfo, err error) error {\n\t\tfiles = append(files, info)\n\t\treturn nil\n\t}\n\terr = filepath.Walk(cniConfTestDir, walkF)\n\tassert.NilError(t, err)\n\tassert.Equal(t, len(files), 3) // files[0] is the entry for '.', files[1] is the lock\n\tassert.Assert(t, filepath.Join(cniConfTestDir, files[2].Name()) == defaultNetConf.File)\n\tassert.Assert(t, firstConfigModTime.Equal(files[2].ModTime()))\n}\n\n// Tests whether nerdctl properly creates the default network\n// with a custom bridge IP and subnet.\n// nolint:unused\nfunc testDefaultNetworkCreationWithBridgeIP(t *testing.T) {\n\t// To prevent subnet collisions when attempting to recreate the default network\n\t// in the isolated CNI config dir we'll be using, we must first delete\n\t// the network in the default CNI config dir.\n\tdefaultCniEnv := CNIEnv{\n\t\tPath:        ncdefaults.CNIPath(),\n\t\tNetconfPath: ncdefaults.CNINetConfPath(),\n\t}\n\tdefaultNet, err := defaultCniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tif defaultNet != nil {\n\t\tassert.NilError(t, defaultCniEnv.RemoveNetwork(defaultNet))\n\t}\n\n\t// We create a tempdir for the CNI conf path to ensure an empty env for this test.\n\tcniConfTestDir := t.TempDir()\n\tcniEnv := CNIEnv{\n\t\tPath:        ncdefaults.CNIPath(),\n\t\tNetconfPath: cniConfTestDir,\n\t}\n\t// Ensure no default network config is not present.\n\tdefaultNetConf, err := cniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tassert.Assert(t, defaultNetConf == nil)\n\n\t// Attempt to create the default network with a test bridgeIP\n\terr = cniEnv.ensureDefaultNetworkConfig(testBridgeIP)\n\tassert.NilError(t, err)\n\n\t// Ensure default network config is present now.\n\tdefaultNetConf, err = cniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tassert.Assert(t, defaultNetConf != nil)\n\n\t// Check network config file present.\n\tstat, err := os.Stat(defaultNetConf.File)\n\tassert.NilError(t, err)\n\tfirstConfigModTime := stat.ModTime()\n\n\t// Check default network label present.\n\tassert.Assert(t, defaultNetConf.NerdctlLabels != nil)\n\tlstr, ok := (*defaultNetConf.NerdctlLabels)[labels.NerdctlDefaultNetwork]\n\tassert.Assert(t, ok)\n\tboolv, err := strconv.ParseBool(lstr)\n\tassert.NilError(t, err)\n\tassert.Assert(t, boolv)\n\n\t// Check bridge IP is set.\n\tassert.Assert(t, defaultNetConf.Plugins != nil)\n\tassert.Assert(t, len(defaultNetConf.Plugins) > 0)\n\tbridgePlugin := defaultNetConf.Plugins[0]\n\tvar bridgeConfig struct {\n\t\tType   string `json:\"type\"`\n\t\tBridge string `json:\"bridge\"`\n\t\tIPAM   struct {\n\t\t\tRanges [][]struct {\n\t\t\t\tGateway string `json:\"gateway\"`\n\t\t\t\tSubnet  string `json:\"subnet\"`\n\t\t\t} `json:\"ranges\"`\n\t\t\tRoutes []struct {\n\t\t\t\tDst string `json:\"dst\"`\n\t\t\t} `json:\"routes\"`\n\t\t\tType string `json:\"type\"`\n\t\t} `json:\"ipam\"`\n\t}\n\n\terr = json.Unmarshal(bridgePlugin.Bytes, &bridgeConfig)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse bridge plugin config: %v\", err)\n\t}\n\n\t// Assert on bridge plugin configuration\n\tassert.Equal(t, \"bridge\", bridgeConfig.Type)\n\t// Assert on IPAM configuration\n\tassert.Equal(t, \"10.42.100.1\", bridgeConfig.IPAM.Ranges[0][0].Gateway)\n\tassert.Equal(t, \"10.42.100.0/24\", bridgeConfig.IPAM.Ranges[0][0].Subnet)\n\tassert.Equal(t, \"0.0.0.0/0\", bridgeConfig.IPAM.Routes[0].Dst)\n\tassert.Equal(t, \"host-local\", bridgeConfig.IPAM.Type)\n\n\t// Ensure network isn't created twice or accidentally re-created.\n\terr = cniEnv.ensureDefaultNetworkConfig(testBridgeIP)\n\tassert.NilError(t, err)\n\n\t// Check for any other network config files.\n\tfiles := []os.FileInfo{}\n\twalkF := func(p string, info os.FileInfo, err error) error {\n\t\tfiles = append(files, info)\n\t\treturn nil\n\t}\n\terr = filepath.Walk(cniConfTestDir, walkF)\n\tassert.NilError(t, err)\n\tassert.Equal(t, len(files), 3) // files[0] is the entry for '.', files[1] is the lock\n\tassert.Assert(t, filepath.Join(cniConfTestDir, files[2].Name()) == defaultNetConf.File)\n\tassert.Assert(t, firstConfigModTime.Equal(files[2].ModTime()))\n}\n\n// Tests whether nerdctl skips the creation of the default network if a\n// network bearing the default network name already exists in a\n// non-nerdctl-managed network config file.\nfunc TestNetworkWithDefaultNameAlreadyExists(t *testing.T) {\n\t// We create a tempdir for the CNI conf path to ensure an empty env for this test.\n\tcniConfTestDir := t.TempDir()\n\tcniEnv := CNIEnv{\n\t\tPath:        t.TempDir(), // irrelevant for this test\n\t\tNetconfPath: cniConfTestDir,\n\t}\n\n\t// Ensure no default network config is not present.\n\tdefaultNetConf, err := cniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tassert.Assert(t, defaultNetConf == nil)\n\n\t// Manually define and write a network config file.\n\tvalues := map[string]string{\n\t\t\"network_name\": DefaultNetworkName,\n\t\t\"subnet\":       \"10.7.1.1/24\",\n\t\t\"gateway\":      \"10.7.1.1\",\n\t}\n\ttpl, err := template.New(\"test\").Parse(preExistingNetworkConfigTemplate)\n\tassert.NilError(t, err)\n\tbuf := &bytes.Buffer{}\n\tassert.NilError(t, tpl.ExecuteTemplate(buf, \"test\", values))\n\n\t// Filename is irrelevant as long as it's not nerdctl's.\n\ttestConfFile := filepath.Join(cniConfTestDir, fmt.Sprintf(\"%s.conf\", testutil.Identifier(t)))\n\terr = filesystem.WriteFile(testConfFile, buf.Bytes(), 0600)\n\tassert.NilError(t, err)\n\n\t// Check network is detected.\n\tnetConfs, err := cniEnv.NetworkList()\n\tassert.NilError(t, err)\n\tassert.Assert(t, len(netConfs) > 0)\n\n\tvar listedDefaultNetConf *NetworkConfig\n\tfor _, netConf := range netConfs {\n\t\tif netConf.Name == DefaultNetworkName {\n\t\t\tlistedDefaultNetConf = netConf\n\t\t\tbreak\n\t\t}\n\t}\n\tassert.Assert(t, listedDefaultNetConf != nil)\n\n\tdefaultNetConf, err = cniEnv.GetDefaultNetworkConfig()\n\tassert.NilError(t, err)\n\tassert.Assert(t, defaultNetConf != nil)\n\tassert.Assert(t, defaultNetConf.File == testConfFile)\n\n\terr = cniEnv.ensureDefaultNetworkConfig(\"\")\n\tassert.NilError(t, err)\n\n\tnetConfs, err = cniEnv.NetworkList()\n\tassert.NilError(t, err)\n\tdefaultNamedNetworksFileDefinitions := []string{}\n\tfor _, netConf := range netConfs {\n\t\tif netConf.Name == DefaultNetworkName {\n\t\t\tdefaultNamedNetworksFileDefinitions = append(defaultNamedNetworksFileDefinitions, netConf.File)\n\t\t}\n\t}\n\tassert.Assert(t, len(defaultNamedNetworksFileDefinitions) == 1)\n\tassert.Assert(t, defaultNamedNetworksFileDefinitions[0] == testConfFile)\n}\n"
  },
  {
    "path": "pkg/netutil/netutil_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"github.com/go-viper/mapstructure/v2\"\n\t\"github.com/vishvananda/netlink\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/systemutil\"\n)\n\nconst (\n\tDefaultNetworkName = \"bridge\"\n\tDefaultCIDR        = \"10.4.0.0/24\"\n\tDefaultIPAMDriver  = \"host-local\"\n\n\t// When creating non-default network without passing in `--subnet` option,\n\t// nerdctl assigns subnet address for the creation starting from `StartingCIDR`\n\t// This prevents subnet address overlapping with `DefaultCIDR` used by the default network\n\tStartingCIDR = \"10.4.1.0/24\"\n)\n\nfunc (n *NetworkConfig) subnets() []*net.IPNet {\n\tvar subnets []*net.IPNet\n\tif len(n.Plugins) > 0 && n.Plugins[0].Network.Type == \"bridge\" {\n\t\tvar bridge bridgeConfig\n\t\tif err := json.Unmarshal(n.Plugins[0].Bytes, &bridge); err != nil {\n\t\t\treturn subnets\n\t\t}\n\t\tif bridge.IPAM[\"type\"] != \"host-local\" {\n\t\t\treturn subnets\n\t\t}\n\t\tvar ipam hostLocalIPAMConfig\n\t\tif err := mapstructure.Decode(bridge.IPAM, &ipam); err != nil {\n\t\t\treturn subnets\n\t\t}\n\t\tfor _, irange := range ipam.Ranges {\n\t\t\tif len(irange) > 0 {\n\t\t\t\t_, subnet, err := net.ParseCIDR(irange[0].Subnet)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsubnets = append(subnets, subnet)\n\t\t\t}\n\t\t}\n\t}\n\treturn subnets\n}\n\nfunc (n *NetworkConfig) clean() error {\n\t// Remove the bridge network interface on the host.\n\tif len(n.Plugins) > 0 && n.Plugins[0].Network.Type == \"bridge\" {\n\t\tvar bridge bridgeConfig\n\t\tif err := json.Unmarshal(n.Plugins[0].Bytes, &bridge); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn removeBridgeNetworkInterface(bridge.BrName)\n\t}\n\treturn nil\n}\n\nfunc (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]interface{}, opts map[string]string, ipv6 bool, internal bool) ([]CNIPlugin, error) {\n\tvar (\n\t\tplugins []CNIPlugin\n\t\terr     error\n\t)\n\tswitch driver {\n\tcase \"bridge\":\n\t\tmtu := 0\n\t\tiPMasq := true\n\t\ticc := true\n\t\tfor opt, v := range opts {\n\t\t\tswitch opt {\n\t\t\tcase \"mtu\", \"com.docker.network.driver.mtu\":\n\t\t\t\tmtu, err = parseMTU(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\tcase \"ip-masq\", \"com.docker.network.bridge.enable_ip_masquerade\":\n\t\t\t\tiPMasq, err = strconv.ParseBool(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\tcase \"icc\", \"com.docker.network.bridge.enable_icc\":\n\t\t\t\ticc, err = strconv.ParseBool(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unsupported %q network option %q\", driver, opt)\n\t\t\t}\n\t\t}\n\t\tvar bridge *bridgeConfig\n\t\tif name == DefaultNetworkName {\n\t\t\tbridge = newBridgePlugin(\"nerdctl0\")\n\t\t} else {\n\t\t\tbridge = newBridgePlugin(\"br-\" + networkID(name)[:12])\n\t\t}\n\t\tbridge.MTU = mtu\n\t\tbridge.IPAM = ipam\n\t\tbridge.IsGW = !internal\n\t\tif internal {\n\t\t\tbridge.IPMasq = false\n\t\t} else {\n\t\t\tbridge.IPMasq = iPMasq\n\t\t}\n\t\tbridge.HairpinMode = true\n\t\tif ipv6 {\n\t\t\tbridge.Capabilities[\"ips\"] = true\n\t\t}\n\n\t\t// Determine the appropriate firewall ingress policy based on icc setting\n\t\tingressPolicy := \"same-bridge\" // Default policy\n\t\tfirewallPath := filepath.Join(e.Path, \"firewall\")\n\t\tif !icc {\n\t\t\t// Check if firewall plugin supports the \"isolated\" policy (v1.7.1+)\n\t\t\tok, err := FirewallPluginGEQVersion(firewallPath, \"v1.7.1\")\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Warnf(\"Failed to detect whether %q is newer than v1.7.1\", firewallPath)\n\t\t\t} else if ok {\n\t\t\t\tingressPolicy = \"isolated\"\n\t\t\t} else {\n\t\t\t\tlog.L.Warnf(\"To use 'isolated' ingress policy, CNI plugin \\\"firewall\\\" (>= 1.7.1) needs to be installed in CNI_PATH (%q), see https://www.cni.dev/plugins/current/meta/firewall/\", e.Path)\n\t\t\t}\n\t\t}\n\n\t\tif internal {\n\t\t\tplugins = []CNIPlugin{bridge, newFirewallPlugin(ingressPolicy), newTuningPlugin()}\n\t\t} else {\n\t\t\tplugins = []CNIPlugin{bridge, newPortMapPlugin(), newFirewallPlugin(ingressPolicy), newTuningPlugin()}\n\t\t}\n\t\tif name != DefaultNetworkName {\n\t\t\tok, err := FirewallPluginGEQVersion(firewallPath, \"v1.1.0\")\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Warnf(\"Failed to detect whether %q is newer than v1.1.0\", firewallPath)\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\tlog.L.Warnf(\"To isolate bridge networks, CNI plugin \\\"firewall\\\" (>= 1.1.0) needs to be installed in CNI_PATH (%q), see https://github.com/containernetworking/plugins\",\n\t\t\t\t\te.Path)\n\t\t\t}\n\t\t}\n\tcase \"macvlan\", \"ipvlan\":\n\t\tmtu := 0\n\t\tmode := \"\"\n\t\tmaster := \"\"\n\t\tfor opt, v := range opts {\n\t\t\tswitch opt {\n\t\t\tcase \"mtu\", \"com.docker.network.driver.mtu\":\n\t\t\t\tmtu, err = parseMTU(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\tcase \"mode\", \"macvlan_mode\", \"ipvlan_mode\":\n\t\t\t\tif driver == \"macvlan\" && opt != \"ipvlan_mode\" {\n\t\t\t\t\tif !strutil.InStringSlice([]string{\"bridge\"}, v) {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"unknown macvlan mode %q\", v)\n\t\t\t\t\t}\n\t\t\t\t} else if driver == \"ipvlan\" && opt != \"macvlan_mode\" {\n\t\t\t\t\tif !strutil.InStringSlice([]string{\"l2\", \"l3\"}, v) {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"unknown ipvlan mode %q\", v)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unsupported %q network option %q\", driver, opt)\n\t\t\t\t}\n\t\t\t\tmode = v\n\t\t\tcase \"parent\":\n\t\t\t\tmaster = v\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unsupported %q network option %q\", driver, opt)\n\t\t\t}\n\t\t}\n\t\tvlan := newVLANPlugin(driver)\n\t\tvlan.MTU = mtu\n\t\tvlan.Master = master\n\t\tvlan.Mode = mode\n\t\tvlan.IPAM = ipam\n\t\tif ipv6 {\n\t\t\tvlan.Capabilities[\"ips\"] = true\n\t\t}\n\t\tplugins = []CNIPlugin{vlan}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported cni driver %q\", driver)\n\t}\n\treturn plugins, nil\n}\n\nfunc (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) {\n\tvar ipamConfig interface{}\n\tswitch driver {\n\tcase \"default\", \"host-local\":\n\t\tipamConf := newHostLocalIPAMConfig()\n\t\tif !internal {\n\t\t\tipamConf.Routes = []IPAMRoute{\n\t\t\t\t{Dst: \"0.0.0.0/0\"},\n\t\t\t}\n\t\t}\n\t\tranges, findIPv4, err := e.parseIPAMRanges(subnets, gatewayStr, ipRangeStr, ipv6)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tipamConf.Ranges = append(ipamConf.Ranges, ranges...)\n\t\tif !findIPv4 {\n\t\t\tranges, _, _ = e.parseIPAMRanges([]string{\"\"}, gatewayStr, ipRangeStr, ipv6)\n\t\t\tipamConf.Ranges = append(ipamConf.Ranges, ranges...)\n\t\t}\n\t\tipamConfig = ipamConf\n\tcase \"dhcp\":\n\t\tipamConf := newDHCPIPAMConfig()\n\t\tcrd, err := defaults.CNIRuntimeDir()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tipamConf.DaemonSocketPath = filepath.Join(crd, \"dhcp.sock\")\n\t\tif err := systemutil.IsSocketAccessible(ipamConf.DaemonSocketPath); err != nil {\n\t\t\tlog.L.Warnf(\"cannot access dhcp socket %q (hint: try running with `dhcp daemon --socketpath=%s &` in CNI_PATH to launch the dhcp daemon)\", ipamConf.DaemonSocketPath, ipamConf.DaemonSocketPath)\n\t\t}\n\n\t\t// Set the host-name option to the value of passed argument NERDCTL_CNI_DHCP_HOSTNAME\n\t\topts[\"host-name\"] = `{\"type\": \"provide\", \"fromArg\": \"NERDCTL_CNI_DHCP_HOSTNAME\"}`\n\n\t\t// Convert all user-defined ipam-options into serializable options\n\t\tfor optName, optValue := range opts {\n\t\t\tparsed := &struct {\n\t\t\t\tType            string `json:\"type\"`\n\t\t\t\tValue           string `json:\"value\"`\n\t\t\t\tValueFromCNIArg string `json:\"fromArg\"`\n\t\t\t\tSkipDefault     bool   `json:\"skipDefault\"`\n\t\t\t}{}\n\t\t\tif err := json.Unmarshal([]byte(optValue), parsed); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"unparsable ipam option %s %q\", optName, optValue)\n\t\t\t}\n\t\t\tif parsed.Type == \"provide\" {\n\t\t\t\tipamConf.ProvideOptions = append(ipamConf.ProvideOptions, provideOption{\n\t\t\t\t\tOption:          optName,\n\t\t\t\t\tValue:           parsed.Value,\n\t\t\t\t\tValueFromCNIArg: parsed.ValueFromCNIArg,\n\t\t\t\t})\n\t\t\t} else if parsed.Type == \"request\" {\n\t\t\t\tipamConf.RequestOptions = append(ipamConf.RequestOptions, requestOption{\n\t\t\t\t\tOption:      optName,\n\t\t\t\t\tSkipDefault: parsed.SkipDefault,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\treturn nil, fmt.Errorf(\"ipam option must have a type (provide or request)\")\n\t\t\t}\n\t\t}\n\n\t\tipamConfig = ipamConf\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported ipam driver %q\", driver)\n\t}\n\n\tipam, err := structToMap(ipamConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ipam, nil\n}\n\nfunc (e *CNIEnv) parseIPAMRanges(subnets []string, gateway, ipRange string, ipv6 bool) ([][]IPAMRange, bool, error) {\n\tfindIPv4 := false\n\tranges := make([][]IPAMRange, 0, len(subnets))\n\tfor i := range subnets {\n\t\tsubnet, err := e.parseSubnet(subnets[i])\n\t\tif err != nil {\n\t\t\treturn nil, findIPv4, err\n\t\t}\n\t\t// if ipv6 flag is not set, subnets of ipv6 should be excluded\n\t\tif !ipv6 && subnet.IP.To4() == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif !findIPv4 && subnet.IP.To4() != nil {\n\t\t\tfindIPv4 = true\n\t\t}\n\t\tipamRange, err := parseIPAMRange(subnet, gateway, ipRange)\n\t\tif err != nil {\n\t\t\treturn nil, findIPv4, err\n\t\t}\n\t\tranges = append(ranges, []IPAMRange{*ipamRange})\n\t}\n\treturn ranges, findIPv4, nil\n}\n\n// FirewallPluginGEQVersion checks if the firewall plugin is greater than or equal to the specified version\nfunc FirewallPluginGEQVersion(firewallPath string, versionStr string) (bool, error) {\n\t// TODO: guess true by default in 2023\n\tguessed := false\n\n\t// Parse the stderr (NOT stdout) of `firewall`, such as \"CNI firewall plugin v1.1.0\\n\", or \"CNI firewall plugin version unknown\\n\"\n\t//\n\t// We do NOT set `CNI_COMMAND=VERSION` here, because the CNI \"VERSION\" command reports the version of the CNI spec,\n\t// not the version of the firewall plugin implementation.\n\t//\n\t// ```\n\t// $ /opt/cni/bin/firewall\n\t// CNI firewall plugin v1.1.0\n\t// $ CNI_COMMAND=VERSION /opt/cni/bin/firewall\n\t// {\"cniVersion\":\"1.0.0\",\"supportedVersions\":[\"0.4.0\",\"1.0.0\"]}\n\t// ```\n\t//\n\tcmd := exec.Command(firewallPath)\n\tvar stdout, stderr bytes.Buffer\n\tcmd.Stdout = &stdout\n\tcmd.Stderr = &stderr\n\tif err := cmd.Run(); err != nil {\n\t\terr = fmt.Errorf(\"failed to run %v: %w (stdout=%q, stderr=%q)\", cmd.Args, err, stdout.String(), stderr.String())\n\t\treturn guessed, err\n\t}\n\n\tver, err := guessFirewallPluginVersion(stderr.String()) // NOT stdout\n\tif err != nil {\n\t\treturn guessed, fmt.Errorf(\"failed to guess the version of %q: %w\", firewallPath, err)\n\t}\n\ttargetVer := semver.MustParse(versionStr)\n\treturn ver.GreaterThan(targetVer) || ver.Equal(targetVer), nil\n}\n\n// guessFirewallPluginVersion guess the version of the CNI firewall plugin (not the version of the implemented CNI spec).\n//\n// stderr is like \"CNI firewall plugin v1.1.0\\n\", or \"CNI firewall plugin version unknown\\n\"\nfunc guessFirewallPluginVersion(stderr string) (*semver.Version, error) {\n\tconst prefix = \"CNI firewall plugin \"\n\tlines := strings.Split(stderr, \"\\n\")\n\tfor i, l := range lines {\n\t\ttrimmed := strings.TrimPrefix(l, prefix)\n\t\tif trimmed == l { // l does not have the expected prefix\n\t\t\tcontinue\n\t\t}\n\t\t// trimmed is like \"v1.1.1\", \"v1.1.0\", ..., \"v0.8.0\", or \"version unknown\"\n\t\tver, err := semver.NewVersion(trimmed)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse %q (line %d of stderr=%q) as a semver: %w\", trimmed, i+1, stderr, err)\n\t\t}\n\t\treturn ver, nil\n\t}\n\treturn nil, fmt.Errorf(\"stderr %q does not have any line that starts with %q\", stderr, prefix)\n}\n\nfunc removeBridgeNetworkInterface(netIf string) error {\n\treturn rootlessutil.WithDetachedNetNSIfAny(func() error {\n\t\tlink, err := netlink.LinkByName(netIf)\n\t\tif err == nil {\n\t\t\tif err := netlink.LinkDel(link); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to remove network interface %s: %v\", netIf, err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "pkg/netutil/netutil_unix_test.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestGuessFirewallPluginVersion(t *testing.T) {\n\n\ttype testCase struct {\n\t\tstderr   string\n\t\texpected string\n\t\terr      string\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tstderr:   \"CNI firewall plugin v1.1.0\\n\",\n\t\t\texpected: \"1.1.0\",\n\t\t},\n\t\t{\n\t\t\tstderr:   \"CNI firewall plugin v0.8.0\\n\",\n\t\t\texpected: \"0.8.0\",\n\t\t},\n\t\t{\n\t\t\tstderr:   \"Foo\\nCNI firewall plugin v123.456.789+beta.10\\nBar\\n\",\n\t\t\texpected: \"123.456.789+beta.10\",\n\t\t},\n\t\t{\n\t\t\tstderr: \"CNI firewall plugin version unknown\\n\",\n\t\t\terr:    semver.ErrInvalidSemVer.Error(),\n\t\t},\n\t\t{\n\t\t\tstderr: \"\",\n\t\t\terr:    \"does not have any line that starts with \\\"CNI firewall plugin \\\"\",\n\t\t},\n\t\t{\n\t\t\tstderr: \"Foo\\nBar\\nBaz\\n\",\n\t\t\terr:    \"does not have any line that starts with \\\"CNI firewall plugin \\\"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tgot, err := guessFirewallPluginVersion(tc.stderr)\n\t\tif tc.err == \"\" {\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, tc.expected, got.String())\n\t\t} else {\n\t\t\tassert.ErrorContains(t, err, tc.err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/netutil/netutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/go-viper/mapstructure/v2\"\n)\n\nconst (\n\tDefaultNetworkName = \"nat\"\n\tDefaultCIDR        = \"10.4.0.0/24\"\n\n\t// When creating non-default network without passing in `--subnet` option,\n\t// nerdctl assigns subnet address for the creation starting from `StartingCIDR`\n\t// This prevents subnet address overlapping with `DefaultCIDR` used by the default network\n\tStartingCIDR = \"10.4.1.0/24\"\n)\n\nfunc (n *NetworkConfig) subnets() []*net.IPNet {\n\tvar subnets []*net.IPNet\n\tif n.Plugins[0].Network.Type == \"nat\" {\n\t\tvar nat natConfig\n\t\tif err := json.Unmarshal(n.Plugins[0].Bytes, &nat); err != nil {\n\t\t\treturn subnets\n\t\t}\n\t\tvar ipam windowsIpamConfig\n\t\tif err := mapstructure.Decode(nat.IPAM, &ipam); err != nil {\n\t\t\treturn subnets\n\t\t}\n\t\t_, subnet, err := net.ParseCIDR(ipam.Subnet)\n\t\tif err != nil {\n\t\t\treturn subnets\n\t\t}\n\t\tsubnets = append(subnets, subnet)\n\t}\n\treturn subnets\n}\n\nfunc (n *NetworkConfig) clean() error {\n\treturn nil\n}\n\nfunc (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]interface{}, opts map[string]string, ipv6 bool, internal bool) ([]CNIPlugin, error) {\n\tvar plugins []CNIPlugin\n\tswitch driver {\n\tcase \"nat\":\n\t\tnat := newNatPlugin(\"Ethernet\")\n\t\tnat.IPAM = ipam\n\t\tplugins = []CNIPlugin{nat}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported cni driver %q\", driver)\n\t}\n\treturn plugins, nil\n}\n\nfunc (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) {\n\tswitch driver {\n\tcase \"default\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported ipam driver %q\", driver)\n\t}\n\n\tipamConfig := newWindowsIPAMConfig()\n\tsubnet, err := e.parseSubnet(subnets[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tipamRange, err := parseIPAMRange(subnet, gatewayStr, ipRangeStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tipamConfig.Subnet = ipamRange.Subnet\n\tipamConfig.Routes = append(ipamConfig.Routes, IPAMRoute{Gateway: ipamRange.Gateway})\n\tipam, err := structToMap(ipamConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ipam, nil\n}\n\nfunc FirewallPluginGEQVersion(firewallPath string, versionStr string) (bool, error) {\n\treturn false, errors.New(\"unsupported in windows\")\n}\n"
  },
  {
    "path": "pkg/netutil/netutil_windows_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport \"testing\"\n\n// Tests whether nerdctl properly creates the default network when required.\n// On Windows, the default driver used will be \"nat\". (netutil.DefaultNetworkName)\nfunc TestDefaultNetworkCreation(t *testing.T) {\n\ttestDefaultNetworkCreation(t)\n}\n"
  },
  {
    "path": "pkg/netutil/networkstore/networkstore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage networkstore\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nconst (\n\tcontainersDirBaseName = \"containers\"\n\tnetworkConfigName     = \"network-config.json\"\n)\n\nvar ErrNetworkStore = errors.New(\"network-store error\")\n\nfunc New(dataStore, namespace, containerID string) (ns *NetworkStore, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrNetworkStore, err)\n\t\t}\n\t}()\n\n\tif dataStore == \"\" || namespace == \"\" || containerID == \"\" {\n\t\treturn nil, fmt.Errorf(\"either dataStore or namespace or containerID is empty\")\n\t}\n\n\tst, err := store.New(filepath.Join(dataStore, containersDirBaseName, namespace, containerID), 0, 0o600)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &NetworkStore{\n\t\tsafeStore: st,\n\t}, nil\n}\n\ntype NetworkConfig struct {\n\tPortMappings []cni.PortMapping `json:\"portMappings,omitempty\"`\n}\n\ntype NetworkStore struct {\n\tsafeStore store.Store\n\n\tNetConf NetworkConfig\n}\n\nfunc (ns *NetworkStore) Acquire(netConf NetworkConfig) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrNetworkStore, err)\n\t\t}\n\t}()\n\n\tnetConfJSON, err := json.Marshal(netConf)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal network config to JSON: %w\", err)\n\t}\n\n\treturn ns.safeStore.WithLock(func() error {\n\t\treturn ns.safeStore.Set(netConfJSON, networkConfigName)\n\t})\n}\n\nfunc (ns *NetworkStore) Load() (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrNetworkStore, err)\n\t\t}\n\t}()\n\n\treturn ns.safeStore.WithLock(func() error {\n\t\tdoesExist, err := ns.safeStore.Exists(networkConfigName)\n\t\tif err != nil || !doesExist {\n\t\t\treturn err\n\t\t}\n\n\t\tdata, err := ns.safeStore.Get(networkConfigName)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, store.ErrNotFound) {\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tvar netConf NetworkConfig\n\t\tif err := json.Unmarshal(data, &netConf); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse network config %v: %w\", netConf, err)\n\t\t}\n\t\tns.NetConf = netConf\n\n\t\treturn err\n\t})\n}\n"
  },
  {
    "path": "pkg/netutil/store.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage netutil\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/containernetworking/cni/libcni\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\n// NOTE: libcni is not safe to use concurrently - or at least delegates concurrency management to the consumer.\n// Furthermore, CNIEnv (prior to this) is assuming the filesystem is ACID and other TOCTOU faults.\n// This small set of methods here are meant to isolate CNIEnv entirely from the filesystem.\n// This is NOT proper - we should instead use the Store implementation, which is the generic abstraction for ACID\n// operations - but for now that will do, waiting for a full rewrite of CNIEnv.\n\nfunc fsEnsureRoot(e *CNIEnv, namespace string) error {\n\tpath := e.NetconfPath\n\tif namespace != \"\" {\n\t\tpath = filepath.Join(e.NetconfPath, namespace)\n\t}\n\treturn os.MkdirAll(path, 0755)\n}\n\nfunc fsRemove(e *CNIEnv, net *NetworkConfig) error {\n\tfn := func() error {\n\t\tif err := os.RemoveAll(net.File); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn net.clean()\n\t}\n\treturn filesystem.WithLock(filepath.Join(e.NetconfPath, \".nerdctl.lock\"), fn)\n}\n\nfunc fsExists(e *CNIEnv, name string) (bool, error) {\n\tfi, err := os.Stat(getConfigPathForNetworkName(e, name))\n\treturn !os.IsNotExist(err) && !fi.IsDir(), err\n}\n\nfunc fsWrite(e *CNIEnv, net *NetworkConfig) error {\n\tfilename := getConfigPathForNetworkName(e, net.Name)\n\t// FIXME: note that this is still problematic.\n\t// Concurrent access may independently first figure out that a given network is missing, and while the lock\n\t// here will prevent concurrent writes, one of the routines will fail.\n\t// Consuming code MUST account for that scenario.\n\treturn filesystem.WithLock(filepath.Join(e.NetconfPath, \".nerdctl.lock\"), func() error {\n\t\tif _, err := os.Stat(filename); err == nil {\n\t\t\treturn errdefs.ErrAlreadyExists\n\t\t}\n\t\treturn filesystem.WriteFile(filename, net.Bytes, 0644)\n\t})\n}\n\nfunc fsRead(e *CNIEnv) ([]*NetworkConfig, error) {\n\tvar nc []*NetworkConfig\n\tvar err error\n\terr = filesystem.WithReadOnlyLock(filepath.Join(e.NetconfPath, \".nerdctl.lock\"), func() error {\n\t\tnamespaced := []string{}\n\t\tvar common []string\n\t\tcommon, err = libcni.ConfFiles(e.NetconfPath, []string{\".conf\", \".conflist\", \".json\"})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif e.Namespace != \"\" {\n\t\t\tnamespaced, err = libcni.ConfFiles(filepath.Join(e.NetconfPath, e.Namespace), []string{\".conf\", \".conflist\", \".json\"})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tnc, err = cniLoad(append(common, namespaced...))\n\t\treturn err\n\t})\n\treturn nc, err\n}\n\nfunc getConfigPathForNetworkName(e *CNIEnv, netName string) string {\n\tif netName == DefaultNetworkName || e.Namespace == \"\" {\n\t\treturn filepath.Join(e.NetconfPath, \"nerdctl-\"+netName+\".conflist\")\n\t}\n\treturn filepath.Join(e.NetconfPath, e.Namespace, \"nerdctl-\"+netName+\".conflist\")\n}\n"
  },
  {
    "path": "pkg/netutil/subnet/subnet.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage subnet\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc GetLiveNetworkSubnets() ([]*net.IPNet, error) {\n\tvar addrs []net.Addr\n\tif err := rootlessutil.WithDetachedNetNSIfAny(func() error {\n\t\tvar err2 error\n\t\taddrs, err2 = net.InterfaceAddrs()\n\t\treturn err2\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\tnets := make([]*net.IPNet, 0, len(addrs))\n\tfor _, address := range addrs {\n\t\t_, n, err := net.ParseCIDR(address.String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnets = append(nets, n)\n\t}\n\treturn nets, nil\n}\n\n// GetFreeSubnet try to find a free subnet in the given network\nfunc GetFreeSubnet(n *net.IPNet, usedNetworks []*net.IPNet) (*net.IPNet, error) {\n\tfor {\n\t\tif !IntersectsWithNetworks(n, usedNetworks) {\n\t\t\treturn n, nil\n\t\t}\n\t\tnext, err := nextSubnet(n)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tn = next\n\t}\n\treturn nil, fmt.Errorf(\"could not find free subnet\")\n}\n\nfunc nextSubnet(subnet *net.IPNet) (*net.IPNet, error) {\n\tnewSubnet := &net.IPNet{\n\t\tIP:   subnet.IP,\n\t\tMask: subnet.Mask,\n\t}\n\tones, bits := newSubnet.Mask.Size()\n\tif ones == 0 {\n\t\treturn nil, fmt.Errorf(\"%s has only one subnet\", subnet.String())\n\t}\n\tzeroes := uint(bits - ones)\n\tshift := zeroes % 8\n\tidx := (ones - 1) / 8\n\tif err := incByte(newSubnet, idx, shift); err != nil {\n\t\treturn nil, err\n\t}\n\treturn newSubnet, nil\n}\n\nfunc incByte(subnet *net.IPNet, idx int, shift uint) error {\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"no more subnets left\")\n\t}\n\n\tvar val byte = 1 << shift\n\t// if overflow we have to inc the previous byte\n\tif uint(subnet.IP[idx])+uint(val) > 255 {\n\t\tif err := incByte(subnet, idx-1, 0); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tsubnet.IP[idx] += val\n\treturn nil\n}\n\nfunc IntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) bool {\n\tfor _, nw := range networklist {\n\t\tif n.Contains(nw.IP) || nw.Contains(n.IP) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// lastIPInSubnet gets the last IP in a subnet\n// https://github.com/containers/podman/blob/v4.0.0-rc1/libpod/network/util/ip.go#L18\nfunc LastIPInSubnet(addr *net.IPNet) (net.IP, error) {\n\t// re-parse to ensure clean network address\n\t_, cidr, err := net.ParseCIDR(addr.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tones, bits := cidr.Mask.Size()\n\tif ones == bits {\n\t\treturn cidr.IP, nil\n\t}\n\tfor i := range cidr.IP {\n\t\tcidr.IP[i] = cidr.IP[i] | ^cidr.Mask[i]\n\t}\n\treturn cidr.IP, nil\n}\n\n// firstIPInSubnet gets the first IP in a subnet\n// https://github.com/containers/podman/blob/v4.0.0-rc1/libpod/network/util/ip.go#L36\nfunc FirstIPInSubnet(addr *net.IPNet) (net.IP, error) {\n\t// re-parse to ensure clean network address\n\t_, cidr, err := net.ParseCIDR(addr.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tones, bits := cidr.Mask.Size()\n\tif ones == bits {\n\t\treturn cidr.IP, nil\n\t}\n\tcidr.IP[len(cidr.IP)-1]++\n\treturn cidr.IP, nil\n}\n"
  },
  {
    "path": "pkg/netutil/subnet/subnet_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage subnet\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestNextSubnet(t *testing.T) {\n\ttestCases := []struct {\n\t\tsubnet string\n\t\texpect string\n\t}{\n\t\t{\n\t\t\tsubnet: \"10.4.1.0/24\",\n\t\t\texpect: \"10.4.2.0/24\",\n\t\t},\n\t\t{\n\t\t\tsubnet: \"10.4.255.0/24\",\n\t\t\texpect: \"10.5.0.0/24\",\n\t\t},\n\t\t{\n\t\t\tsubnet: \"10.4.255.0/16\",\n\t\t\texpect: \"10.5.0.0/16\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\t_, net, _ := net.ParseCIDR(tc.subnet)\n\t\tnextSubnet, err := nextSubnet(net)\n\t\tassert.NilError(t, err)\n\t\tassert.Equal(t, nextSubnet.String(), tc.expect)\n\t}\n}\n"
  },
  {
    "path": "pkg/ocihook/ocihook.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ocihook\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\ttypes100 \"github.com/containernetworking/cni/pkg/types/100\"\n\t\"github.com/opencontainers/runtime-spec/specs-go\"\n\tb4nndclient \"github.com/rootless-containers/bypass4netns/pkg/api/daemon/client\"\n\trlkclient \"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n\n\t\"github.com/containerd/go-cni\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/bypass4netnsutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/namestore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/nettype\"\n\t\"github.com/containerd/nerdctl/v2/pkg/ocihook/state\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\nconst (\n\t// NetworkNamespace is the network namespace path to be passed to the CNI plugins.\n\t// When this annotation is set from the runtime spec.State payload, it takes\n\t// precedence over the PID based resolution (/proc/<pid>/ns/net) where pid is\n\t// spec.State.Pid.\n\t// This is mostly used for VM based runtime, where the spec.State PID does not\n\t// necessarily lives in the created container networking namespace.\n\t//\n\t// On Windows, this label will contain the UUID of a namespace managed by\n\t// the Host Compute Network Service (HCN) API.\n\tNetworkNamespace = labels.Prefix + \"network-namespace\"\n)\n\nfunc Run(stdin io.Reader, stderr io.Writer, event, dataStore, cniPath, cniNetconfPath, bridgeIP string) error {\n\tif stdin == nil || event == \"\" || dataStore == \"\" || cniPath == \"\" || cniNetconfPath == \"\" {\n\t\treturn errors.New(\"got insufficient args\")\n\t}\n\n\tvar state specs.State\n\tif err := json.NewDecoder(stdin).Decode(&state); err != nil {\n\t\treturn err\n\t}\n\n\tcontainerStateDir := state.Annotations[labels.StateDir]\n\tif containerStateDir == \"\" {\n\t\treturn errors.New(\"state dir must be set\")\n\t}\n\tif err := os.MkdirAll(containerStateDir, 0700); err != nil {\n\t\treturn fmt.Errorf(\"failed to create %q: %w\", containerStateDir, err)\n\t}\n\tlogFilePath := filepath.Join(containerStateDir, \"oci-hook.\"+event+\".log\")\n\tlogFile, err := os.Create(logFilePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcurrentOutput := log.L.Logger.Out\n\tlog.L.Logger.SetOutput(io.MultiWriter(stderr, logFile))\n\tdefer func() {\n\t\tlog.L.Logger.SetOutput(currentOutput)\n\t\terr = logFile.Close()\n\t\tif err != nil {\n\t\t\tlog.L.Logger.WithError(err).Error(\"failed closing oci hook log file\")\n\t\t}\n\t}()\n\n\t// FIXME: CNI plugins are not safe to use concurrently\n\t// See\n\t// https://github.com/containerd/nerdctl/issues/3518\n\t// https://github.com/containerd/nerdctl/issues/2908\n\t// and likely others\n\t// Fixing these issues would require a lot of work, possibly even stopping using individual cni binaries altogether\n\t// or at least being very mindful in what operation we call inside CNIEnv at what point, with filesystem locking.\n\t// This below is a stopgap solution that just enforces a global lock\n\t// Note this here is probably not enough, as concurrent CNI operations may happen outside of the scope of ocihooks\n\t// through explicit calls to Remove, etc.\n\t// Finally note that this is not the same (albeit similar) as libcni filesystem manipulation locking,\n\t// hence the independent lock\n\terr = os.MkdirAll(cniNetconfPath, 0o700)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlock, err := filesystem.Lock(filepath.Join(cniNetconfPath, \".cni-concurrency.lock\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer filesystem.Unlock(lock)\n\n\topts, err := newHandlerOpts(&state, dataStore, cniPath, cniNetconfPath, bridgeIP)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch event {\n\tcase \"createRuntime\":\n\t\treturn onCreateRuntime(opts)\n\tcase \"postStop\":\n\t\treturn onPostStop(opts)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected event %q\", event)\n\t}\n}\n\nfunc newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath, bridgeIP string) (*handlerOpts, error) {\n\to := &handlerOpts{\n\t\tstate:     state,\n\t\tdataStore: dataStore,\n\t}\n\n\textraHosts, err := getExtraHosts(state)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\to.extraHosts = extraHosts\n\n\ths, err := loadSpec(o.state.Bundle)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\to.rootfs = hs.Root.Path\n\tif !filepath.IsAbs(o.rootfs) {\n\t\to.rootfs = filepath.Join(o.state.Bundle, o.rootfs)\n\t}\n\n\tnamespace := o.state.Annotations[labels.Namespace]\n\tif namespace == \"\" {\n\t\treturn nil, errors.New(\"namespace must be set\")\n\t}\n\tif o.state.ID == \"\" {\n\t\treturn nil, errors.New(\"state.ID must be set\")\n\t}\n\to.fullID = namespace + \"-\" + o.state.ID\n\n\tnetworksJSON := o.state.Annotations[labels.Networks]\n\tvar networks []string\n\tif err := json.Unmarshal([]byte(networksJSON), &networks); err != nil {\n\t\treturn nil, err\n\t}\n\n\tnetType, err := nettype.Detect(networks)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch netType {\n\tcase nettype.Host, nettype.None, nettype.Container, nettype.Namespace:\n\t\t// NOP\n\tcase nettype.CNI:\n\t\te, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork(bridgeIP))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcniOpts := []cni.Opt{\n\t\t\tcni.WithPluginDir([]string{cniPath}),\n\t\t}\n\t\tvar netw *netutil.NetworkConfig\n\t\tfor _, netstr := range networks {\n\t\t\tif netw, err = e.NetworkByNameOrID(netstr); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcniOpts = append(cniOpts, cni.WithConfListBytes(netw.Bytes))\n\t\t\to.cniNames = append(o.cniNames, netstr)\n\t\t}\n\t\to.cni, err = cni.New(cniOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif o.cni == nil {\n\t\t\tlog.L.Warnf(\"no CNI network could be loaded from the provided network names: %v\", networks)\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected network type %v\", netType)\n\t}\n\n\tif pidFile := o.state.Annotations[labels.PIDFile]; pidFile != \"\" {\n\t\tif err := writePidFile(pidFile, state.Pid); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tports, err := portutil.LoadPortMappings(o.dataStore, namespace, o.state.ID, o.state.Annotations)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\to.ports = ports\n\n\tif ipAddress, ok := o.state.Annotations[labels.IPAddress]; ok {\n\t\to.containerIP = ipAddress\n\t}\n\n\tif macAddress, ok := o.state.Annotations[labels.MACAddress]; ok {\n\t\to.containerMAC = macAddress\n\t}\n\n\tif ip6Address, ok := o.state.Annotations[labels.IP6Address]; ok {\n\t\to.containerIP6 = ip6Address\n\t}\n\n\tif rootlessutil.IsRootlessChild() {\n\t\to.rootlessKitClient, err = rootlessutil.NewRootlessKitClient()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb4nnEnabled, _, err := bypass4netnsutil.IsBypass4netnsEnabled(o.state.Annotations)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif b4nnEnabled {\n\t\t\tsocketPath, err := bypass4netnsutil.GetBypass4NetnsdDefaultSocketPath()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\to.bypassClient, err = b4nndclient.New(socketPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"bypass4netnsd not running? (Hint: run `containerd-rootless-setuptool.sh install-bypass4netnsd`): %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\treturn o, nil\n}\n\ntype handlerOpts struct {\n\tstate             *specs.State\n\tdataStore         string\n\trootfs            string\n\tports             []cni.PortMapping\n\tcni               cni.CNI\n\tcniNames          []string\n\tfullID            string\n\trootlessKitClient rlkclient.Client\n\tbypassClient      b4nndclient.Client\n\textraHosts        map[string]string // host:ip\n\tcontainerIP       string\n\tcontainerMAC      string\n\tcontainerIP6      string\n}\n\n// hookSpec is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/containerd/command/oci-hook.go#L59-L64\ntype hookSpec struct {\n\tRoot struct {\n\t\tPath string `json:\"path\"`\n\t} `json:\"root\"`\n}\n\n// loadSpec is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/containerd/command/oci-hook.go#L65-L76\nfunc loadSpec(bundle string) (*hookSpec, error) {\n\tf, err := os.Open(filepath.Join(bundle, \"config.json\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\tvar s hookSpec\n\tif err := json.NewDecoder(f).Decode(&s); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &s, nil\n}\n\nfunc getExtraHosts(state *specs.State) (map[string]string, error) {\n\textraHostsJSON := state.Annotations[labels.ExtraHosts]\n\tvar extraHosts []string\n\tif err := json.Unmarshal([]byte(extraHostsJSON), &extraHosts); err != nil {\n\t\treturn nil, err\n\t}\n\n\thosts := make(map[string]string)\n\tfor _, host := range extraHosts {\n\t\tif v := strings.SplitN(host, \":\", 2); len(v) == 2 {\n\t\t\thosts[v[0]] = v[1]\n\t\t}\n\t}\n\treturn hosts, nil\n}\n\nfunc getNetNSPath(state *specs.State) (string, error) {\n\t// If we have a network-namespace annotation we use it over the passed Pid.\n\tnetNsPath, netNsFound := state.Annotations[NetworkNamespace]\n\tif netNsFound {\n\t\tif _, err := os.Stat(netNsPath); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn netNsPath, nil\n\t}\n\n\tif state.Pid == 0 {\n\t\treturn \"\", errors.New(\"both state.Pid and the netNs annotation are unset\")\n\t}\n\n\t// We dont't have a networking namespace annotation, but we have a PID.\n\ts := fmt.Sprintf(\"/proc/%d/ns/net\", state.Pid)\n\tif _, err := os.Stat(s); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn s, nil\n}\n\nfunc getPortMapOpts(opts *handlerOpts) ([]cni.NamespaceOpts, error) {\n\tif len(opts.ports) > 0 {\n\t\tif !rootlessutil.IsRootlessChild() {\n\t\t\treturn []cni.NamespaceOpts{cni.WithCapabilityPortMap(opts.ports)}, nil\n\t\t}\n\t\tvar (\n\t\t\tchildIP                            net.IP\n\t\t\tportDriverDisallowsLoopbackChildIP bool\n\t\t)\n\t\tinfo, err := opts.rootlessKitClient.Info(context.TODO())\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warn(\"cannot call RootlessKit Info API, make sure you have RootlessKit v0.14.1 or later\")\n\t\t} else {\n\t\t\tchildIP = info.NetworkDriver.ChildIP\n\t\t\tportDriverDisallowsLoopbackChildIP = info.PortDriver.DisallowLoopbackChildIP // true for slirp4netns port driver\n\t\t}\n\t\t// For rootless, we need to modify the hostIP that is not bindable in the child namespace.\n\t\t// https: //github.com/containerd/nerdctl/issues/88\n\t\t//\n\t\t// We must NOT modify opts.ports here, because we use the unmodified opts.ports for\n\t\t// interaction with RootlessKit API.\n\t\tports := make([]cni.PortMapping, len(opts.ports))\n\t\tfor i, p := range opts.ports {\n\t\t\tif hostIP := net.ParseIP(p.HostIP); hostIP != nil && !hostIP.IsUnspecified() {\n\t\t\t\t// loopback address is always bindable in the child namespace, but other addresses are unlikely.\n\t\t\t\tif !hostIP.IsLoopback() {\n\t\t\t\t\tif !(childIP != nil && childIP.Equal(hostIP)) {\n\t\t\t\t\t\tif portDriverDisallowsLoopbackChildIP {\n\t\t\t\t\t\t\tp.HostIP = childIP.String()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tp.HostIP = \"127.0.0.1\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if portDriverDisallowsLoopbackChildIP {\n\t\t\t\t\tp.HostIP = childIP.String()\n\t\t\t\t}\n\t\t\t}\n\t\t\tports[i] = p\n\t\t}\n\t\treturn []cni.NamespaceOpts{cni.WithCapabilityPortMap(ports)}, nil\n\t}\n\treturn nil, nil\n}\n\nfunc getIPAddressOpts(opts *handlerOpts) ([]cni.NamespaceOpts, error) {\n\tif opts.containerIP != \"\" {\n\t\tif rootlessutil.IsRootlessChild() {\n\t\t\tlog.L.Debug(\"container IP assignment is not fully supported in rootless mode. The IP is not accessible from the host (but still accessible from other containers).\")\n\t\t}\n\n\t\treturn []cni.NamespaceOpts{\n\t\t\tcni.WithLabels(map[string]string{\n\t\t\t\t// Special tick for go-cni. Because go-cni marks all labels and args as same\n\t\t\t\t// So, we need add a special label to pass the containerIP to the host-local plugin.\n\t\t\t\t// FYI: https://github.com/containerd/go-cni/blob/v1.1.3/README.md?plain=1#L57-L64\n\t\t\t\t\"IgnoreUnknown\": \"1\",\n\t\t\t}),\n\t\t\tcni.WithArgs(\"IP\", opts.containerIP),\n\t\t}, nil\n\t}\n\treturn nil, nil\n}\n\nfunc getMACAddressOpts(opts *handlerOpts) ([]cni.NamespaceOpts, error) {\n\tif opts.containerMAC != \"\" {\n\t\treturn []cni.NamespaceOpts{\n\t\t\tcni.WithLabels(map[string]string{\n\t\t\t\t// allow loose CNI argument verification\n\t\t\t\t// FYI: https://github.com/containernetworking/cni/issues/560\n\t\t\t\t\"IgnoreUnknown\": \"1\",\n\t\t\t}),\n\t\t\tcni.WithArgs(\"MAC\", opts.containerMAC),\n\t\t}, nil\n\t}\n\treturn nil, nil\n}\n\nfunc getIP6AddressOpts(opts *handlerOpts) ([]cni.NamespaceOpts, error) {\n\tif opts.containerIP6 != \"\" {\n\t\tif rootlessutil.IsRootlessChild() {\n\t\t\tlog.L.Debug(\"container IP6 assignment is not fully supported in rootless mode. The IP6 is not accessible from the host (but still accessible from other containers).\")\n\t\t}\n\t\treturn []cni.NamespaceOpts{\n\t\t\tcni.WithLabels(map[string]string{\n\t\t\t\t// allow loose CNI argument verification\n\t\t\t\t// FYI: https://github.com/containernetworking/cni/issues/560\n\t\t\t\t\"IgnoreUnknown\": \"1\",\n\t\t\t}),\n\t\t\tcni.WithCapability(\"ips\", []string{opts.containerIP6}),\n\t\t}, nil\n\t}\n\treturn nil, nil\n}\n\nfunc reserveSocket(protocol, hostAddr string) (*os.File, error) {\n\ttype filer interface {\n\t\tFile() (*os.File, error)\n\t}\n\tvar f filer\n\tswitch {\n\tcase strings.HasPrefix(protocol, \"tcp\"):\n\t\tl, err := net.Listen(protocol, hostAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer l.Close()\n\t\tvar ok bool\n\t\tf, ok = l.(filer)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"cannot get file descriptor from the listener of type %T\", l)\n\t\t}\n\tcase strings.HasPrefix(protocol, \"udp\"):\n\t\tl, err := net.ListenPacket(protocol, hostAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer l.Close()\n\t\tvar ok bool\n\t\tf, ok = l.(filer)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"cannot get file descriptor from the listener of type %T\", l)\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported protocol %q\", protocol)\n\t}\n\treturn f.File()\n}\n\n// portReserverPidFilePath returns /run/nerdctl/<namespace>/<id>/port-reserver.pid\nfunc portReserverPidFilePath(opts *handlerOpts) string {\n\treturn filepath.Join(\"/run/nerdctl/\", opts.state.Annotations[labels.Namespace], opts.state.ID, \"port-reserver.pid\")\n}\n\nfunc applyNetworkSettings(opts *handlerOpts) (err error) {\n\tportMapOpts, err := getPortMapOpts(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !rootlessutil.IsRootlessChild() && len(opts.ports) > 0 {\n\t\t// When running in rootful mode, reserve the ports on the host\n\t\t// so that the ports appears on /proc/net/tcp.\n\t\t//\n\t\t// This also prevents other processes from binding to the same ports.\n\t\t//\n\t\t// Note that in rootless mode this is not necessary because\n\t\t// RootlessKit's port driver already reserves the ports.\n\t\t//\n\t\t// See https://github.com/lima-vm/lima/issues/4085\n\t\t//\n\t\t// Similar patterns are used in Docker and Podman.\n\t\t// - https://github.com/moby/moby/pull/48132\n\t\t// - https://github.com/containers/podman/pull/23446\n\t\treserverCmd := exec.Command(\"sleep\", \"infinity\")\n\t\tfor _, p := range opts.ports {\n\t\t\tprotocol := p.Protocol\n\t\t\tif !strings.HasSuffix(protocol, \"4\") && !strings.HasSuffix(protocol, \"6\") {\n\t\t\t\t// e.g. \"tcp\" -> \"tcp4\"\n\t\t\t\tprotocol += \"4\"\n\t\t\t}\n\t\t\thostAddr := net.JoinHostPort(p.HostIP, strconv.Itoa(int(p.HostPort)))\n\t\t\tf, err := reserveSocket(protocol, hostAddr)\n\t\t\tif err != nil {\n\t\t\t\tlog.L.WithError(err).Warnf(\"cannot reserve the port %s/%s\", hostAddr, protocol)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treserverCmd.ExtraFiles = append(reserverCmd.ExtraFiles, f)\n\t\t}\n\t\tif err := reserverCmd.Start(); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot start the port reserver process: %w\", err)\n\t\t}\n\t\treserverCmdPid := reserverCmd.Process.Pid\n\t\tlog.L.Debugf(\"started the port reserver process (pid=%d)\", reserverCmdPid)\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tlog.L.Debugf(\"killing the port reserver process (pid=%d)\", reserverCmdPid)\n\t\t\t\t_ = reserverCmd.Process.Kill()\n\t\t\t}\n\t\t}()\n\t\tif err := writePidFile(portReserverPidFilePath(opts), reserverCmdPid); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot write the pid file of the port reserver process: %w\", err)\n\t\t}\n\t}\n\tnsPath, err := getNetNSPath(opts.state)\n\tif err != nil {\n\t\treturn err\n\t}\n\tctx := context.Background()\n\ths, err := hostsstore.New(opts.dataStore, opts.state.Annotations[labels.Namespace])\n\tif err != nil {\n\t\treturn err\n\t}\n\tipAddressOpts, err := getIPAddressOpts(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmacAddressOpts, err := getMACAddressOpts(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tip6AddressOpts, err := getIP6AddressOpts(opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar namespaceOpts []cni.NamespaceOpts\n\tnamespaceOpts = append(namespaceOpts, portMapOpts...)\n\tnamespaceOpts = append(namespaceOpts, ipAddressOpts...)\n\tnamespaceOpts = append(namespaceOpts, macAddressOpts...)\n\tnamespaceOpts = append(namespaceOpts, ip6AddressOpts...)\n\tnamespaceOpts = append(namespaceOpts,\n\t\tcni.WithLabels(map[string]string{\n\t\t\t\"IgnoreUnknown\": \"1\",\n\t\t}),\n\t\tcni.WithArgs(\"NERDCTL_CNI_DHCP_HOSTNAME\", opts.state.Annotations[labels.Hostname]),\n\t)\n\thsMeta := hostsstore.Meta{\n\t\tID:         opts.state.ID,\n\t\tNetworks:   make(map[string]*types100.Result, len(opts.cniNames)),\n\t\tHostname:   opts.state.Annotations[labels.Hostname],\n\t\tDomainname: opts.state.Annotations[labels.Domainname],\n\t\tExtraHosts: opts.extraHosts,\n\t\tName:       opts.state.Annotations[labels.Name],\n\t}\n\n\t// When containerd gets bounced, containers that were previously running and that are restarted will go again\n\t// through onCreateRuntime (*unlike* in a normal stop/start flow).\n\t// As such, a container may very well have an ip already. The bridge plugin would thus refuse to loan a new one\n\t// and error out, thus making the onCreateRuntime hook fail. In turn, runc (or containerd) will mis-interpret this,\n\t// and subsequently call onPostStop (although the container will not get deleted), and we will release the name...\n\t// leading to a bricked system where multiple containers may share the same name.\n\t// Thus, we do pre-emptively clean things up - error is not checked, as in the majority of cases, that would\n\t// legitimately error (and that does not matter)\n\t// See https://github.com/containerd/nerdctl/issues/3355\n\t_ = opts.cni.Remove(ctx, opts.fullID, \"\", namespaceOpts...)\n\n\t// Defer CNI configuration removal to ensure idempotency of oci-hook.\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tlog.L.Warn(\"Container failed starting. Removing allocated network configuration.\")\n\t\t\t_ = opts.cni.Remove(ctx, opts.fullID, nsPath, namespaceOpts...)\n\t\t}\n\t}()\n\n\tcniRes, err := opts.cni.Setup(ctx, opts.fullID, nsPath, namespaceOpts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to call cni.Setup: %w\", err)\n\t}\n\n\tcniResRaw := cniRes.Raw()\n\tfor i, cniName := range opts.cniNames {\n\t\thsMeta.Networks[cniName] = cniResRaw[i]\n\t}\n\n\tb4nnEnabled, b4nnBindEnabled, err := bypass4netnsutil.IsBypass4netnsEnabled(opts.state.Annotations)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := hs.Acquire(hsMeta); err != nil {\n\t\treturn err\n\t}\n\n\tif rootlessutil.IsRootlessChild() {\n\t\tif b4nnEnabled {\n\t\t\tbm, err := bypass4netnsutil.NewBypass4netnsCNIBypassManager(opts.bypassClient, opts.rootlessKitClient, opts.state.Annotations)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = bm.StartBypass(ctx, opts.ports, opts.state.ID, opts.state.Annotations[labels.StateDir])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"bypass4netnsd not running? (Hint: run `containerd-rootless-setuptool.sh install-bypass4netnsd`): %w\", err)\n\t\t\t}\n\t\t}\n\t\tif !b4nnBindEnabled && len(opts.ports) > 0 {\n\t\t\tif err := exposePortsRootless(ctx, opts.rootlessKitClient, opts.ports); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to expose ports in rootless mode: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc onCreateRuntime(opts *handlerOpts) error {\n\tloadAppArmor()\n\n\tname := opts.state.Annotations[labels.Name]\n\tns := opts.state.Annotations[labels.Namespace]\n\tnamst, err := namestore.New(opts.dataStore, ns)\n\tif err != nil {\n\t\tlog.L.WithError(err).Error(\"failed opening the namestore in onCreateRuntime\")\n\t} else if err := namst.Acquire(name, opts.state.ID); err != nil {\n\t\tlog.L.WithError(err).Error(\"failed re-acquiring name - see https://github.com/containerd/nerdctl/issues/2992\")\n\t}\n\n\tvar netError error\n\tif opts.cni != nil {\n\t\tnetError = applyNetworkSettings(opts)\n\t}\n\n\t// Set StartedAt and CreateError\n\tlf, err := state.New(opts.state.Annotations[labels.StateDir])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = lf.Transform(func(lf *state.Store) error {\n\t\tlf.StartedAt = time.Now()\n\t\tlf.CreateError = netError != nil\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn netError\n}\n\nfunc onPostStop(opts *handlerOpts) error {\n\tlf, err := state.New(opts.state.Annotations[labels.StateDir])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar shouldExit bool\n\terr = lf.Transform(func(lf *state.Store) error {\n\t\t// See https://github.com/containerd/nerdctl/issues/3357\n\t\t// Check if we actually errored during runtimeCreate\n\t\t// If that is the case, CreateError is set, and we are in postStop while the container will NOT be deleted (see ticket).\n\t\t// Thus, do NOT treat this as a deletion, as the container is still there.\n\t\t// Reset CreateError, and return.\n\t\tshouldExit = lf.CreateError\n\t\tlf.CreateError = false\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif shouldExit {\n\t\treturn nil\n\t}\n\n\tctx := context.Background()\n\tns := opts.state.Annotations[labels.Namespace]\n\tif opts.cni != nil {\n\t\tvar err error\n\t\tb4nnEnabled, b4nnBindEnabled, err := bypass4netnsutil.IsBypass4netnsEnabled(opts.state.Annotations)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif rootlessutil.IsRootlessChild() {\n\t\t\tif b4nnEnabled {\n\t\t\t\tbm, err := bypass4netnsutil.NewBypass4netnsCNIBypassManager(opts.bypassClient, opts.rootlessKitClient, opts.state.Annotations)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = bm.StopBypass(ctx, opts.state.ID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !b4nnBindEnabled && len(opts.ports) > 0 {\n\t\t\t\tif err := unexposePortsRootless(ctx, opts.rootlessKitClient, opts.ports); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to unexpose ports in rootless mode: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tportMapOpts, err := getPortMapOpts(opts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tipAddressOpts, err := getIPAddressOpts(opts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmacAddressOpts, err := getMACAddressOpts(opts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tip6AddressOpts, err := getIP6AddressOpts(opts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar namespaceOpts []cni.NamespaceOpts\n\t\tnamespaceOpts = append(namespaceOpts, portMapOpts...)\n\t\tnamespaceOpts = append(namespaceOpts, ipAddressOpts...)\n\t\tnamespaceOpts = append(namespaceOpts, macAddressOpts...)\n\t\tnamespaceOpts = append(namespaceOpts, ip6AddressOpts...)\n\t\tif err := opts.cni.Remove(ctx, opts.fullID, \"\", namespaceOpts...); err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"failed to call cni.Remove\")\n\t\t\treturn err\n\t\t}\n\n\t\t// opts.cni.Remove has trouble removing network configurations when netns is empty.\n\t\t// Therefore, we force the deletion of iptables rules here to prevent netns exhaustion.\n\t\t// This is a workaround until https://github.com/containernetworking/plugins/pull/1078 is merged.\n\t\tif err := cleanupIptablesRules(opts.fullID); err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"failed to clean up iptables rules for container %s\", opts.fullID)\n\t\t\t// Don't return error here, continue with the rest of the cleanup\n\t\t}\n\n\t\ths, err := hostsstore.New(opts.dataStore, ns)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := hs.Release(opts.state.ID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tnamst, err := namestore.New(opts.dataStore, ns)\n\tif err != nil {\n\t\treturn err\n\t}\n\tname := opts.state.Annotations[labels.Name]\n\t// Double-releasing may happen with containers started with --rm, so, ignore NotFound errors\n\tif err := namst.Release(name, opts.state.ID); err != nil && !errors.Is(err, store.ErrNotFound) {\n\t\treturn fmt.Errorf(\"failed to release container name %s: %w\", name, err)\n\t}\n\t// Kill port-reserver process if any\n\tportReserverPidFile := portReserverPidFilePath(opts)\n\tif err = killProcessByPidFile(portReserverPidFile); err != nil {\n\t\tlog.L.WithError(err).Errorf(\"failed to kill the port-reserver process\")\n\t}\n\treturn nil\n}\n\n// cleanupIptablesRules cleans up iptables rules related to the container\nfunc cleanupIptablesRules(containerID string) error {\n\t// Check if iptables command exists\n\tif _, err := exec.LookPath(\"iptables\"); err != nil {\n\t\treturn fmt.Errorf(\"iptables command not found: %w\", err)\n\t}\n\n\t// Tables to check for rules\n\ttables := []string{\"nat\", \"filter\", \"mangle\"}\n\n\tfor _, table := range tables {\n\t\t// Get all iptables rules for this table\n\t\tcmd := exec.Command(\"iptables\", \"-t\", table, \"-S\")\n\t\toutput, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Warnf(\"failed to list iptables rules for table %s\", table)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Find and delete rules related to the container\n\t\trules := strings.Split(string(output), \"\\n\")\n\t\tfor _, rule := range rules {\n\t\t\tif strings.Contains(rule, containerID) {\n\t\t\t\t// Execute delete command\n\t\t\t\tdeleteCmd := exec.Command(\"sh\", \"-c\", \"--\", fmt.Sprintf(`iptables -t %s -D %s`, table, rule[3:]))\n\t\t\t\tif deleteOutput, err := deleteCmd.CombinedOutput(); err != nil {\n\t\t\t\t\tlog.L.WithError(err).Warnf(\"failed to delete iptables rule: %s, output: %s\", rule, string(deleteOutput))\n\t\t\t\t} else {\n\t\t\t\t\tlog.L.Debugf(\"deleted iptables rule: %s\", rule)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// writePidFile writes the pid atomically to a file.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/commands.go#L265-L282\nfunc writePidFile(path string, pid int) error {\n\tpath, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdir := filepath.Dir(path)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\ttempPath := filepath.Join(dir, fmt.Sprintf(\".%s\", filepath.Base(path)))\n\tf, err := os.OpenFile(tempPath, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = fmt.Fprint(f, pid)\n\tf.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn os.Rename(tempPath, path)\n}\n\nfunc killProcessByPidFile(pidFile string) error {\n\tpidData, err := os.ReadFile(pidFile)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\terr = nil\n\t\t}\n\t\treturn err\n\t}\n\tpid, err := strconv.Atoi(strings.TrimSpace(string(pidData)))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse pid %q from %q: %w\", string(pidData), pidFile, err)\n\t}\n\tproc, err := os.FindProcess(pid)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to find process %d: %w\", pid, err)\n\t}\n\tif err := proc.Kill(); err != nil {\n\t\treturn fmt.Errorf(\"failed to kill process %d: %w\", pid, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/ocihook/ocihook_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ocihook\n\nimport (\n\t\"github.com/containerd/containerd/v2/contrib/apparmor\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/apparmorutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/defaults\"\n)\n\nfunc loadAppArmor() {\n\tif !apparmorutil.CanLoadNewProfile() {\n\t\treturn\n\t}\n\t// ensure that the default profile is loaded to the host\n\tif err := apparmor.LoadDefaultProfile(defaults.AppArmorProfileName); err != nil {\n\t\tlog.L.WithError(err).Errorf(\"failed to load AppArmor profile %q\", defaults.AppArmorProfileName)\n\t\t// We do not abort here. This is by design, and not a security issue.\n\t\t//\n\t\t// If the container is configured to use the default AppArmor profile\n\t\t// but the profile was not actually loaded, runc will fail.\n\t}\n}\n"
  },
  {
    "path": "pkg/ocihook/ocihook_nolinux.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ocihook\n\nfunc loadAppArmor() {\n\t//noop\n}\n"
  },
  {
    "path": "pkg/ocihook/rootless_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ocihook\n\nimport (\n\t\"context\"\n\n\trlkclient \"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc exposePortsRootless(ctx context.Context, rlkClient rlkclient.Client, ports []cni.PortMapping) error {\n\tpm, err := rootlessutil.NewRootlessCNIPortManager(rlkClient)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, p := range ports {\n\t\tif err := pm.ExposePort(ctx, p); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc unexposePortsRootless(ctx context.Context, rlkClient rlkclient.Client, ports []cni.PortMapping) error {\n\tpm, err := rootlessutil.NewRootlessCNIPortManager(rlkClient)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, p := range ports {\n\t\tif err := pm.UnexposePort(ctx, p); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/ocihook/rootless_other.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage ocihook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\trlkclient \"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n\n\t\"github.com/containerd/go-cni\"\n)\n\nfunc exposePortsRootless(ctx context.Context, rlkClient rlkclient.Client, ports []cni.PortMapping) error {\n\treturn fmt.Errorf(\"cannot expose ports rootlessly on non-Linux hosts\")\n}\n\nfunc unexposePortsRootless(ctx context.Context, rlkClient rlkclient.Client, ports []cni.PortMapping) error {\n\treturn fmt.Errorf(\"cannot unexpose ports rootlessly on non-Linux hosts\")\n}\n"
  },
  {
    "path": "pkg/ocihook/state/state.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package state provides a store to retrieve and save container lifecycle related information\n// This is typically used by oci-hooks for information that cannot be retrieved / updated otherwise\n// Specifically, the state carries container start time, and transient information about possible failures during\n// hook events processing.\n// All store methods are safe to use concurrently and only write atomically.\n// Since the state is transient and carrying solely informative data, errors returned from here could be treated as\n// soft-failures.\n// Note that locking is done at the container state directory level.\n// state is currently used by ocihooks and for read by dockercompat (to display started-at time)\npackage state\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/store\"\n)\n\n// lifecycleFile is the name of file carrying the container information, relative to stateDir\nconst lifecycleFile = \"lifecycle.json\"\n\n// ErrLifecycleStore will wrap all errors here\nvar ErrLifecycleStore = errors.New(\"lifecycle-store error\")\n\n// New will return a lifecycle struct for the container which stateDir is passed as argument\nfunc New(stateDir string) (*Store, error) {\n\tst, err := store.New(stateDir, 0, 0)\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrLifecycleStore, err)\n\t}\n\n\treturn &Store{\n\t\tsafeStore: st,\n\t}, nil\n}\n\n// Store exposes methods to retrieve and transform state information about containers.\ntype Store struct {\n\tsafeStore store.Store\n\n\t// StartedAt reflects the time at which we received the oci-hook onCreateRuntime event\n\tStartedAt   time.Time `json:\"started_at\"`\n\tCreateError bool      `json:\"create_error\"`\n}\n\n// Load will populate the struct with existing in-store lifecycle information\nfunc (lf *Store) Load() (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrLifecycleStore, err)\n\t\t}\n\t}()\n\n\treturn lf.safeStore.WithLock(lf.rawLoad)\n}\n\n// Transform should be used to perform random mutations\nfunc (lf *Store) Transform(fun func(lf *Store) error) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrLifecycleStore, err)\n\t\t}\n\t}()\n\n\treturn lf.safeStore.WithLock(func() error {\n\t\terr = lf.rawLoad()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = fun(lf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn lf.rawSave()\n\t})\n}\n\n// Delete will destroy the lifecycle data\nfunc (lf *Store) Delete() (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errors.Join(ErrLifecycleStore, err)\n\t\t}\n\t}()\n\n\treturn lf.safeStore.WithLock(lf.rawDelete)\n}\n\nfunc (lf *Store) rawLoad() (err error) {\n\tdata, err := lf.safeStore.Get(lifecycleFile)\n\tif err == nil {\n\t\terr = json.Unmarshal(data, lf)\n\t} else if errors.Is(err, store.ErrNotFound) {\n\t\terr = nil\n\t}\n\n\treturn err\n}\n\nfunc (lf *Store) rawSave() (err error) {\n\tdata, err := json.Marshal(lf)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn lf.safeStore.Set(data, lifecycleFile)\n}\n\nfunc (lf *Store) rawDelete() (err error) {\n\treturn lf.safeStore.Delete(lifecycleFile)\n}\n"
  },
  {
    "path": "pkg/platformutil/binfmt.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platformutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\n\t\"github.com/containerd/platforms\"\n)\n\nfunc qemuArchFromOCIArch(ociArch string) (string, error) {\n\tswitch ociArch {\n\tcase \"amd64\":\n\t\treturn \"x86_64\", nil\n\tcase \"arm64\":\n\t\treturn \"aarch64\", nil\n\tcase \"386\":\n\t\treturn \"i386\", nil\n\tcase \"arm\", \"s390x\", \"ppc64le\", \"riscv64\", \"mips64\":\n\t\treturn ociArch, nil\n\tcase \"mips64le\":\n\t\treturn \"mips64el\", nil // NOT typo\n\tcase \"loong64\":\n\t\treturn \"loongarch64\", nil // NOT typo\n\t}\n\treturn \"\", fmt.Errorf(\"unknown OCI architecture string: %q\", ociArch)\n}\n\nfunc canExecProbably(s string) (bool, error) {\n\tif s == \"\" {\n\t\treturn true, nil\n\t}\n\tp, err := platforms.Parse(s)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif platforms.Default().Match(p) {\n\t\treturn true, nil\n\t}\n\tif runtime.GOOS == \"linux\" {\n\t\tqemuArch, err := qemuArchFromOCIArch(p.Architecture)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tcandidates := []string{\n\t\t\t\"/proc/sys/fs/binfmt_misc/qemu-\" + qemuArch,\n\t\t\t\"/proc/sys/fs/binfmt_misc/buildkit-qemu-\" + qemuArch,\n\t\t}\n\t\t// Rosetta 2 for Linux on ARM Mac\n\t\t// https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta\n\t\tif runtime.GOARCH == \"arm64\" && p.Architecture == \"amd64\" {\n\t\t\tcandidates = append(candidates, \"/proc/sys/fs/binfmt_misc/rosetta\")\n\t\t}\n\t\tfor _, cand := range candidates {\n\t\t\tif _, err := os.Stat(cand); err == nil {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc CanExecProbably(ss ...string) (bool, error) {\n\tfor _, s := range ss {\n\t\tok, err := canExecProbably(s)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif !ok {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/platformutil/layers.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platformutil\n\nimport (\n\t\"context\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/platforms\"\n)\n\nfunc LayerDescs(ctx context.Context, provider content.Provider, imageTarget ocispec.Descriptor, platform platforms.MatchComparer) ([]ocispec.Descriptor, error) {\n\tvar descs []ocispec.Descriptor\n\terr := images.Walk(ctx, images.Handlers(images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\tif images.IsLayerType(desc.MediaType) {\n\t\t\tdescs = append(descs, desc)\n\t\t}\n\t\treturn nil, nil\n\t}), images.FilterPlatforms(images.ChildrenHandler(provider), platform)), imageTarget)\n\treturn descs, err\n}\n"
  },
  {
    "path": "pkg/platformutil/platformutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platformutil\n\nimport (\n\t\"fmt\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/platforms\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/strutil\"\n)\n\n// NewMatchComparerFromOCISpecPlatformSlice returns MatchComparer.\n// If platformz is empty, NewMatchComparerFromOCISpecPlatformSlice returns All (not DefaultStrict).\nfunc NewMatchComparerFromOCISpecPlatformSlice(platformz []ocispec.Platform) platforms.MatchComparer {\n\tif len(platformz) == 0 {\n\t\treturn platforms.All\n\t}\n\treturn platforms.Ordered(platformz...)\n}\n\n// NewMatchComparer returns MatchComparer.\n// If all is true, NewMatchComparer always returns All, regardless to the value of ss.\n// If all is false and ss is empty, NewMatchComparer returns DefaultStrict (not Default).\n// Otherwise NewMatchComparer returns Ordered MatchComparer.\nfunc NewMatchComparer(all bool, ss []string) (platforms.MatchComparer, error) {\n\tif all {\n\t\treturn platforms.All, nil\n\t}\n\tif len(ss) == 0 {\n\t\t// return DefaultStrict, not Default\n\t\treturn platforms.DefaultStrict(), nil\n\t}\n\top, err := NewOCISpecPlatformSlice(false, ss)\n\treturn platforms.Ordered(op...), err\n}\n\n// NewOCISpecPlatformSlice returns a slice of ocispec.Platform\n// If all is true, NewOCISpecPlatformSlice always returns an empty slice, regardless to the value of ss.\n// If all is false and ss is empty, NewOCISpecPlatformSlice returns DefaultSpec.\n// Otherwise NewOCISpecPlatformSlice returns the slice that correspond to ss.\nfunc NewOCISpecPlatformSlice(all bool, ss []string) ([]ocispec.Platform, error) {\n\tif all {\n\t\treturn nil, nil\n\t}\n\tif dss := strutil.DedupeStrSlice(ss); len(dss) > 0 {\n\t\tvar op []ocispec.Platform\n\t\tfor _, s := range dss {\n\t\t\tp, err := platforms.Parse(s)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid platform: %q\", s)\n\t\t\t}\n\t\t\top = append(op, p)\n\t\t}\n\t\treturn op, nil\n\t}\n\treturn []ocispec.Platform{platforms.DefaultSpec()}, nil\n}\n\nfunc NormalizeString(s string) (string, error) {\n\tif s == \"\" {\n\t\treturn platforms.DefaultString(), nil\n\t}\n\tparsed, err := platforms.Parse(s)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tnormalized := platforms.Normalize(parsed)\n\treturn platforms.Format(normalized), nil\n}\n"
  },
  {
    "path": "pkg/portutil/iptable/iptables.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage iptable\n\nimport (\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// ParseIPTableRules takes a slice of iptables rules as input and returns a slice of\n// uint64 containing the parsed destination port numbers from the rules.\nfunc ParseIPTableRules(rules []string) []uint64 {\n\tports := []uint64{}\n\n\t// Regex to match the '--dports' option followed by the port number\n\tdportRegex := regexp.MustCompile(`--dports ((,?\\d+)+)`)\n\n\tfor _, rule := range rules {\n\t\tmatches := dportRegex.FindStringSubmatch(rule)\n\t\tif len(matches) > 1 {\n\t\t\tfor _, _match := range strings.Split(matches[1], \",\") {\n\t\t\t\tport64, err := strconv.ParseUint(_match, 10, 16)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tports = append(ports, port64)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ports\n}\n"
  },
  {
    "path": "pkg/portutil/iptable/iptables_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage iptable\n\nimport (\n\t\"github.com/coreos/go-iptables/iptables\"\n)\n\n// Chain used for port forwarding rules: https://www.cni.dev/plugins/current/meta/portmap/#dnat\nconst cniDnatChain = \"CNI-HOSTPORT-DNAT\"\n\nfunc ReadIPTables(table string) ([]string, error) {\n\tipt, err := iptables.New()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar rules []string\n\tchainExists, _ := ipt.ChainExists(table, cniDnatChain)\n\tif chainExists {\n\t\trules, err = ipt.List(table, cniDnatChain)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn rules, nil\n}\n"
  },
  {
    "path": "pkg/portutil/iptable/iptables_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage iptable\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParseIPTableRules(t *testing.T) {\n\ttestCases := []struct {\n\t\tname  string\n\t\trules []string\n\t\twant  []uint64\n\t}{\n\t\t{\n\t\t\tname:  \"Empty input\",\n\t\t\trules: []string{},\n\t\t\twant:  []uint64{},\n\t\t},\n\t\t{\n\t\t\tname: \"Single rule with single port\",\n\t\t\trules: []string{\n\t\t\t\t\"-A CNI-HOSTPORT-DNAT -p tcp -m comment --comment \\\"dnat name: \\\"bridge\\\" id: \\\"some-id\\\"\\\" -m multiport --dports 8080 -j CNI-DN-some-hash\",\n\t\t\t},\n\t\t\twant: []uint64{8080},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple rules with multiple ports\",\n\t\t\trules: []string{\n\t\t\t\t\"-A CNI-HOSTPORT-DNAT -p tcp -m comment --comment \\\"dnat name: \\\"bridge\\\" id: \\\"some-id\\\"\\\" -m multiport --dports 8080 -j CNI-DN-some-hash\",\n\t\t\t\t\"-A CNI-HOSTPORT-DNAT -p tcp -m comment --comment \\\"dnat name: \\\"bridge\\\" id: \\\"some-id\\\"\\\" -m multiport --dports 9090 -j CNI-DN-some-hash\",\n\t\t\t},\n\t\t\twant: []uint64{8080, 9090},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := ParseIPTableRules(tc.rules)\n\t\t\tif !equal(got, tc.want) {\n\t\t\t\tt.Errorf(\"ParseIPTableRules(%v) = %v; want %v\", tc.rules, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc equal(a, b []uint64) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/portutil/port_allocate_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage portutil\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil/iptable\"\n\t\"github.com/containerd/nerdctl/v2/pkg/portutil/procnet\"\n)\n\nconst (\n\t// This port range is compatible with Docker, FYI https://github.com/moby/moby/blob/eb9e42a09ee123af1d95bf7d46dd738258fa2109/libnetwork/portallocator/portallocator_unix.go#L7-L12\n\tallocateEnd = uint64(60999)\n\n\ttcpTimeWait  = 6 //TIME_WAIT state is represented by the value 6 in /proc/net/tcp\n\ttcpCloseWait = 8 //CLOSE_WAIT state is represented by the value 8 in /proc/net/tcp\n)\n\nvar (\n\tallocateStart = uint64(49153)\n)\n\nfunc filter(ss []procnet.NetworkDetail, filterFunc func(detail procnet.NetworkDetail) bool) (ret []procnet.NetworkDetail) {\n\tfor _, s := range ss {\n\t\tif filterFunc(s) {\n\t\t\tret = append(ret, s)\n\t\t}\n\t}\n\treturn\n}\n\nfunc portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {\n\tusedPorts, err := getUsedPorts(ip, protocol)\n\tif err != nil {\n\t\treturn 0, 0, err\n\t}\n\n\tstart := allocateStart\n\tif count > allocateEnd-allocateStart+1 {\n\t\treturn 0, 0, fmt.Errorf(\"can not allocate %d ports\", count)\n\t}\n\tfor start < allocateEnd {\n\t\tneedReturn := true\n\t\tfor i := start; i < start+count; i++ {\n\t\t\tif _, ok := usedPorts[i]; ok {\n\t\t\t\tneedReturn = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif needReturn {\n\t\t\tallocateStart = start + count\n\t\t\treturn start, start + count - 1, nil\n\t\t}\n\t\tstart += count\n\t}\n\treturn 0, 0, fmt.Errorf(\"there is not enough %d free ports\", count)\n}\n\nfunc getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {\n\tnetprocItems := []procnet.NetworkDetail{}\n\n\tif protocol == \"tcp\" || protocol == \"udp\" {\n\t\tnetprocData, err := procnet.ReadStatsFileData(protocol)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnetprocItems = append(netprocItems, procnet.Parse(netprocData)...)\n\t}\n\n\t// In some circumstances, when we bind address like \"0.0.0.0:80\", we will get the formation of \":::80\" in /proc/net/tcp6.\n\t// So we need some trick to process this situation.\n\tif protocol == \"tcp\" {\n\t\ttempTCPV6Data, err := procnet.ReadStatsFileData(\"tcp6\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnetprocItems = append(netprocItems, procnet.Parse(tempTCPV6Data)...)\n\t}\n\tif protocol == \"udp\" {\n\t\ttempUDPV6Data, err := procnet.ReadStatsFileData(\"udp6\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnetprocItems = append(netprocItems, procnet.Parse(tempUDPV6Data)...)\n\t}\n\tif ip != \"\" {\n\t\tnetprocItems = filter(netprocItems, func(s procnet.NetworkDetail) bool {\n\t\t\t// In some circumstances, when we bind address like \"0.0.0.0:80\", we will get the formation of \":::80\" in /proc/net/tcp6.\n\t\t\t// So we need some trick to process this situation.\n\t\t\treturn s.LocalIP.String() == \"::\" || s.LocalIP.String() == ip\n\t\t})\n\t}\n\n\tusedPort := make(map[uint64]bool)\n\tfor _, value := range netprocItems {\n\t\t// Skip ports in TIME_WAIT or CLOSE_WAIT state\n\t\tif protocol == \"tcp\" && (value.State == tcpTimeWait || value.State == tcpCloseWait) {\n\t\t\t// In rootless mode, Rootlesskit creates extra socket connections to proxy traffic from the host network namespace\n\t\t\t// to the container namespace. Proxy TCP connections can remain in TIME_WAIT state for 10-20 seconds even when the\n\t\t\t// container is stopped/removed, which is standard TCP behavior. These ports are actually available for allocation\n\t\t\t// despite appearing in /proc/net/tcp.\n\t\t\tcontinue\n\t\t}\n\t\tusedPort[value.LocalPort] = true\n\t}\n\n\tipTableItems, err := iptable.ReadIPTables(\"nat\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdestinationPorts := iptable.ParseIPTableRules(ipTableItems)\n\n\tfor _, port := range destinationPorts {\n\t\tusedPort[port] = true\n\t}\n\n\treturn usedPort, nil\n}\n"
  },
  {
    "path": "pkg/portutil/port_allocate_other.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage portutil\n\nimport \"fmt\"\n\nfunc portAllocate(protocol string, ip string, count uint64) (uint64, uint64, error) {\n\treturn 0, 0, fmt.Errorf(\"auto port allocate are not support Non-Linux platform yet\")\n}\n\nfunc getUsedPorts(ip string, protocol string) (map[uint64]bool, error) {\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/portutil/portutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage portutil\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/docker/go-connections/nat\"\n\n\t\"github.com/containerd/go-cni\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/labels\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil/networkstore\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\n// return respectively ip, hostPort, containerPort\nfunc splitParts(rawport string) (string, string, string) {\n\tlastIndex := strings.LastIndex(rawport, \":\")\n\tcontainerPort := rawport[lastIndex+1:]\n\tif lastIndex == -1 {\n\t\treturn \"\", \"\", containerPort\n\t}\n\n\thostAddrPort := rawport[:lastIndex]\n\taddr, port, err := net.SplitHostPort(hostAddrPort)\n\tif err != nil {\n\t\treturn \"\", hostAddrPort, containerPort\n\t}\n\n\treturn addr, port, containerPort\n}\n\n// ParseFlagP parse port mapping pair, like \"127.0.0.1:3000:8080/tcp\",\n// \"127.0.0.1:3000-3001:8080-8081/tcp\" and \"3000:8080\" ...\nfunc ParseFlagP(s string) ([]cni.PortMapping, error) {\n\tproto := \"tcp\"\n\tsplitBySlash := strings.Split(s, \"/\")\n\tswitch len(splitBySlash) {\n\tcase 1:\n\t// NOP\n\tcase 2:\n\t\tproto = strings.ToLower(splitBySlash[1])\n\t\tswitch proto {\n\t\tcase \"tcp\", \"udp\", \"sctp\":\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"invalid protocol %q\", splitBySlash[1])\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"failed to parse %q, unexpected slashes\", s)\n\t}\n\n\tres := cni.PortMapping{\n\t\tProtocol: proto,\n\t}\n\n\tmr := []cni.PortMapping{}\n\n\tip, hostPort, containerPort := splitParts(splitBySlash[0])\n\n\tif containerPort == \"\" {\n\t\treturn nil, fmt.Errorf(\"no port specified: %s\", splitBySlash[0])\n\t}\n\tvar startHostPort uint64\n\tvar endHostPort uint64\n\n\tstartPort, endPort, err := nat.ParsePortRange(containerPort)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid containerPort: %s\", containerPort)\n\t}\n\tif hostPort == \"\" {\n\t\t// AutoHostPort could not be supported in rootless mode right now, because we can't get correct network from /proc/net/*\n\t\tif rootlessutil.IsRootless() {\n\t\t\treturn nil, fmt.Errorf(\"automatic port allocation is not implemented for rootless mode (Hint: specify the port like \\\"12345:%s\\\", not just \\\"%s\\\")\",\n\t\t\t\tcontainerPort, containerPort)\n\t\t}\n\t\tstartHostPort, endHostPort, err = portAllocate(proto, ip, endPort-startPort+1)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlog.L.Debugf(\"There is no hostPort has been spec in command, the auto allocate port is from %d:%d to %d:%d\", startHostPort, startPort, endHostPort, endPort)\n\t} else {\n\t\tstartHostPort, endHostPort, err = nat.ParsePortRange(hostPort)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid hostPort: %s\", hostPort)\n\t\t}\n\t\tvar usedPorts map[uint64]bool\n\t\tusedPorts, err = getUsedPorts(ip, proto)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor i := startHostPort; i <= endHostPort; i++ {\n\t\t\tif usedPorts[i] {\n\t\t\t\treturn nil, fmt.Errorf(\"bind for %s:%d failed: port is already allocated\", ip, i)\n\t\t\t}\n\t\t}\n\t}\n\tif hostPort != \"\" && (endPort-startPort) != (endHostPort-startHostPort) {\n\t\tif endPort != startPort {\n\t\t\treturn nil, fmt.Errorf(\"invalid ranges specified for container and host Ports: %s and %s\", containerPort, hostPort)\n\t\t}\n\t}\n\n\tfor i := int32(0); i <= (int32(endPort) - int32(startPort)); i++ {\n\n\t\tres.ContainerPort = int32(startPort) + i\n\t\tres.HostPort = int32(startHostPort) + i\n\t\tif ip == \"\" {\n\t\t\t//TODO handle ipv6\n\t\t\tres.HostIP = \"0.0.0.0\"\n\t\t} else {\n\t\t\t// TODO handle ipv6\n\t\t\tif net.ParseIP(ip) == nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid ip address: %s\", ip)\n\t\t\t}\n\t\t\tres.HostIP = ip\n\t\t}\n\n\t\tmr = append(mr, res)\n\t}\n\n\treturn mr, nil\n}\n\nfunc StoreNetworkConfig(dataStore, namespace, id string, netConf networkstore.NetworkConfig) error {\n\tns, err := networkstore.New(dataStore, namespace, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn ns.Acquire(netConf)\n}\n\nfunc LoadPortMappings(dataStore, namespace, id string, containerLabels map[string]string) ([]cni.PortMapping, error) {\n\tvar ports []cni.PortMapping\n\n\tns, err := networkstore.New(dataStore, namespace, id)\n\tif err != nil {\n\t\treturn ports, err\n\t}\n\tif err = ns.Load(); err != nil {\n\t\treturn ports, err\n\t}\n\tif len(ns.NetConf.PortMappings) != 0 {\n\t\treturn ns.NetConf.PortMappings, nil\n\t}\n\n\tportsJSON := containerLabels[labels.Ports]\n\tif portsJSON == \"\" {\n\t\treturn ports, nil\n\t}\n\tif err := json.Unmarshal([]byte(portsJSON), &ports); err != nil {\n\t\treturn ports, fmt.Errorf(\"failed to parse label %q=%q: %s\", labels.Ports, portsJSON, err.Error())\n\t}\n\tlog.L.Warnf(\"container %s (%s) is using legacy port mapping configuration. To ensure compatibility with the new port mapping logic, please recreate this container. For more details, see: https://github.com/containerd/nerdctl/pull/4290\", containerLabels[labels.Name], id[:12])\n\treturn ports, nil\n}\n"
  },
  {
    "path": "pkg/portutil/portutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage portutil\n\nimport (\n\t\"reflect\"\n\t\"runtime\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/go-cni\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\nfunc TestParseFlagPWithPlatformSpec(t *testing.T) {\n\tif runtime.GOOS != \"linux\" || rootlessutil.IsRootless() {\n\t\tt.Skip(\"no non-Linux platform or rootless mode in Linux are not supported yet\")\n\t}\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    []cni.PortMapping\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"without colon\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3000,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Enable auto host port\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000-3001\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3000,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3001,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Enable auto host port error\",\n\t\t\targs: args{\n\t\t\t\ts: \"49153-61000\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Enable auto host port with tcp protocol\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000-3001/tcp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3000,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3001,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Enable auto host port with udp protocol\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000-3001/udp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3000,\n\t\t\t\t\tProtocol:      \"udp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tContainerPort: 3001,\n\t\t\t\t\tProtocol:      \"udp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseFlagP(tt.args.s)\n\t\t\tif err != nil {\n\t\t\t\tt.Log(err)\n\t\t\t\tassert.Equal(t, true, tt.wantErr)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tassert.Equal(t, len(got), len(tt.want))\n\t\t\t\tif len(got) > 0 {\n\t\t\t\t\tsort.Slice(got, func(i, j int) bool {\n\t\t\t\t\t\treturn got[i].HostPort < got[j].HostPort\n\t\t\t\t\t})\n\t\t\t\t\tassert.Equal(\n\t\t\t\t\t\tt,\n\t\t\t\t\t\tgot[len(got)-1].HostPort-got[0].HostPort,\n\t\t\t\t\t\tgot[len(got)-1].ContainerPort-got[0].ContainerPort,\n\t\t\t\t\t)\n\t\t\t\t\tfor i := range len(got) {\n\t\t\t\t\t\tassert.Equal(t, got[i].ContainerPort, tt.want[i].ContainerPort)\n\t\t\t\t\t\tassert.Equal(t, got[i].Protocol, tt.want[i].Protocol)\n\t\t\t\t\t\tassert.Equal(t, got[i].HostIP, tt.want[i].HostIP)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseFlagP(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname       string\n\t\targs       args\n\t\twant       []cni.PortMapping\n\t\twantErrMsg string\n\t}{\n\t\t{\n\t\t\tname: \"normal\",\n\t\t\targs: args{\n\t\t\t\ts: \"127.0.0.1:3000:8080/tcp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"with port range\",\n\t\t\targs: args{\n\t\t\t\ts: \"127.0.0.1:3000-3001:8080-8081/tcp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3001,\n\t\t\t\t\tContainerPort: 8081,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"with wrong port range\",\n\t\t\targs: args{\n\t\t\t\ts: \"127.0.0.1:3000-3001:8080-8082/tcp\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: \"invalid ranges specified for container and host Ports: 8080-8082 and 3000-3001\",\n\t\t},\n\t\t{\n\t\t\tname: \"without host ip\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000:8080/tcp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"without protocol\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000:8080\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"with protocol udp\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000:8080/udp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"udp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"with protocol sctp\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000:8080/sctp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      3000,\n\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\tProtocol:      \"sctp\",\n\t\t\t\t\tHostIP:        \"0.0.0.0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"with ipv6 host ip\",\n\t\t\targs: args{\n\t\t\t\ts: \"[::0]:8080:80/tcp\",\n\t\t\t},\n\t\t\twant: []cni.PortMapping{\n\t\t\t\t{\n\t\t\t\t\tHostPort:      8080,\n\t\t\t\t\tContainerPort: 80,\n\t\t\t\t\tProtocol:      \"tcp\",\n\t\t\t\t\tHostIP:        \"::0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrMsg: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"with invalid protocol\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000:8080/invalid\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: `invalid protocol \"invalid\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"multiple colon\",\n\t\t\targs: args{\n\t\t\t\ts: \"127.0.0.1:3000:0.0.0.0:8080\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: \"invalid hostPort: 127.0.0.1:3000:0.0.0.0\",\n\t\t},\n\t\t{\n\t\t\tname: \"multiple slash\",\n\t\t\targs: args{\n\t\t\t\ts: \"127.0.0.1:3000:8080/tcp/\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: `failed to parse \"127.0.0.1:3000:8080/tcp/\", unexpected slashes`,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid ip\",\n\t\t\targs: args{\n\t\t\t\ts: \"127.0.0.256:3000:8080/tcp\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: \"invalid ip address: 127.0.0.256\",\n\t\t},\n\t\t{\n\t\t\tname: \"large port\",\n\t\t\targs: args{\n\t\t\t\ts: \"3000:65536\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: \"invalid containerPort: 65536\",\n\t\t},\n\t\t{\n\t\t\tname: \"blank\",\n\t\t\targs: args{\n\t\t\t\ts: \"\",\n\t\t\t},\n\t\t\twant:       nil,\n\t\t\twantErrMsg: \"no port specified: \",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseFlagP(tt.args.s)\n\t\t\tif tt.wantErrMsg == \"\" {\n\t\t\t\tassert.NilError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.Error(t, err, tt.wantErrMsg)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tassert.Equal(t, len(got), len(tt.want))\n\t\t\t\tif len(got) > 0 {\n\t\t\t\t\tsort.Slice(got, func(i, j int) bool {\n\t\t\t\t\t\treturn got[i].HostPort < got[j].HostPort\n\t\t\t\t\t})\n\t\t\t\t\tassert.Equal(\n\t\t\t\t\t\tt,\n\t\t\t\t\t\tgot[len(got)-1].HostPort-got[0].HostPort,\n\t\t\t\t\t\tgot[len(got)-1].ContainerPort-got[0].ContainerPort,\n\t\t\t\t\t)\n\t\t\t\t\tfor i := range len(got) {\n\t\t\t\t\t\tassert.Equal(t, got[i].HostPort, tt.want[i].HostPort)\n\t\t\t\t\t\tassert.Equal(t, got[i].ContainerPort, tt.want[i].ContainerPort)\n\t\t\t\t\t\tassert.Equal(t, got[i].Protocol, tt.want[i].Protocol)\n\t\t\t\t\t\tassert.Equal(t, got[i].HostIP, tt.want[i].HostIP)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/portutil/procnet/procnet.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage procnet\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype NetworkDetail struct {\n\tLocalIP   net.IP\n\tLocalPort uint64\n\tState     int\n}\n\nfunc Parse(data []string) (results []NetworkDetail) {\n\ttemp := removeEmpty(data)\n\tfor _, value := range temp {\n\t\tlineData := removeEmpty(strings.Split(strings.TrimSpace(value), \" \"))\n\t\tip, port, err := ParseAddress(lineData[1])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tstate := 0\n\t\tif len(lineData) > 2 {\n\t\t\tstateHex, err := strconv.ParseInt(lineData[3], 16, 32)\n\t\t\tif err == nil {\n\t\t\t\tstate = int(stateHex)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, NetworkDetail{\n\t\t\tLocalIP:   ip,\n\t\t\tLocalPort: uint64(port),\n\t\t\tState:     state,\n\t\t})\n\t}\n\treturn results\n}\n\nfunc removeEmpty(array []string) (results []string) {\n\tfor _, i := range array {\n\t\tif i != \"\" {\n\t\t\tresults = append(results, i)\n\t\t}\n\t}\n\treturn results\n}\n\n// ParseAddress parses a string, e.g.,Akihiro Suda, 10 months ago: • initial commit\n// \"0100007F:0050\"                         (127.0.0.1:80)\n// \"000080FE00000000FF57A6705DC771FE:0050\" ([fe80::70a6:57ff:fe71:c75d]:80)\n// \"00000000000000000000000000000000:0050\" (0.0.0.0:80)\n//\n// See https://serverfault.com/questions/592574/why-does-proc-net-tcp6-represents-1-as-1000\n//\n// ParseAddress is expected to be used for /proc/net/{tcp,tcp6} entries on\n// little endian machines.\n// Not sure how those entries look like on big endian machines.\n// All the code below is copied from the lima project in https://github.com/lima-vm/lima/blob/v0.8.3/pkg/guestagent/procnettcp/procnettcp.go#L95-L137\n// and is licensed under the Apache License, Version 2.0\nfunc ParseAddress(s string) (net.IP, uint16, error) {\n\tsplit := strings.SplitN(s, \":\", 2)\n\tif len(split) != 2 {\n\t\treturn nil, 0, fmt.Errorf(\"unparsable address %q\", s)\n\t}\n\tswitch l := len(split[0]); l {\n\tcase 8, 32:\n\tdefault:\n\t\treturn nil, 0, fmt.Errorf(\"unparsable address %q, expected length of %q to be 8 or 32, got %d\",\n\t\t\ts, split[0], l)\n\t}\n\n\tipBytes := make([]byte, len(split[0])/2) // 4 bytes (8 chars) or 16 bytes (32 chars)\n\tfor i := 0; i < len(split[0])/8; i++ {\n\t\tquartet := split[0][8*i : 8*(i+1)]\n\t\tquartetLE, err := hex.DecodeString(quartet) // surprisingly little endian, per 4 bytes\n\t\tif err != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"unparsable address %q: unparsable quartet %q: %w\", s, quartet, err)\n\t\t}\n\t\tfor j := 0; j < len(quartetLE); j++ {\n\t\t\tipBytes[4*i+len(quartetLE)-1-j] = quartetLE[j]\n\t\t}\n\t}\n\tip := net.IP(ipBytes)\n\n\tport64, err := strconv.ParseUint(split[1], 16, 16)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"unparsable address %q: unparsable port %q\", s, split[1])\n\t}\n\tport := uint16(port64)\n\n\treturn ip, port, nil\n}\n"
  },
  {
    "path": "pkg/portutil/procnet/procnet_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage procnet\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n)\n\nconst (\n\ttcpProto  = \"tcp\"\n\tudpProto  = \"udp\"\n\ttcp6Proto = \"tcp6\"\n\tudp6Proto = \"udp6\"\n\t// FIXME: The /proc/net/tcp is not recommended by the kernel, FYI https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt\n\t// In the future, we should use netlink instead of /proc/net/tcp\n\tnetTCPStats  = \"/proc/net/tcp\"\n\tnetUDPStats  = \"/proc/net/udp\"\n\tnetTCP6Stats = \"/proc/net/tcp6\"\n\tnetUDP6Stats = \"/proc/net/udp6\"\n)\n\nfunc ReadStatsFileData(protocol string) ([]string, error) {\n\tvar fileAddress string\n\n\tif protocol == tcpProto {\n\t\tfileAddress = netTCPStats\n\t} else if protocol == udpProto {\n\t\tfileAddress = netUDPStats\n\t} else if protocol == tcp6Proto {\n\t\tfileAddress = netTCP6Stats\n\t} else if protocol == udp6Proto {\n\t\tfileAddress = netUDP6Stats\n\t} else {\n\t\treturn nil, fmt.Errorf(\"unknown protocol %s\", protocol)\n\t}\n\n\tfp, err := os.Open(fileAddress)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer fp.Close()\n\tvar lines []string\n\tsc := bufio.NewScanner(fp)\n\n\tfor i := 0; sc.Scan(); i++ {\n\t\tif i == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tlines = append(lines, sc.Text())\n\t}\n\n\treturn lines, nil\n}\n"
  },
  {
    "path": "pkg/portutil/procnet/procnetd_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage procnet\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\n// All the code in this file is copied from the iima project in https://github.com/lima-vm/lima/blob/v0.8.3/pkg/guestagent/procnettcp/procnettcp_test.go.\n// and is licensed under the Apache License, Version 2.0.\n\nfunc TestParseTCP(t *testing.T) {\n\tprocNetTCP := []string{\n\t\t\"0: 0100007F:8AEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 28152 1 0000000000000000 100 0 0 10 0\",\n\t\t\"1: 0103000A:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31474 1 0000000000000000 100 0 0 10 5\",\n\t\t\"2: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   102        0 30955 1 0000000000000000 100 0 0 10 0\",\n\t\t\"3: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 32910 1 0000000000000000 100 0 0 10 0\",\n\t\t\"4: 0100007F:053A 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31430 1 0000000000000000 100 0 0 10 0\",\n\t\t\"5: 0B3CA8C0:0016 690AA8C0:F705 01 00000000:00000000 02:00028D8B 00000000     0        0 32989 4 0000000000000000 20 4 31 10 19\"}\n\tentries := Parse(procNetTCP)\n\tt.Log(entries)\n\n\tassert.Check(t, net.ParseIP(\"127.0.0.1\").Equal(entries[0].LocalIP))\n\tassert.Equal(t, uint64(35567), entries[0].LocalPort)\n\n\tassert.Check(t, net.ParseIP(\"192.168.60.11\").Equal(entries[5].LocalIP))\n\tassert.Equal(t, uint64(22), entries[5].LocalPort)\n}\n\nfunc TestParseTCP6(t *testing.T) {\n\tprocNetTCP := []string{\n\t\t\"0: 000080FE00000000FF57A6705DC771FE:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 850222 1 0000000000000000 100 0 0 10 0\",\n\t}\n\tentries := Parse(procNetTCP)\n\tt.Log(entries)\n\n\tassert.Check(t, net.ParseIP(\"fe80::70a6:57ff:fe71:c75d\").Equal(entries[0].LocalIP))\n\tassert.Equal(t, uint64(80), entries[0].LocalPort)\n}\n\nfunc TestParseTCP6Zero(t *testing.T) {\n\tprocNetTCP := []string{\n\t\t\"0: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 33825 1 0000000000000000 100 0 0 10 0\",\n\t\t\"1: 00000000000000000000000000000000:006F 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 26772 1 0000000000000000 100 0 0 10 0\",\n\t\t\"2: 00000000000000000000000000000000:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 1210901 1 0000000000000000 100 0 0 10 0\"}\n\n\tentries := Parse(procNetTCP)\n\tt.Log(entries)\n\n\tassert.Check(t, net.IPv6zero.Equal(entries[0].LocalIP))\n\tassert.Equal(t, uint64(22), entries[0].LocalPort)\n}\n"
  },
  {
    "path": "pkg/referenceutil/cid_ipfs.go",
    "content": "//go:build !no_ipfs\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage referenceutil\n\nimport \"github.com/ipfs/go-cid\"\n\nfunc decodeCid(v string) (string, error) {\n\tc, err := cid.Decode(v)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn c.String(), nil\n}\n"
  },
  {
    "path": "pkg/referenceutil/cid_noipfs.go",
    "content": "//go:build no_ipfs\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage referenceutil\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/containerd/errdefs\"\n)\n\nfunc decodeCid(v string) (string, error) {\n\treturn \"\", fmt.Errorf(\"%w: ipfs is disabled by the distributor of this build\", errdefs.ErrNotImplemented)\n}\n"
  },
  {
    "path": "pkg/referenceutil/referenceutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage referenceutil\n\nimport (\n\t\"errors\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/distribution/reference\"\n\t\"github.com/opencontainers/go-digest\"\n)\n\ntype Protocol string\n\nconst IPFSProtocol Protocol = \"ipfs\"\nconst IPNSProtocol Protocol = \"ipns\"\nconst shortIDLength = 5\n\nvar ErrLoadOCIArchiveRequired = errors.New(\"image must be loaded from archive before parsing image reference\")\n\ntype ImageReference struct {\n\tProtocol    Protocol\n\tDigest      digest.Digest\n\tTag         string\n\tExplicitTag string\n\tPath        string\n\tDomain      string\n\n\tnn reference.Reference\n}\n\nfunc (ir *ImageReference) Name() string {\n\tret := ir.Domain\n\tif ret != \"\" {\n\t\tret += \"/\"\n\t}\n\tret += ir.Path\n\treturn ret\n}\n\nfunc (ir *ImageReference) FamiliarName() string {\n\tif ir.Protocol != \"\" && ir.Domain == \"\" {\n\t\treturn ir.Path\n\t}\n\tif ir.nn != nil {\n\t\treturn reference.FamiliarName(ir.nn.(reference.Named))\n\t}\n\treturn \"\"\n}\n\nfunc (ir *ImageReference) FamiliarMatch(pattern string) (bool, error) {\n\tif ir.nn != nil {\n\t\treturn reference.FamiliarMatch(pattern, ir.nn)\n\t}\n\treturn false, nil\n}\n\nfunc (ir *ImageReference) String() string {\n\tif ir.Protocol != \"\" && ir.Domain == \"\" {\n\t\treturn ir.Path\n\t}\n\tif ir.Path == \"\" && ir.Digest != \"\" {\n\t\treturn ir.Digest.String()\n\t}\n\tif ir.nn != nil {\n\t\treturn ir.nn.String()\n\t}\n\treturn \"\"\n}\n\nfunc (ir *ImageReference) SuggestContainerName(suffix string) string {\n\tname := \"untitled\"\n\tif ir.Protocol != \"\" && ir.Domain == \"\" {\n\t\tname = string(ir.Protocol) + \"-\" + ir.String()[:shortIDLength]\n\t} else if ir.Path != \"\" {\n\t\tname = path.Base(ir.Path)\n\t}\n\treturn name + \"-\" + suffix[:5]\n}\n\nfunc Parse(rawRef string) (*ImageReference, error) {\n\tir := &ImageReference{}\n\n\tif strings.HasPrefix(rawRef, \"ipfs://\") {\n\t\tir.Protocol = IPFSProtocol\n\t\trawRef = rawRef[7:]\n\t} else if strings.HasPrefix(rawRef, \"ipns://\") {\n\t\tir.Protocol = IPNSProtocol\n\t\trawRef = rawRef[7:]\n\t} else if strings.HasPrefix(rawRef, \"oci-archive://\") {\n\t\t// The image must be loaded from the specified archive path first\n\t\t// before parsing the image reference specified in its OCI image manifest.\n\t\treturn nil, ErrLoadOCIArchiveRequired\n\t}\n\tif decodedCID, err := decodeCid(rawRef); err == nil {\n\t\tir.Protocol = IPFSProtocol\n\t\trawRef = decodedCID\n\t\tir.Path = rawRef\n\t\treturn ir, nil\n\t}\n\n\tif dgst, err := digest.Parse(rawRef); err == nil {\n\t\tir.Digest = dgst\n\t\treturn ir, nil\n\t} else if dgst, err := digest.Parse(\"sha256:\" + rawRef); err == nil {\n\t\tir.Digest = dgst\n\t\treturn ir, nil\n\t}\n\n\tvar err error\n\tir.nn, err = reference.ParseNormalizedNamed(rawRef)\n\tif err != nil {\n\t\treturn ir, err\n\t}\n\tif tg, ok := ir.nn.(reference.Tagged); ok {\n\t\tir.ExplicitTag = tg.Tag()\n\t}\n\tif tg, ok := ir.nn.(reference.Named); ok {\n\t\tir.nn = reference.TagNameOnly(tg)\n\t\tir.Domain = reference.Domain(tg)\n\t\tir.Path = reference.Path(tg)\n\t}\n\tif tg, ok := ir.nn.(reference.Tagged); ok {\n\t\tir.Tag = tg.Tag()\n\t}\n\tif tg, ok := ir.nn.(reference.Digested); ok {\n\t\tir.Digest = tg.Digest()\n\t}\n\n\treturn ir, nil\n}\n"
  },
  {
    "path": "pkg/referenceutil/referenceutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage referenceutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/opencontainers/go-digest\"\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestReferenceUtil(t *testing.T) {\n\tneedles := map[string]struct {\n\t\tError         string\n\t\tString        string\n\t\tSuggested     string\n\t\tFamiliarName  string\n\t\tFamiliarMatch map[string]bool\n\t\tProtocol      Protocol\n\t\tDigest        digest.Digest\n\t\tPath          string\n\t\tDomain        string\n\t\tTag           string\n\t\tExplicitTag   string\n\t}{\n\t\t\"\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"∞\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"abcd:∞\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"abcd@sha256:∞\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"abcd@∞\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"abcd:foo@sha256:∞\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"abcd:foo@∞\": {\n\t\t\tError: \"invalid reference format\",\n\t\t},\n\t\t\"sha256:whatever\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/sha256:whatever\",\n\t\t\tSuggested:    \"sha256-abcde\",\n\t\t\tFamiliarName: \"sha256\",\n\t\t\tFamiliarMatch: map[string]bool{\n\t\t\t\t\"*a*\":                      true,\n\t\t\t\t\"?ha25?\":                   true,\n\t\t\t\t\"[s-z]ha25[0-9]\":           true,\n\t\t\t\t\"[^a]ha25[^a-z]\":           true,\n\t\t\t\t\"*6:whatever\":              true,\n\t\t\t\t\"docker.io/library/sha256\": false,\n\t\t\t},\n\t\t\tProtocol:    \"\",\n\t\t\tDigest:      \"\",\n\t\t\tPath:        \"library/sha256\",\n\t\t\tDomain:      \"docker.io\",\n\t\t\tTag:         \"whatever\",\n\t\t\tExplicitTag: \"whatever\",\n\t\t},\n\t\t\"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tSuggested:    \"untitled-abcde\",\n\t\t\tFamiliarName: \"\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tPath:         \"\",\n\t\t\tDomain:       \"\",\n\t\t\tTag:          \"\",\n\t\t},\n\t\t\"4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tSuggested:    \"untitled-abcde\",\n\t\t\tFamiliarName: \"\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tPath:         \"\",\n\t\t\tDomain:       \"\",\n\t\t\tTag:          \"\",\n\t\t},\n\t\t\"image_name\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"library/image_name\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"something/image_name\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/something/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"something/image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"something/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"docker.io/library/image_name\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"image_name:latest\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"latest\",\n\t\t},\n\t\t\"image_name:foo\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name:foo\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"foo\",\n\t\t\tExplicitTag:  \"foo\",\n\t\t},\n\t\t\"image_name@sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name@sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"image_name:latest@sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/image_name:latest@sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"sha256:4b826db5f1f14d1db0b560304f189d4b17798ddce2278b7822c9d32313fe3f50\",\n\t\t\tPath:         \"library/image_name\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"latest\",\n\t\t},\n\t\t\"ghcr.io:1234/image_name\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"ghcr.io:1234/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"ghcr.io:1234/image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"image_name\",\n\t\t\tDomain:       \"ghcr.io:1234\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"ghcr.io/sub_name/image_name\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"ghcr.io/sub_name/image_name:latest\",\n\t\t\tSuggested:    \"image_name-abcde\",\n\t\t\tFamiliarName: \"ghcr.io/sub_name/image_name\",\n\t\t\tProtocol:     \"\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"sub_name/image_name\",\n\t\t\tDomain:       \"ghcr.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\",\n\t\t\tSuggested:    \"ipfs-bafkr-abcde\",\n\t\t\tFamiliarName: \"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\",\n\t\t\tProtocol:     \"ipfs\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\",\n\t\t\tDomain:       \"\",\n\t\t\tTag:          \"\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"ipfs://bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\",\n\t\t\tSuggested:    \"ipfs-bafkr-abcde\",\n\t\t\tFamiliarName: \"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\",\n\t\t\tProtocol:     \"ipfs\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"bafkreicq4dg6nkef5ju422ptedcwfz6kcvpvvhuqeykfrwq5krazf3muze\",\n\t\t\tDomain:       \"\",\n\t\t\tTag:          \"\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"ipfs://ghcr.io/stargz-containers/alpine:3.13-org\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"ghcr.io/stargz-containers/alpine:3.13-org\",\n\t\t\tSuggested:    \"alpine-abcde\",\n\t\t\tFamiliarName: \"ghcr.io/stargz-containers/alpine\",\n\t\t\tFamiliarMatch: map[string]bool{\n\t\t\t\t\"ghcr.io/stargz-containers/alpine\": true,\n\t\t\t\t\"*/*/*\":                            true,\n\t\t\t\t\"*/*/*:3.13-org\":                   true,\n\t\t\t},\n\t\t\tProtocol:    \"ipfs\",\n\t\t\tDigest:      \"\",\n\t\t\tPath:        \"stargz-containers/alpine\",\n\t\t\tDomain:      \"ghcr.io\",\n\t\t\tTag:         \"3.13-org\",\n\t\t\tExplicitTag: \"3.13-org\",\n\t\t},\n\t\t\"ipfs://alpine\": {\n\t\t\tError:        \"\",\n\t\t\tString:       \"docker.io/library/alpine:latest\",\n\t\t\tSuggested:    \"alpine-abcde\",\n\t\t\tFamiliarName: \"alpine\",\n\t\t\tProtocol:     \"ipfs\",\n\t\t\tDigest:       \"\",\n\t\t\tPath:         \"library/alpine\",\n\t\t\tDomain:       \"docker.io\",\n\t\t\tTag:          \"latest\",\n\t\t\tExplicitTag:  \"\",\n\t\t},\n\t\t\"oci-archive:///tmp/build/saved-image.tar\": {\n\t\t\tError: \"image must be loaded from archive before parsing image reference\",\n\t\t},\n\t}\n\n\tfor k, v := range needles {\n\t\tparsed, err := Parse(k)\n\t\tif v.Error != \"\" || err != nil {\n\t\t\tassert.Error(t, err, v.Error)\n\t\t\tcontinue\n\t\t}\n\t\tassert.Equal(t, parsed.String(), v.String, k)\n\t\tassert.Equal(t, parsed.SuggestContainerName(\"abcdefghij\"), v.Suggested, k)\n\t\tassert.Equal(t, parsed.FamiliarName(), v.FamiliarName, k)\n\t\tfor needle, result := range v.FamiliarMatch {\n\t\t\tres, err := parsed.FamiliarMatch(needle)\n\t\t\tassert.NilError(t, err)\n\t\t\tassert.Equal(t, res, result, k)\n\t\t}\n\n\t\tassert.Equal(t, parsed.Protocol, v.Protocol, k)\n\t\tassert.Equal(t, parsed.Digest, v.Digest, k)\n\t\tassert.Equal(t, parsed.Path, v.Path, k)\n\t\tassert.Equal(t, parsed.Domain, v.Domain, k)\n\t\tassert.Equal(t, parsed.Tag, v.Tag, k)\n\t\tassert.Equal(t, parsed.ExplicitTag, v.ExplicitTag, k)\n\t}\n}\n"
  },
  {
    "path": "pkg/reflectutil/reflectutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage reflectutil\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n)\n\nfunc UnknownNonEmptyFields(structOrStructPtr interface{}, knownNames ...string) []string {\n\tvar unknown []string\n\tknownNamesMap := make(map[string]struct{}, len(knownNames))\n\tfor _, name := range knownNames {\n\t\tknownNamesMap[name] = struct{}{}\n\t}\n\torigVal := reflect.ValueOf(structOrStructPtr)\n\tvar val reflect.Value\n\tswitch kind := origVal.Kind(); kind {\n\tcase reflect.Ptr:\n\t\tval = origVal.Elem()\n\tcase reflect.Struct:\n\t\tval = origVal\n\tdefault:\n\t\tpanic(fmt.Errorf(\"expected Ptr or Struct, got %+v\", kind))\n\t}\n\tfor i := 0; i < val.NumField(); i++ {\n\t\tiField := val.Field(i)\n\t\tif isEmpty(iField) {\n\t\t\tcontinue\n\t\t}\n\t\tiName := val.Type().Field(i).Name\n\t\tif _, ok := knownNamesMap[iName]; !ok {\n\t\t\tunknown = append(unknown, iName)\n\t\t}\n\t}\n\treturn unknown\n}\n\nfunc isEmpty(v reflect.Value) bool {\n\t// NOTE: IsZero returns false for zero-length map and slice\n\tif v.IsZero() {\n\t\treturn true\n\t}\n\tswitch v.Kind() {\n\tcase reflect.Map, reflect.Slice:\n\t\treturn v.Len() == 0\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/reflectutil/reflectutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage reflectutil\n\nimport (\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestUnknownNonEmptyFields(t *testing.T) {\n\ttype foo struct {\n\t\tFooBool    bool\n\t\tFooBoolPtr *bool\n\t\tFooMap     map[string]struct{}\n\t\tFooStr     string\n\t\tFooStr2    string\n\t\tFooStr3    string\n\t}\n\n\tfoo1 := foo{\n\t\tFooBool:    true,\n\t\tFooBoolPtr: nil,\n\t\tFooMap:     nil,\n\t\tFooStr:     \"foo\",\n\t\tFooStr2:    \"\",\n\t\tFooStr3:    \"oof\",\n\t}\n\tassert.DeepEqual(t,\n\t\t[]string{\"FooStr\", \"FooStr3\"},\n\t\tUnknownNonEmptyFields(&foo1, \"FooBool\"))\n\tassert.DeepEqual(t,\n\t\t[]string{\"FooStr\", \"FooStr3\"},\n\t\tUnknownNonEmptyFields(foo1, \"FooBool\"))\n\tassert.DeepEqual(t,\n\t\t[]string{\"FooStr\", \"FooStr3\"},\n\t\tUnknownNonEmptyFields(&foo1, \"FooBool\", \"FooMap\"))\n\n\tfoo2 := foo1\n\tfoo2.FooBoolPtr = &foo1.FooBool\n\tfoo2.FooMap = map[string]struct{}{\n\t\t\"blah\": {},\n\t}\n\tassert.DeepEqual(t,\n\t\t[]string{\"FooBoolPtr\", \"FooMap\", \"FooStr\", \"FooStr3\"},\n\t\tUnknownNonEmptyFields(&foo2, \"FooBool\"))\n\n\tfoo3 := foo1\n\tfoo3.FooMap = make(map[string]struct{})\n\tassert.DeepEqual(t,\n\t\t[]string{\"FooStr\", \"FooStr3\"},\n\t\tUnknownNonEmptyFields(&foo3, \"FooBool\"))\n}\n"
  },
  {
    "path": "pkg/resolvconf/resolvconf.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/moby/blob/6014c1e29dc34dffa77fb5749cc3281c1b4854ac/libnetwork/resolvconf/resolvconf.go\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/6014c1e29dc34dffa77fb5749cc3281c1b4854ac/NOTICE\n*/\n\n// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf\n// originally from https://github.com/moby/moby/blob/6014c1e29dc34dffa77fb5749cc3281c1b4854ac/libnetwork/resolvconf/resolvconf.go\npackage resolvconf\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"io\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nconst (\n\t// defaultPath is the default path to the resolv.conf that contains information to resolve DNS. See Path().\n\tdefaultPath = \"/etc/resolv.conf\"\n\t// alternatePath is a path different from defaultPath, that may be used to resolve DNS. See Path().\n\talternatePath = \"/run/systemd/resolve/resolv.conf\"\n)\n\n// constants for the IP address type\nconst (\n\tIP = iota // IPv4 and IPv6\n\tIPv4\n\tIPv6\n)\n\nvar (\n\tdetectSystemdResolvConfOnce sync.Once\n\tpathAfterSystemdDetection   = defaultPath\n)\n\n// Path returns the path to the resolv.conf file that libnetwork should use.\n//\n// When /etc/resolv.conf contains 127.0.0.53 as the only nameserver, then\n// it is assumed systemd-resolved manages DNS. Because inside the container 127.0.0.53\n// is not a valid DNS server, Path() returns /run/systemd/resolve/resolv.conf\n// which is the resolv.conf that systemd-resolved generates and manages.\n// Otherwise Path() returns /etc/resolv.conf.\n//\n// Errors are silenced as they will inevitably resurface at future open/read calls.\n//\n// More information at https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html#/etc/resolv.conf\nfunc Path() string {\n\tdetectSystemdResolvConfOnce.Do(func() {\n\t\tcandidateResolvConf, err := filesystem.ReadFile(defaultPath)\n\t\tif err != nil {\n\t\t\t// silencing error as it will resurface at next calls trying to read defaultPath\n\t\t\treturn\n\t\t}\n\t\tns := GetNameservers(candidateResolvConf, IP)\n\t\tif len(ns) == 1 && ns[0] == \"127.0.0.53\" {\n\t\t\tpathAfterSystemdDetection = alternatePath\n\t\t\tlog.L.Debugf(\"detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: %s\", alternatePath)\n\t\t}\n\t})\n\treturn pathAfterSystemdDetection\n}\n\nconst (\n\t// ipLocalhost is a regex pattern for IPv4 or IPv6 loopback range.\n\tipLocalhost  = `((127\\.([0-9]{1,3}\\.){2}[0-9]{1,3})|(::1)$)`\n\tipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`\n\tipv4Address  = `(` + ipv4NumBlock + `\\.){3}` + ipv4NumBlock\n\n\t// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also\n\t// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants\n\t// -- e.g. other link-local types -- either won't work in containers or are unnecessary.\n\t// For readability and sufficiency for Docker purposes this seemed more reasonable than a\n\t// 1000+ character regexp with exact and complete IPv6 validation\n\tipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})(%\\w+)?`\n)\n\nvar (\n\t// Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS\n\tdefaultIPv4Dns = []string{\"nameserver 8.8.8.8\", \"nameserver 8.8.4.4\"}\n\tdefaultIPv6Dns = []string{\"nameserver 2001:4860:4860::8888\", \"nameserver 2001:4860:4860::8844\"}\n\n\tlocalhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\\s+` + ipLocalhost + `\\s*\\n*`)\n\tnsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\\s+` + ipv6Address + `\\s*\\n*`)\n\tnsRegexp          = regexp.MustCompile(`^\\s*nameserver\\s*((` + ipv4Address + `)|(` + ipv6Address + `))\\s*$`)\n\tnsIPv6Regexpmatch = regexp.MustCompile(`^\\s*nameserver\\s*((` + ipv6Address + `))\\s*$`)\n\tnsIPv4Regexpmatch = regexp.MustCompile(`^\\s*nameserver\\s*((` + ipv4Address + `))\\s*$`)\n\tsearchRegexp      = regexp.MustCompile(`^\\s*search\\s*(([^\\s]+\\s*)*)$`)\n\toptionsRegexp     = regexp.MustCompile(`^\\s*options\\s*(([^\\s]+\\s*)*)$`)\n)\n\nvar lastModified struct {\n\tsync.Mutex\n\tsha256   string\n\tcontents []byte\n}\n\n// File contains the resolv.conf content and its hash\ntype File struct {\n\tContent []byte\n\tHash    string\n}\n\n// Get returns the contents of /etc/resolv.conf and its hash\nfunc Get() (*File, error) {\n\treturn GetSpecific(Path())\n}\n\n// GetSpecific returns the contents of the user specified resolv.conf file and its hash\nfunc GetSpecific(path string) (*File, error) {\n\tresolv, err := filesystem.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thash, err := hashData(bytes.NewReader(resolv))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &File{Content: resolv, Hash: hash}, nil\n}\n\n// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash\n// and, if modified since last check, returns the bytes and new hash.\n// This feature is used by the resolv.conf updater for containers\nfunc GetIfChanged() (*File, error) {\n\tlastModified.Lock()\n\tdefer lastModified.Unlock()\n\n\tresolv, err := filesystem.ReadFile(Path())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnewHash, err := hashData(bytes.NewReader(resolv))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif lastModified.sha256 != newHash {\n\t\tlastModified.sha256 = newHash\n\t\tlastModified.contents = resolv\n\t\treturn &File{Content: resolv, Hash: newHash}, nil\n\t}\n\t// nothing changed, so return no data\n\treturn nil, nil\n}\n\n// GetLastModified retrieves the last used contents and hash of the host resolv.conf.\n// Used by containers updating on restart\nfunc GetLastModified() *File {\n\tlastModified.Lock()\n\tdefer lastModified.Unlock()\n\n\treturn &File{Content: lastModified.contents, Hash: lastModified.sha256}\n}\n\n// FilterResolvDNS cleans up the config in resolvConf.  It has two main jobs:\n//  1. It looks for localhost (127.*|::1) entries in the provided\n//     resolv.conf, removing local nameserver entries, and, if the resulting\n//     cleaned config has no defined nameservers left, adds default DNS entries\n//  2. Given the caller provides the enable/disable state of IPv6, the filter\n//     code will remove all IPv6 nameservers if it is not enabled for containers\nfunc FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {\n\treturn FilterResolvDNSWithLocalhostOption(resolvConf, ipv6Enabled, false)\n}\n\n// FilterResolvDNSWithLocalhostOption is like FilterResolvDNS but allows controlling\n// whether localhost nameservers are preserved. This is useful for host network mode\n// where the container should inherit the host's DNS configuration including localhost resolvers.\n//\n// Parameters:\n//   - resolvConf: the resolv.conf file content\n//   - ipv6Enabled: whether IPv6 nameservers should be preserved\n//   - allowLocalhostDNS: if true, localhost nameservers are preserved; if false, they are filtered out\nfunc FilterResolvDNSWithLocalhostOption(resolvConf []byte, ipv6Enabled bool, allowLocalhostDNS bool) (*File, error) {\n\tcleanedResolvConf := resolvConf\n\t// if allowLocalhostDNS is false, remove localhost nameservers\n\tif !allowLocalhostDNS {\n\t\tcleanedResolvConf = localhostNSRegexp.ReplaceAll(cleanedResolvConf, []byte{})\n\t}\n\t// if IPv6 is not enabled, also clean out any IPv6 address nameserver\n\tif !ipv6Enabled {\n\t\tcleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})\n\t}\n\t// if the resulting resolvConf has no more nameservers defined, add appropriate\n\t// default DNS servers for IPv4 and (optionally) IPv6\n\tif len(GetNameservers(cleanedResolvConf, IP)) == 0 {\n\t\tlog.L.Infof(\"No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v\", defaultIPv4Dns)\n\t\tdns := defaultIPv4Dns\n\t\tif ipv6Enabled {\n\t\t\tlog.L.Infof(\"IPv6 enabled; Adding default IPv6 external servers: %v\", defaultIPv6Dns)\n\t\t\tdns = append(dns, defaultIPv6Dns...)\n\t\t}\n\t\tcleanedResolvConf = append(cleanedResolvConf, []byte(\"\\n\"+strings.Join(dns, \"\\n\"))...)\n\t}\n\thash, err := hashData(bytes.NewReader(cleanedResolvConf))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &File{Content: cleanedResolvConf, Hash: hash}, nil\n}\n\n// getLines parses input into lines and strips away comments.\nfunc getLines(input []byte, commentMarker []byte) [][]byte {\n\tlines := bytes.Split(input, []byte(\"\\n\"))\n\tvar output [][]byte\n\tfor _, currentLine := range lines {\n\t\tvar commentIndex = bytes.Index(currentLine, commentMarker)\n\t\tif commentIndex == -1 {\n\t\t\toutput = append(output, currentLine)\n\t\t} else {\n\t\t\toutput = append(output, currentLine[:commentIndex])\n\t\t}\n\t}\n\treturn output\n}\n\n// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf\nfunc GetNameservers(resolvConf []byte, kind int) []string {\n\tnameservers := []string{}\n\tfor _, line := range getLines(resolvConf, []byte(\"#\")) {\n\t\tvar ns [][]byte\n\t\tif kind == IP {\n\t\t\tns = nsRegexp.FindSubmatch(line)\n\t\t} else if kind == IPv4 {\n\t\t\tns = nsIPv4Regexpmatch.FindSubmatch(line)\n\t\t} else if kind == IPv6 {\n\t\t\tns = nsIPv6Regexpmatch.FindSubmatch(line)\n\t\t}\n\t\tif len(ns) > 0 {\n\t\t\tnameservers = append(nameservers, string(ns[1]))\n\t\t}\n\t}\n\treturn nameservers\n}\n\n// GetNameserversAsCIDR returns nameservers (if any) listed in\n// /etc/resolv.conf as CIDR blocks (e.g., \"1.2.3.4/32\")\n// This function's output is intended for net.ParseCIDR\nfunc GetNameserversAsCIDR(resolvConf []byte) []string {\n\tnameservers := []string{}\n\tfor _, nameserver := range GetNameservers(resolvConf, IP) {\n\t\tvar address string\n\t\t// If IPv6, strip zone if present\n\t\tif strings.Contains(nameserver, \":\") {\n\t\t\taddress = strings.Split(nameserver, \"%\")[0] + \"/128\"\n\t\t} else {\n\t\t\taddress = nameserver + \"/32\"\n\t\t}\n\t\tnameservers = append(nameservers, address)\n\t}\n\treturn nameservers\n}\n\n// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf\n// If more than one search line is encountered, only the contents of the last\n// one is returned.\nfunc GetSearchDomains(resolvConf []byte) []string {\n\tdomains := []string{}\n\tfor _, line := range getLines(resolvConf, []byte(\"#\")) {\n\t\tmatch := searchRegexp.FindSubmatch(line)\n\t\tif match == nil {\n\t\t\tcontinue\n\t\t}\n\t\tdomains = strings.Fields(string(match[1]))\n\t}\n\treturn domains\n}\n\n// GetOptions returns options (if any) listed in /etc/resolv.conf\n// If more than one options line is encountered, only the contents of the last\n// one is returned.\nfunc GetOptions(resolvConf []byte) []string {\n\toptions := []string{}\n\tfor _, line := range getLines(resolvConf, []byte(\"#\")) {\n\t\tmatch := optionsRegexp.FindSubmatch(line)\n\t\tif match == nil {\n\t\t\tcontinue\n\t\t}\n\t\toptions = strings.Fields(string(match[1]))\n\t}\n\treturn options\n}\n\n// Build writes a configuration file to path containing a \"nameserver\" entry\n// for every element in dns, a \"search\" entry for every element in\n// dnsSearch, and an \"options\" entry for every element in dnsOptions.\nfunc Build(path string, dns, dnsSearch, dnsOptions []string) (*File, error) {\n\tcontent := bytes.NewBuffer(nil)\n\tif len(dnsSearch) > 0 {\n\t\tif searchString := strings.Join(dnsSearch, \" \"); strings.Trim(searchString, \" \") != \".\" {\n\t\t\tif _, err := content.WriteString(\"search \" + searchString + \"\\n\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, dns := range dns {\n\t\tif _, err := content.WriteString(\"nameserver \" + dns + \"\\n\"); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif len(dnsOptions) > 0 {\n\t\tif optsString := strings.Join(dnsOptions, \" \"); strings.Trim(optsString, \" \") != \"\" {\n\t\t\tif _, err := content.WriteString(\"options \" + optsString + \"\\n\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\thash, err := hashData(bytes.NewReader(content.Bytes()))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = filesystem.WriteFile(path, content.Bytes(), 0o644)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// WriteFile relies on syscall.Open. Unless there are ACLs, the effective mode of the file will be matched\n\t// against the current process umask.\n\t// See https://www.man7.org/linux/man-pages/man2/open.2.html for details.\n\t// Since we must make sure that these files are world readable, explicitly chmod them here.\n\treturn &File{Content: content.Bytes(), Hash: hash}, os.Chmod(path, 0o644)\n}\n\nfunc hashData(src io.Reader) (string, error) {\n\th := sha256.New()\n\tif _, err := io.Copy(h, src); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn \"sha256:\" + hex.EncodeToString(h.Sum(nil)), nil\n}\n"
  },
  {
    "path": "pkg/resolvconf/resolvconf_linux_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// originally from https://github.com/moby/moby/blob/6014c1e29dc34dffa77fb5749cc3281c1b4854ac/libnetwork/resolvconf/resolvconf_linux_test.go\npackage resolvconf\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nfunc TestGet(t *testing.T) {\n\tresolvConfUtils, err := Get()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresolvConfSystem, err := filesystem.ReadFile(\"/run/systemd/resolve/resolv.conf\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(resolvConfUtils.Content) != string(resolvConfSystem) {\n\t\tt.Fatalf(\"/etc/resolv.conf and GetResolvConf have different content.\")\n\t}\n\thashSystem, err := hashData(bytes.NewReader(resolvConfSystem))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif resolvConfUtils.Hash != hashSystem {\n\t\tt.Fatalf(\"/etc/resolv.conf and GetResolvConf have different hashes.\")\n\t}\n}\n\nfunc TestGetNameservers(t *testing.T) {\n\tfor resolv, result := range map[string][]string{`\nnameserver 1.2.3.4\nnameserver 40.3.200.10\nsearch example.com`: {\"1.2.3.4\", \"40.3.200.10\"},\n\t\t`search example.com`: {},\n\t\t`nameserver 1.2.3.4\nsearch example.com\nnameserver 4.30.20.100`: {\"1.2.3.4\", \"4.30.20.100\"},\n\t\t``:                        {},\n\t\t`  nameserver 1.2.3.4   `: {\"1.2.3.4\"},\n\t\t`search example.com\nnameserver 1.2.3.4\n#nameserver 4.3.2.1`: {\"1.2.3.4\"},\n\t\t`search example.com\nnameserver 1.2.3.4 # not 4.3.2.1`: {\"1.2.3.4\"},\n\t} {\n\t\ttest := GetNameservers([]byte(resolv), IP)\n\t\tif !strSlicesEqual(test, result) {\n\t\t\tt.Fatalf(\"Wrong nameserver string {%s} should be %v. Input: %s\", test, result, resolv)\n\t\t}\n\t}\n}\n\nfunc TestGetNameserversAsCIDR(t *testing.T) {\n\tfor resolv, result := range map[string][]string{`\nnameserver 1.2.3.4\nnameserver 40.3.200.10\nsearch example.com`: {\"1.2.3.4/32\", \"40.3.200.10/32\"},\n\t\t`search example.com`: {},\n\t\t`nameserver 1.2.3.4\nsearch example.com\nnameserver 4.30.20.100`: {\"1.2.3.4/32\", \"4.30.20.100/32\"},\n\t\t``:                        {},\n\t\t`  nameserver 1.2.3.4   `: {\"1.2.3.4/32\"},\n\t\t`search example.com\nnameserver 1.2.3.4\n#nameserver 4.3.2.1`: {\"1.2.3.4/32\"},\n\t\t`search example.com\nnameserver 1.2.3.4 # not 4.3.2.1`: {\"1.2.3.4/32\"},\n\t} {\n\t\ttest := GetNameserversAsCIDR([]byte(resolv))\n\t\tif !strSlicesEqual(test, result) {\n\t\t\tt.Fatalf(\"Wrong nameserver string {%s} should be %v. Input: %s\", test, result, resolv)\n\t\t}\n\t}\n}\n\nfunc TestGetSearchDomains(t *testing.T) {\n\tfor resolv, result := range map[string][]string{\n\t\t`search example.com`:                                   {\"example.com\"},\n\t\t`search example.com # ignored`:                         {\"example.com\"},\n\t\t`\t  search\t example.com\t  `:                            {\"example.com\"},\n\t\t`\t  search\t example.com\t  # ignored`:                   {\"example.com\"},\n\t\t`search foo.example.com example.com`:                   {\"foo.example.com\", \"example.com\"},\n\t\t`\t   search\t   foo.example.com\t example.com\t`:          {\"foo.example.com\", \"example.com\"},\n\t\t`\t   search\t   foo.example.com\t example.com\t# ignored`: {\"foo.example.com\", \"example.com\"},\n\t\t``:          {},\n\t\t`# ignored`: {},\n\t\t`nameserver 1.2.3.4\nsearch foo.example.com example.com`: {\"foo.example.com\", \"example.com\"},\n\t\t`nameserver 1.2.3.4\nsearch dup1.example.com dup2.example.com\nsearch foo.example.com example.com`: {\"foo.example.com\", \"example.com\"},\n\t\t`nameserver 1.2.3.4\nsearch foo.example.com example.com\nnameserver 4.30.20.100`: {\"foo.example.com\", \"example.com\"},\n\t} {\n\t\ttest := GetSearchDomains([]byte(resolv))\n\t\tif !strSlicesEqual(test, result) {\n\t\t\tt.Fatalf(\"Wrong search domain string {%s} should be %v. Input: %s\", test, result, resolv)\n\t\t}\n\t}\n}\n\nfunc TestGetOptions(t *testing.T) {\n\tfor resolv, result := range map[string][]string{\n\t\t`options opt1`:                            {\"opt1\"},\n\t\t`options opt1 # ignored`:                  {\"opt1\"},\n\t\t`\t  options\t opt1\t  `:                     {\"opt1\"},\n\t\t`\t  options\t opt1\t  # ignored`:            {\"opt1\"},\n\t\t`options opt1 opt2 opt3`:                  {\"opt1\", \"opt2\", \"opt3\"},\n\t\t`options opt1 opt2 opt3 # ignored`:        {\"opt1\", \"opt2\", \"opt3\"},\n\t\t`\t   options\t opt1\t opt2\t opt3\t`:          {\"opt1\", \"opt2\", \"opt3\"},\n\t\t`\t   options\t opt1\t opt2\t opt3\t# ignored`: {\"opt1\", \"opt2\", \"opt3\"},\n\t\t``:                   {},\n\t\t`# ignored`:          {},\n\t\t`nameserver 1.2.3.4`: {},\n\t\t`nameserver 1.2.3.4\noptions opt1 opt2 opt3`: {\"opt1\", \"opt2\", \"opt3\"},\n\t\t`nameserver 1.2.3.4\noptions opt1 opt2\noptions opt3 opt4`: {\"opt3\", \"opt4\"},\n\t} {\n\t\ttest := GetOptions([]byte(resolv))\n\t\tif !strSlicesEqual(test, result) {\n\t\t\tt.Fatalf(\"Wrong options string {%s} should be %v. Input: %s\", test, result, resolv)\n\t\t}\n\t}\n}\n\nfunc strSlicesEqual(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc TestBuild(t *testing.T) {\n\tfile, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(file.Name())\n\n\t_, err = Build(file.Name(), []string{\"ns1\", \"ns2\", \"ns3\"}, []string{\"search1\"}, []string{\"opt1\"})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontent, err := filesystem.ReadFile(file.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif expected := \"search search1\\nnameserver ns1\\nnameserver ns2\\nnameserver ns3\\noptions opt1\\n\"; !bytes.Contains(content, []byte(expected)) {\n\t\tt.Fatalf(\"Expected to find '%s' got '%s'\", expected, content)\n\t}\n}\n\nfunc TestBuildWithZeroLengthDomainSearch(t *testing.T) {\n\tfile, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(file.Name())\n\n\t_, err = Build(file.Name(), []string{\"ns1\", \"ns2\", \"ns3\"}, []string{\".\"}, []string{\"opt1\"})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontent, err := filesystem.ReadFile(file.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif expected := \"nameserver ns1\\nnameserver ns2\\nnameserver ns3\\noptions opt1\\n\"; !bytes.Contains(content, []byte(expected)) {\n\t\tt.Fatalf(\"Expected to find '%s' got '%s'\", expected, content)\n\t}\n\tif notExpected := \"search .\"; bytes.Contains(content, []byte(notExpected)) {\n\t\tt.Fatalf(\"Expected to not find '%s' got '%s'\", notExpected, content)\n\t}\n}\n\nfunc TestBuildWithNoOptions(t *testing.T) {\n\tfile, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(file.Name())\n\n\t_, err = Build(file.Name(), []string{\"ns1\", \"ns2\", \"ns3\"}, []string{\"search1\"}, []string{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontent, err := filesystem.ReadFile(file.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif expected := \"search search1\\nnameserver ns1\\nnameserver ns2\\nnameserver ns3\\n\"; !bytes.Contains(content, []byte(expected)) {\n\t\tt.Fatalf(\"Expected to find '%s' got '%s'\", expected, content)\n\t}\n\tif notExpected := \"search .\"; bytes.Contains(content, []byte(notExpected)) {\n\t\tt.Fatalf(\"Expected to not find '%s' got '%s'\", notExpected, content)\n\t}\n}\n\nfunc TestFilterResolvDns(t *testing.T) {\n\tns0 := \"nameserver 10.16.60.14\\nnameserver 10.16.60.21\\n\"\n\n\tif result, _ := FilterResolvDNS([]byte(ns0), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed No Localhost: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\tns1 := \"nameserver 10.16.60.14\\nnameserver 10.16.60.21\\nnameserver 127.0.0.1\\n\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\tns1 = \"nameserver 10.16.60.14\\nnameserver 127.0.0.1\\nnameserver 10.16.60.21\\n\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\tns1 = \"nameserver 127.0.1.1\\nnameserver 10.16.60.14\\nnameserver 10.16.60.21\\n\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\tns1 = \"nameserver ::1\\nnameserver 10.16.60.14\\nnameserver 127.0.2.1\\nnameserver 10.16.60.21\\n\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\tns1 = \"nameserver 10.16.60.14\\nnameserver ::1\\nnameserver 10.16.60.21\\nnameserver ::1\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\t// with IPv6 disabled (false param), the IPv6 nameserver should be removed\n\tns1 = \"nameserver 10.16.60.14\\nnameserver 2002:dead:beef::1\\nnameserver 10.16.60.21\\nnameserver ::1\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost+IPv6 off: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\t// with IPv6 disabled (false param), the IPv6 link-local nameserver with zone ID should be removed\n\tns1 = \"nameserver 10.16.60.14\\nnameserver FE80::BB1%1\\nnameserver FE80::BB1%eth0\\nnameserver 10.16.60.21\\n\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost+IPv6 off: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\t// with IPv6 enabled, the IPv6 nameserver should be preserved\n\tns0 = \"nameserver 10.16.60.14\\nnameserver 2002:dead:beef::1\\nnameserver 10.16.60.21\\n\"\n\tns1 = \"nameserver 10.16.60.14\\nnameserver 2002:dead:beef::1\\nnameserver 10.16.60.21\\nnameserver ::1\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), true); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed Localhost+IPv6 on: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\t// with IPv6 enabled, and no non-localhost servers, Google defaults (both IPv4+IPv6) should be added\n\tns0 = \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\\nnameserver 2001:4860:4860::8888\\nnameserver 2001:4860:4860::8844\"\n\tns1 = \"nameserver 127.0.0.1\\nnameserver ::1\\nnameserver 127.0.2.1\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), true); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed no Localhost+IPv6 enabled: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n\n\t// with IPv6 disabled, and no non-localhost servers, Google defaults (only IPv4) should be added\n\tns0 = \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\"\n\tns1 = \"nameserver 127.0.0.1\\nnameserver ::1\\nnameserver 127.0.2.1\"\n\tif result, _ := FilterResolvDNS([]byte(ns1), false); result != nil {\n\t\tif ns0 != string(result.Content) {\n\t\t\tt.Fatalf(\"Failed no Localhost+IPv6 enabled: expected \\n<%s> got \\n<%s>\", ns0, string(result.Content))\n\t\t}\n\t}\n}\n\nfunc TestFilterResolvDnsWithLocalhostOption(t *testing.T) {\n\ttestCases := []struct {\n\t\tname              string\n\t\tinput             string\n\t\tallowLocalhostDNS bool\n\t\tipv6Enabled       bool\n\t\texpected          string\n\t}{\n\t\t{\n\t\t\tname:              \"filter_disallow_localhost_ipv6_disabled\",\n\t\t\tinput:             \"nameserver 127.0.0.53\\nnameserver 192.88.99.1\\nnameserver ::1\\nnameserver 2001:db8::1\\n\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       false,\n\t\t\texpected:          \"nameserver 192.88.99.1\\n\",\n\t\t},\n\t\t{\n\t\t\tname:              \"filter_allow_localhost_ipv6_disabled\",\n\t\t\tinput:             \"nameserver 127.0.0.53\\nnameserver 192.88.99.1\\nnameserver ::1\\nnameserver 2001:db8::1\\n\",\n\t\t\tallowLocalhostDNS: true,\n\t\t\tipv6Enabled:       false,\n\t\t\texpected:          \"nameserver 127.0.0.53\\nnameserver 192.88.99.1\\n\",\n\t\t},\n\t\t{\n\t\t\tname:              \"filter_disallow_localhost_ipv6_enabled\",\n\t\t\tinput:             \"nameserver 127.0.0.53\\nnameserver 192.88.99.1\\nnameserver ::1\\nnameserver 2001:db8::1\\n\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       true,\n\t\t\texpected:          \"nameserver 192.88.99.1\\nnameserver 2001:db8::1\\n\",\n\t\t},\n\t\t{\n\t\t\tname:              \"filter_allow_localhost_ipv6_enabled\",\n\t\t\tinput:             \"nameserver 127.0.0.53\\nnameserver 192.88.99.1\\nnameserver ::1\\nnameserver 2001:db8::1\\n\",\n\t\t\tallowLocalhostDNS: true,\n\t\t\tipv6Enabled:       true,\n\t\t\texpected:          \"nameserver 127.0.0.53\\nnameserver 192.88.99.1\\nnameserver ::1\\nnameserver 2001:db8::1\\n\",\n\t\t},\n\t\t{\n\t\t\tname:              \"fallback_none_ipv6_disabled\",\n\t\t\tinput:             \"\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       false,\n\t\t\texpected:          \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\",\n\t\t},\n\t\t{\n\t\t\tname:              \"fallback_none_ipv6_enabled\",\n\t\t\tinput:             \"\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       true,\n\t\t\texpected:          \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\\nnameserver 2001:4860:4860::8888\\nnameserver 2001:4860:4860::8844\",\n\t\t},\n\t\t{\n\t\t\tname:              \"fallback_localhost4_ipv6_disabled\",\n\t\t\tinput:             \"nameserver 127.0.0.53\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       false,\n\t\t\texpected:          \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\",\n\t\t},\n\t\t{\n\t\t\tname:              \"fallback_localhost4_ipv6_enabled\",\n\t\t\tinput:             \"nameserver 127.0.0.53\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       true,\n\t\t\texpected:          \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\\nnameserver 2001:4860:4860::8888\\nnameserver 2001:4860:4860::8844\",\n\t\t},\n\t\t{\n\t\t\tname:              \"fallback_localhost6_ipv6_disabled\",\n\t\t\tinput:             \"nameserver ::1\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       false,\n\t\t\texpected:          \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\",\n\t\t},\n\t\t{\n\t\t\tname:              \"fallback_localhost6_ipv6_enabled\",\n\t\t\tinput:             \"nameserver ::1\",\n\t\t\tallowLocalhostDNS: false,\n\t\t\tipv6Enabled:       true,\n\t\t\texpected:          \"\\nnameserver 8.8.8.8\\nnameserver 8.8.4.4\\nnameserver 2001:4860:4860::8888\\nnameserver 2001:4860:4860::8844\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult, err := FilterResolvDNSWithLocalhostOption([]byte(tc.input), tc.ipv6Enabled, tc.allowLocalhostDNS)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif result == nil {\n\t\t\t\tt.Fatal(\"result is nil\")\n\t\t\t}\n\t\t\tif tc.expected != string(result.Content) {\n\t\t\t\tt.Fatalf(\"expected \\n<%s> got \\n<%s>\", tc.expected, string(result.Content))\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/rootlessutil/child_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage rootlessutil\n\nimport (\n\t\"os\"\n\n\t\"github.com/moby/sys/userns\"\n)\n\nfunc IsRootlessChild() bool {\n\treturn !IsRootlessParent() && userns.RunningInUserNS() && os.Getenv(\"ROOTLESSKIT_STATE_DIR\") != \"\"\n}\n"
  },
  {
    "path": "pkg/rootlessutil/parent_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage rootlessutil\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nfunc IsRootlessParent() bool {\n\treturn os.Geteuid() != 0\n}\n\nfunc RootlessKitStateDir() (string, error) {\n\tif v := os.Getenv(\"ROOTLESSKIT_STATE_DIR\"); v != \"\" {\n\t\treturn v, nil\n\t}\n\txdr, err := XDGRuntimeDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// \"${XDG_RUNTIME_DIR}/containerd-rootless\" is hardcoded in containerd-rootless.sh\n\tstateDir := filepath.Join(xdr, \"containerd-rootless\")\n\tif _, err := os.Stat(stateDir); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn stateDir, nil\n}\n\nfunc RootlessKitChildPid(stateDir string) (int, error) {\n\tpidFilePath := filepath.Join(stateDir, \"child_pid\")\n\tif _, err := os.Stat(pidFilePath); err != nil {\n\t\treturn 0, err\n\t}\n\n\tpidFileBytes, err := filesystem.ReadFile(pidFilePath)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tpidStr := strings.TrimSpace(string(pidFileBytes))\n\treturn strconv.Atoi(pidStr)\n}\n\nfunc ParentMain(hostGatewayIP string) error {\n\tif !IsRootlessParent() {\n\t\treturn errors.New(\"should not be called when !IsRootlessParent()\")\n\t}\n\tstateDir, err := RootlessKitStateDir()\n\tlog.L.Debugf(\"stateDir: %s\", stateDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"rootless containerd not running? (hint: use `containerd-rootless-setuptool.sh install` to start rootless containerd): %w\", err)\n\t}\n\tchildPid, err := RootlessKitChildPid(stateDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdetachedNetNSPath, err := detachedNetNS(stateDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdetachNetNSMode := detachedNetNSPath != \"\"\n\tlog.L.Debugf(\"RootlessKit detach-netns mode: %v\", detachNetNSMode)\n\n\t// FIXME: remove dependency on `nsenter` binary\n\targ0, err := exec.LookPath(\"nsenter\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t// args are compatible with both util-linux nsenter and busybox nsenter\n\targs := []string{\n\t\t\"-r/\", // root dir (busybox nsenter wants this to be explicitly specified),\n\t}\n\n\t// Only append wd if we do have a working dir\n\t// - https://github.com/rootless-containers/usernetes/pull/327\n\t// - https://github.com/containerd/nerdctl/issues/3328\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tlog.L.WithError(err).Warn(\"unable to determine working directory\")\n\t} else {\n\t\targs = append(args, \"-w\"+wd)\n\t\tos.Setenv(\"PWD\", wd)\n\t}\n\n\targs = append(args, \"--preserve-credentials\",\n\t\t\"-m\", \"-U\",\n\t\t\"-t\", strconv.Itoa(childPid),\n\t\t\"-F\", // no fork\n\t)\n\tif !detachNetNSMode {\n\t\targs = append(args, \"-n\")\n\t}\n\targs = append(args, os.Args...)\n\tlog.L.Debugf(\"rootless parent main: executing %q with %v\", arg0, args)\n\n\t// Env vars corresponds to RootlessKit spec:\n\t// https://github.com/rootless-containers/rootlesskit/tree/v0.13.1#environment-variables\n\tos.Setenv(\"ROOTLESSKIT_STATE_DIR\", stateDir)\n\tos.Setenv(\"ROOTLESSKIT_PARENT_EUID\", strconv.Itoa(os.Geteuid()))\n\tos.Setenv(\"ROOTLESSKIT_PARENT_EGID\", strconv.Itoa(os.Getegid()))\n\tos.Setenv(\"NERDCTL_HOST_GATEWAY_IP\", hostGatewayIP)\n\treturn syscall.Exec(arg0, args, os.Environ())\n}\n"
  },
  {
    "path": "pkg/rootlessutil/port_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage rootlessutil\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n\t\"github.com/rootless-containers/rootlesskit/v2/pkg/port\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/go-cni\"\n)\n\nfunc NewRootlessCNIPortManager(client client.Client) (*RootlessCNIPortManager, error) {\n\tif client == nil {\n\t\treturn nil, errdefs.ErrInvalidArgument\n\t}\n\tpm := &RootlessCNIPortManager{\n\t\tClient: client,\n\t}\n\treturn pm, nil\n}\n\ntype RootlessCNIPortManager struct {\n\tclient.Client\n}\n\nfunc (rlcpm *RootlessCNIPortManager) ExposePort(ctx context.Context, cpm cni.PortMapping) error {\n\t// NOTE: When `nerdctl run -p 8080:80` is being launched, cpm.HostPort is set to 8080 and cpm.ContainerPort is set to 80.\n\t// We want to forward the port 8080 of the parent namespace into the port 8080 of the child namespace (which is the \"host\"\n\t// from the point of view of CNI). So we do NOT set sp.ChildPort to cpm.ContainerPort here.\n\tsp := port.Spec{\n\t\tProto:      cpm.Protocol,\n\t\tParentIP:   cpm.HostIP,\n\t\tParentPort: int(cpm.HostPort),\n\t\tChildPort:  int(cpm.HostPort), // NOT typo of cpm.ContainerPort\n\t}\n\t_, err := rlcpm.Client.PortManager().AddPort(ctx, sp)\n\treturn err\n}\n\nfunc (rlcpm *RootlessCNIPortManager) UnexposePort(ctx context.Context, cpm cni.PortMapping) error {\n\tpm := rlcpm.Client.PortManager()\n\tports, err := pm.ListPorts(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tid := -1\n\tfor _, p := range ports {\n\t\tsp := p.Spec\n\t\tif sp.Proto != cpm.Protocol || sp.ParentPort != int(cpm.HostPort) || sp.ChildPort != int(cpm.HostPort) {\n\t\t\tcontinue\n\t\t}\n\t\tspParentIP := net.ParseIP(sp.ParentIP)\n\t\tcpmHostIP := net.ParseIP(cpm.HostIP)\n\t\tif spParentIP == nil || !spParentIP.Equal(cpmHostIP) {\n\t\t\tcontinue\n\t\t}\n\t\tid = p.ID\n\t\tbreak\n\t}\n\tif id < 0 {\n\t\t// no ID found, return nil for idempotency\n\t\treturn nil\n\t}\n\treturn pm.RemovePort(ctx, id)\n}\n"
  },
  {
    "path": "pkg/rootlessutil/rootlessutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage rootlessutil\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"github.com/containernetworking/plugins/pkg/ns\"\n\t\"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n)\n\nfunc IsRootless() bool {\n\treturn IsRootlessParent() || IsRootlessChild()\n}\n\nfunc ParentEUID() int {\n\tif !IsRootlessChild() {\n\t\treturn os.Geteuid()\n\t}\n\tenv := os.Getenv(\"ROOTLESSKIT_PARENT_EUID\")\n\tif env == \"\" {\n\t\tpanic(\"environment variable ROOTLESSKIT_PARENT_EUID is not set\")\n\t}\n\ti, err := strconv.Atoi(env)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"failed to parse ROOTLESSKIT_PARENT_EUID=%q: %w\", env, err))\n\t}\n\treturn i\n}\n\nfunc ParentEGID() int {\n\tif !IsRootlessChild() {\n\t\treturn os.Getegid()\n\t}\n\tenv := os.Getenv(\"ROOTLESSKIT_PARENT_EGID\")\n\tif env == \"\" {\n\t\tpanic(\"environment variable ROOTLESSKIT_PARENT_EGID is not set\")\n\t}\n\ti, err := strconv.Atoi(env)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"failed to parse ROOTLESSKIT_PARENT_EGID=%q: %w\", env, err))\n\t}\n\treturn i\n}\n\nfunc NewRootlessKitClient() (client.Client, error) {\n\tstateDir, err := RootlessKitStateDir()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tapiSock := filepath.Join(stateDir, \"api.sock\")\n\treturn client.New(apiSock)\n}\n\n// RootlessContainredSockAddress returns sock address of rootless containerd based on https://github.com/containerd/nerdctl/blob/main/docs/faq.md#containerd-socket-address\nfunc RootlessContainredSockAddress() (string, error) {\n\tstateDir, err := RootlessKitStateDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tchildPid, err := RootlessKitChildPid(stateDir)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(fmt.Sprintf(\"/proc/%d/root/run/containerd/containerd.sock\", childPid)), nil\n}\n\n// DetachedNetNS returns non-empty netns path if RootlessKit is running with --detach-netns mode.\n// Otherwise returns \"\" without an error.\nfunc DetachedNetNS() (string, error) {\n\tif !IsRootless() {\n\t\treturn \"\", nil\n\t}\n\tstateDir, err := RootlessKitStateDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn detachedNetNS(stateDir)\n}\n\nfunc detachedNetNS(stateDir string) (string, error) {\n\tp := filepath.Join(stateDir, \"netns\")\n\tif _, err := os.Stat(p); err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn \"\", nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn p, nil\n}\n\n// WithDetachedNetNSIfAny executes fn in [DetachedNetNS] if RootlessKit is running with --detach-netns mode.\n// Otherwise it just executes fn in the current netns.\nfunc WithDetachedNetNSIfAny(fn func() error) error {\n\tnetns, err := DetachedNetNS()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif netns == \"\" {\n\t\treturn fn()\n\t}\n\treturn ns.WithNetNSPath(netns, func(_ ns.NetNS) error { return fn() })\n}\n"
  },
  {
    "path": "pkg/rootlessutil/rootlessutil_other.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// On non-Linux platforms, the rootlessutil package always denies\n// rootlessness and errors out, since RootlessKit only works on Linux\n// and none of the Windows containerd runtimes can be considered rootless.\n// https://github.com/containerd/nerdctl/issues/2115\npackage rootlessutil\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/rootless-containers/rootlesskit/v2/pkg/api/client\"\n)\n\n// Always returns false on non-Linux platforms.\nfunc IsRootless() bool {\n\treturn false\n}\n\n// Always returns false on non-Linux platforms.\nfunc IsRootlessChild() bool {\n\treturn false\n}\n\n// Always returns false on non-Linux platforms.\nfunc IsRootlessParent() bool {\n\treturn false\n}\n\n// Always errors out on non-Linux platforms.\nfunc XDGRuntimeDir() (string, error) {\n\treturn \"\", fmt.Errorf(\"can only query XDG env vars on Linux\")\n}\n\n// Always returns -1 on non-Linux platforms.\nfunc ParentEUID() int {\n\treturn -1\n}\n\n// Always errors out on non-Linux platforms.\nfunc NewRootlessKitClient() (client.Client, error) {\n\treturn nil, fmt.Errorf(\"cannot instantiate RootlessKit client on non-Linux hosts\")\n}\n\n// Always errors out on non-Linux platforms.\nfunc ParentMain(hostGatewayIP string) error {\n\treturn fmt.Errorf(\"cannot use RootlessKit on main entry point on non-Linux hosts\")\n}\n\nfunc RootlessContainredSockAddress() (string, error) {\n\treturn \"\", fmt.Errorf(\"cannot inspect RootlessKit state on non-Linux hosts\")\n}\n\nfunc DetachedNetNS() (string, error) {\n\treturn \"\", nil\n}\n\nfunc WithDetachedNetNSIfAny(fn func() error) error {\n\treturn fn()\n}\n"
  },
  {
    "path": "pkg/rootlessutil/xdg_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage rootlessutil\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n)\n\nfunc XDGRuntimeDir() (string, error) {\n\tif xrd := os.Getenv(\"XDG_RUNTIME_DIR\"); xrd != \"\" {\n\t\treturn xrd, nil\n\t}\n\t// Fall back to \"/run/user/<euid>\".\n\t// Note that We cannot rely on os.Geteuid() because we might be inside UserNS.\n\tif parentEuid, ok := os.LookupEnv(\"ROOTLESSKIT_PARENT_EUID\"); ok {\n\t\tif _, err := strconv.Atoi(parentEuid); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"invalid ROOTLESSKIT_PARENT_EUID environment variable value %q: %w\", parentEuid, err)\n\t\t}\n\t\treturn \"/run/user/\" + parentEuid, nil\n\t}\n\treturn \"\", errors.New(\"environment variable XDG_RUNTIME_DIR is not set, see https://rootlesscontaine.rs/getting-started/common/login/\")\n}\n\nfunc XDGConfigHome() (string, error) {\n\tif xch := os.Getenv(\"XDG_CONFIG_HOME\"); xch != \"\" {\n\t\treturn xch, nil\n\t}\n\t// Fall back to \"~/.config\"\n\t// Note that We cannot rely on user.Current().HomeDir because we might be inside UserNS.\n\thome := os.Getenv(\"HOME\")\n\tif home == \"\" {\n\t\treturn \"\", errors.New(\"environment variable HOME is not set\")\n\t}\n\treturn filepath.Join(home, \".config\"), nil\n}\n\nfunc XDGDataHome() (string, error) {\n\tif xdh := os.Getenv(\"XDG_DATA_HOME\"); xdh != \"\" {\n\t\treturn xdh, nil\n\t}\n\t// Fall back to \"~/.local/share\"\n\t// Note that We cannot rely on user.Current().HomeDir because we might be inside UserNS.\n\thome := os.Getenv(\"HOME\")\n\tif home == \"\" {\n\t\treturn \"\", errors.New(\"environment variable HOME is not set\")\n\t}\n\treturn filepath.Join(home, \".local/share\"), nil\n}\n"
  },
  {
    "path": "pkg/signalutil/signals.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage signalutil\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n)\n\n// killer is from https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/signals.go#L30-L32\ntype killer interface {\n\tKill(context.Context, syscall.Signal, ...containerd.KillOpts) error\n}\n\n// ForwardAllSignals forwards signals.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/signals.go#L34-L55\nfunc ForwardAllSignals(ctx context.Context, task killer) chan os.Signal {\n\tsigc := make(chan os.Signal, 128)\n\tsignal.Notify(sigc)\n\tgo func() {\n\t\tfor s := range sigc {\n\t\t\tif canIgnoreSignal(s) {\n\t\t\t\tlog.G(ctx).Debugf(\"Ignoring signal %s\", s)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlog.G(ctx).Debug(\"forwarding signal \", s)\n\t\t\tif err := task.Kill(ctx, s.(syscall.Signal)); err != nil {\n\t\t\t\tif errdefs.IsNotFound(err) {\n\t\t\t\t\tlog.G(ctx).WithError(err).Debugf(\"Not forwarding signal %s\", s)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlog.G(ctx).WithError(err).Errorf(\"forward signal %s\", s)\n\t\t\t}\n\t\t}\n\t}()\n\treturn sigc\n}\n\n// StopCatch stops and closes a channel.\n// From https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/signals.go#L57-L61\nfunc StopCatch(sigc chan os.Signal) {\n\tsignal.Stop(sigc)\n\tclose(sigc)\n}\n"
  },
  {
    "path": "pkg/signalutil/signals_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage signalutil\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// canIgnoreSignal is from https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/signals_linux.go#L25-L27\nfunc canIgnoreSignal(s os.Signal) bool {\n\treturn s == unix.SIGURG\n}\n"
  },
  {
    "path": "pkg/signalutil/signals_other.go",
    "content": "//go:build !linux\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage signalutil\n\nimport \"os\"\n\n// canIgnoreSignal is from https://github.com/containerd/containerd/blob/v1.7.0-rc.2/cmd/ctr/commands/signals_notlinux.go#L23-L25\nfunc canIgnoreSignal(_ os.Signal) bool {\n\treturn false\n}\n"
  },
  {
    "path": "pkg/signutil/cosignutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage signutil\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\n// SignCosign signs an image(`rawRef`) using a cosign private key (`keyRef`)\nfunc SignCosign(rawRef string, keyRef string) error {\n\tcosignExecutable, err := exec.LookPath(\"cosign\")\n\tif err != nil {\n\t\tlog.L.WithError(err).Error(\"cosign executable not found in path $PATH\")\n\t\tlog.L.Info(\"you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation\")\n\t\treturn err\n\t}\n\n\tcosignCmd := exec.Command(cosignExecutable, []string{\"sign\"}...)\n\tcosignCmd.Env = os.Environ()\n\n\t// if key is empty, use keyless mode(experimental)\n\tif keyRef != \"\" {\n\t\tcosignCmd.Args = append(cosignCmd.Args, \"--key\", keyRef)\n\t} else {\n\t\tcosignCmd.Env = append(cosignCmd.Env, \"COSIGN_EXPERIMENTAL=true\")\n\t}\n\n\tcosignCmd.Args = append(cosignCmd.Args, \"--yes\")\n\tcosignCmd.Args = append(cosignCmd.Args, rawRef)\n\n\tlog.L.Debugf(\"running %s %v\", cosignExecutable, cosignCmd.Args)\n\n\terr = processCosignIO(cosignCmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn cosignCmd.Wait()\n}\n\n// VerifyCosign verifies an image(`rawRef`) with a cosign public key(`keyRef`)\n// `hostsDirs` are used to resolve image `rawRef`\n// Either --cosign-certificate-identity or --cosign-certificate-identity-regexp and either --cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp must be set for keyless flows.\nfunc VerifyCosign(ctx context.Context, rawRef string, keyRef string, hostsDirs []string,\n\tcertIdentity string, certIdentityRegexp string, certOidcIssuer string, certOidcIssuerRegexp string) (string, error) {\n\tdigest, err := imgutil.ResolveDigest(ctx, rawRef, false, hostsDirs)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Errorf(\"unable to resolve digest for an image %s: %v\", rawRef, err)\n\t\treturn rawRef, err\n\t}\n\tref := rawRef\n\tif !strings.Contains(ref, \"@\") {\n\t\tref += \"@\" + digest\n\t}\n\n\tlog.G(ctx).Debugf(\"verifying image: %s\", ref)\n\n\tcosignExecutable, err := exec.LookPath(\"cosign\")\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Error(\"cosign executable not found in path $PATH\")\n\t\tlog.G(ctx).Info(\"you might consider installing cosign from: https://docs.sigstore.dev/cosign/installation\")\n\t\treturn ref, err\n\t}\n\n\tcosignCmd := exec.Command(cosignExecutable, []string{\"verify\"}...)\n\tcosignCmd.Env = os.Environ()\n\n\t// if key is empty, use keyless mode(experimental)\n\tif keyRef != \"\" {\n\t\tcosignCmd.Args = append(cosignCmd.Args, \"--key\", keyRef)\n\t} else {\n\t\tif certIdentity == \"\" && certIdentityRegexp == \"\" {\n\t\t\treturn ref, errors.New(\"--cosign-certificate-identity or --cosign-certificate-identity-regexp is required for Cosign verification in keyless mode\")\n\t\t}\n\t\tif certIdentity != \"\" {\n\t\t\tcosignCmd.Args = append(cosignCmd.Args, \"--certificate-identity\", certIdentity)\n\t\t}\n\t\tif certIdentityRegexp != \"\" {\n\t\t\tcosignCmd.Args = append(cosignCmd.Args, \"--certificate-identity-regexp\", certIdentityRegexp)\n\t\t}\n\t\tif certOidcIssuer == \"\" && certOidcIssuerRegexp == \"\" {\n\t\t\treturn ref, errors.New(\"--cosign-certificate-oidc-issuer or --cosign-certificate-oidc-issuer-regexp is required for Cosign verification in keyless mode\")\n\t\t}\n\t\tif certOidcIssuer != \"\" {\n\t\t\tcosignCmd.Args = append(cosignCmd.Args, \"--certificate-oidc-issuer\", certOidcIssuer)\n\t\t}\n\t\tif certOidcIssuerRegexp != \"\" {\n\t\t\tcosignCmd.Args = append(cosignCmd.Args, \"--certificate-oidc-issuer-regexp\", certOidcIssuerRegexp)\n\t\t}\n\t\tcosignCmd.Env = append(cosignCmd.Env, \"COSIGN_EXPERIMENTAL=true\")\n\t}\n\n\tcosignCmd.Args = append(cosignCmd.Args, ref)\n\n\tlog.G(ctx).Debugf(\"running %s %v\", cosignExecutable, cosignCmd.Args)\n\n\terr = processCosignIO(cosignCmd)\n\tif err != nil {\n\t\treturn ref, err\n\t}\n\tif err := cosignCmd.Wait(); err != nil {\n\t\treturn ref, err\n\t}\n\n\treturn ref, nil\n}\n\nfunc processCosignIO(cosignCmd *exec.Cmd) error {\n\tstdout, err := cosignCmd.StdoutPipe()\n\tif err != nil {\n\t\tlog.L.Warn(\"cosign: \" + err.Error())\n\t}\n\tstderr, err := cosignCmd.StderrPipe()\n\tif err != nil {\n\t\tlog.L.Warn(\"cosign: \" + err.Error())\n\t}\n\tif err := cosignCmd.Start(); err != nil {\n\t\t// only return err if it's critical (cosign start failed.)\n\t\treturn err\n\t}\n\n\tscanner := bufio.NewScanner(stdout)\n\tfor scanner.Scan() {\n\t\tlog.L.Info(\"cosign: \" + scanner.Text())\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\tlog.L.Warn(\"cosign: \" + err.Error())\n\t}\n\n\terrScanner := bufio.NewScanner(stderr)\n\tfor errScanner.Scan() {\n\t\tlog.L.Info(\"cosign: \" + errScanner.Text())\n\t}\n\tif err := errScanner.Err(); err != nil {\n\t\tlog.L.Warn(\"cosign: \" + err.Error())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/signutil/notationutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage signutil\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/imgutil\"\n)\n\n// SignNotation signs an image(`rawRef`) using a notation key name (`keyNameRef`)\nfunc SignNotation(rawRef string, keyNameRef string) error {\n\tnotationExecutable, err := exec.LookPath(\"notation\")\n\tif err != nil {\n\t\tlog.L.WithError(err).Error(\"notation executable not found in path $PATH\")\n\t\tlog.L.Info(\"you might consider installing notation from: https://notaryproject.dev/docs/installation/cli/\")\n\t\treturn err\n\t}\n\n\tnotationCmd := exec.Command(notationExecutable, []string{\"sign\"}...)\n\tnotationCmd.Env = os.Environ()\n\n\t// If keyNameRef is empty, don't append --key to notation command. This will cause using the notation default key.\n\tif keyNameRef != \"\" {\n\t\tnotationCmd.Args = append(notationCmd.Args, \"--key\", keyNameRef)\n\t}\n\n\tnotationCmd.Args = append(notationCmd.Args, rawRef)\n\n\tlog.L.Debugf(\"running %s %v\", notationExecutable, notationCmd.Args)\n\n\terr = processNotationIO(notationCmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn notationCmd.Wait()\n}\n\n// VerifyNotation verifies an image(`rawRef`) with the pre-configured notation trust policy\n// `hostsDirs` are used to resolve image `rawRef`\nfunc VerifyNotation(ctx context.Context, rawRef string, hostsDirs []string) (string, error) {\n\tdigest, err := imgutil.ResolveDigest(ctx, rawRef, false, hostsDirs)\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Errorf(\"unable to resolve digest for an image %s: %v\", rawRef, err)\n\t\treturn rawRef, err\n\t}\n\tref := rawRef\n\tif !strings.Contains(ref, \"@\") {\n\t\tref += \"@\" + digest\n\t}\n\n\tlog.G(ctx).Debugf(\"verifying image: %s\", ref)\n\n\tnotationExecutable, err := exec.LookPath(\"notation\")\n\tif err != nil {\n\t\tlog.G(ctx).WithError(err).Error(\"notation executable not found in path $PATH\")\n\t\tlog.G(ctx).Info(\"you might consider installing notation from: https://notaryproject.dev/docs/installation/cli/\")\n\t\treturn ref, err\n\t}\n\n\tnotationCmd := exec.Command(notationExecutable, []string{\"verify\"}...)\n\tnotationCmd.Env = os.Environ()\n\n\tnotationCmd.Args = append(notationCmd.Args, ref)\n\n\tlog.G(ctx).Debugf(\"running %s %v\", notationExecutable, notationCmd.Args)\n\n\terr = processNotationIO(notationCmd)\n\tif err != nil {\n\t\treturn ref, err\n\t}\n\tif err := notationCmd.Wait(); err != nil {\n\t\treturn ref, err\n\t}\n\n\treturn ref, nil\n}\n\nfunc processNotationIO(notationCmd *exec.Cmd) error {\n\tstdout, err := notationCmd.StdoutPipe()\n\tif err != nil {\n\t\tlog.L.Warn(\"notation: \" + err.Error())\n\t}\n\tstderr, err := notationCmd.StderrPipe()\n\tif err != nil {\n\t\tlog.L.Warn(\"notation: \" + err.Error())\n\t}\n\tif err := notationCmd.Start(); err != nil {\n\t\t// only return err if it's critical (notation start failed.)\n\t\treturn err\n\t}\n\n\tscanner := bufio.NewScanner(stdout)\n\tfor scanner.Scan() {\n\t\tlog.L.Info(\"notation: \" + scanner.Text())\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\tlog.L.Warn(\"notation: \" + err.Error())\n\t}\n\n\terrScanner := bufio.NewScanner(stderr)\n\tfor errScanner.Scan() {\n\t\tlog.L.Info(\"notation: \" + errScanner.Text())\n\t}\n\tif err := errScanner.Err(); err != nil {\n\t\tlog.L.Warn(\"notation: \" + err.Error())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/signutil/signutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage signutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\n// Sign signs an image using a signer and options provided in options.\nfunc Sign(rawRef string, experimental bool, options types.ImageSignOptions) error {\n\tswitch options.Provider {\n\tcase \"cosign\":\n\t\tif !experimental {\n\t\t\treturn fmt.Errorf(\"cosign only work with enable experimental feature\")\n\t\t}\n\n\t\tif err := SignCosign(rawRef, options.CosignKey); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase \"notation\":\n\t\tif !experimental {\n\t\t\treturn fmt.Errorf(\"notation only work with enable experimental feature\")\n\t\t}\n\n\t\tif err := SignNotation(rawRef, options.NotationKeyName); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase \"\", \"none\":\n\t\tlog.L.Debugf(\"signing process skipped\")\n\tdefault:\n\t\treturn fmt.Errorf(\"no signers found: %s\", options.Provider)\n\t}\n\treturn nil\n}\n\n// Verify verifies an image using a verifier and options provided in options.\nfunc Verify(ctx context.Context, rawRef string, hostsDirs []string, experimental bool, options types.ImageVerifyOptions) (ref string, err error) {\n\tswitch options.Provider {\n\tcase \"cosign\":\n\t\tif !experimental {\n\t\t\treturn \"\", fmt.Errorf(\"cosign only work with enable experimental feature\")\n\t\t}\n\n\t\tif ref, err = VerifyCosign(ctx, rawRef, options.CosignKey, hostsDirs, options.CosignCertificateIdentity, options.CosignCertificateIdentityRegexp, options.CosignCertificateOidcIssuer, options.CosignCertificateOidcIssuerRegexp); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\tcase \"notation\":\n\t\tif !experimental {\n\t\t\treturn \"\", fmt.Errorf(\"notation only work with enable experimental feature\")\n\t\t}\n\n\t\tif ref, err = VerifyNotation(ctx, rawRef, hostsDirs); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\tcase \"\", \"none\":\n\t\tref = rawRef\n\t\tlog.G(ctx).Debugf(\"verifying process skipped\")\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"no verifiers found: %s\", options.Provider)\n\t}\n\treturn ref, nil\n}\n"
  },
  {
    "path": "pkg/snapshotterutil/socisource.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Copyright The Soci Snapshotter Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Taken from https://github.com/awslabs/soci-snapshotter/blob/237fc956b8366e49927c84fcfee9a2defbb8f53c/fs/source/source.go\n// to avoid taking dependency, as maintainers do not wish to upgrade to containerd v2 yet.\n\npackage snapshotterutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/pkg/labels\"\n\tctdsnapshotters \"github.com/containerd/containerd/v2/pkg/snapshotters\"\n)\n\nconst (\n\t// TargetSizeLabel is a label which contains layer size.\n\tTargetSizeLabel = \"containerd.io/snapshot/remote/soci.size\"\n\n\t// targetImageLayersSizeLabel is a label which contains layer sizes contained in\n\t// the target image.\n\ttargetImageLayersSizeLabel = \"containerd.io/snapshot/remote/image.layers.size\"\n\n\t// TargetSociIndexDigestLabel is a label which contains the digest of the soci index.\n\tTargetSociIndexDigestLabel = \"containerd.io/snapshot/remote/soci.index.digest\"\n)\n\n// SociAppendDefaultLabelsHandlerWrapper makes a handler which appends image's basic\n// information to each layer descriptor as annotations during unpack. These\n// annotations will be passed to this remote snapshotter as labels and used to\n// construct source information.\nfunc SociAppendDefaultLabelsHandlerWrapper(indexDigest string, wrapper func(images.Handler) images.Handler) func(f images.Handler) images.Handler {\n\treturn func(f images.Handler) images.Handler {\n\t\treturn images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {\n\t\t\tchildren, err := wrapper(f).Handle(ctx, desc)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tswitch desc.MediaType {\n\t\t\tcase ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:\n\t\t\t\tfor i := range children {\n\t\t\t\t\tc := &children[i]\n\t\t\t\t\tif images.IsLayerType(c.MediaType) {\n\t\t\t\t\t\tif c.Annotations == nil {\n\t\t\t\t\t\t\tc.Annotations = make(map[string]string)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tc.Annotations[TargetSizeLabel] = fmt.Sprintf(\"%d\", c.Size)\n\t\t\t\t\t\tc.Annotations[TargetSociIndexDigestLabel] = indexDigest\n\n\t\t\t\t\t\tremainingLayerDigestsCount := len(strings.Split(c.Annotations[ctdsnapshotters.TargetImageLayersLabel], \",\"))\n\n\t\t\t\t\t\tvar layerSizes string\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t\tWe must ensure that the counts of layer sizes and layer digests are equal.\n\t\t\t\t\t\t\tWe will limit the # of neighboring label sizes to equal the # of neighboring\n\t\t\t\t\t\t\tlayer digests for any given layer.\n\t\t\t\t\t\t*/\n\t\t\t\t\t\tfor _, l := range children[i : i+remainingLayerDigestsCount] {\n\t\t\t\t\t\t\tif images.IsLayerType(l.MediaType) {\n\t\t\t\t\t\t\t\tls := fmt.Sprintf(\"%d,\", l.Size)\n\t\t\t\t\t\t\t\t// This avoids the label hits the size limitation.\n\t\t\t\t\t\t\t\t// Skipping layers is allowed here and only affects performance.\n\t\t\t\t\t\t\t\tif err := labels.Validate(targetImageLayersSizeLabel, layerSizes+ls); err != nil {\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlayerSizes += ls\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tc.Annotations[targetImageLayersSizeLabel] = strings.TrimSuffix(layerSizes, \",\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn children, nil\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/snapshotterutil/sociutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage snapshotterutil\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\n\t\"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/api/types\"\n)\n\n// setupSociCommand creates and sets up a SOCI command with common configuration\nfunc setupSociCommand(gOpts types.GlobalCommandOptions) (*exec.Cmd, error) {\n\tsociExecutable, err := exec.LookPath(\"soci\")\n\tif err != nil {\n\t\tlog.L.WithError(err).Error(\"soci executable not found in path $PATH\")\n\t\tlog.L.Info(\"you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md\")\n\t\treturn nil, err\n\t}\n\n\tsociCmd := exec.Command(sociExecutable)\n\tsociCmd.Env = os.Environ()\n\n\t// #region for global flags.\n\tif gOpts.Address != \"\" {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--address\", gOpts.Address)\n\t}\n\tif gOpts.Namespace != \"\" {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--namespace\", gOpts.Namespace)\n\t}\n\n\treturn sociCmd, nil\n}\n\n// CheckSociVersion checks if the SOCI binary version is at least the required version\nfunc CheckSociVersion(requiredVersion string) error {\n\tsociExecutable, err := exec.LookPath(\"soci\")\n\tif err != nil {\n\t\tlog.L.WithError(err).Error(\"soci executable not found in path $PATH\")\n\t\tlog.L.Info(\"you might consider installing soci from: https://github.com/awslabs/soci-snapshotter/blob/main/docs/install.md\")\n\t\treturn err\n\t}\n\n\tcmd := exec.Command(sociExecutable, \"--version\")\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get SOCI version: %w\", err)\n\t}\n\n\t// Parse the version string\n\tversionStr := string(output)\n\t// Handle format like \"soci version v0.10.0 8bbfe951bbb411798ee85dbd908544df4a1619a8.m\"\n\tre := regexp.MustCompile(`v?(\\d+\\.\\d+\\.\\d+)`)\n\tmatches := re.FindStringSubmatch(versionStr)\n\tif len(matches) < 2 {\n\t\treturn fmt.Errorf(\"failed to parse SOCI version from output: %s\", versionStr)\n\t}\n\n\t// Extract version number\n\tinstalledVersionStr := matches[1]\n\n\t// Parse versions using semver package\n\tinstalledVersion, err := semver.NewVersion(installedVersionStr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse installed SOCI version: %w\", err)\n\t}\n\n\treqVersion, err := semver.NewVersion(requiredVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse required SOCI version: %w\", err)\n\t}\n\n\t// Compare versions\n\tif installedVersion.LessThan(reqVersion) {\n\t\treturn fmt.Errorf(\"SOCI version %s is lower than the required version %s for the convert operation\", installedVersion.String(), reqVersion.String())\n\t}\n\n\treturn nil\n}\n\n// ConvertSociIndexV2 converts an image to SOCI format and returns the converted image reference with digest\nfunc ConvertSociIndexV2(ctx context.Context, client *client.Client, srcRef string, destRef string, gOpts types.GlobalCommandOptions, sOpts types.SociOptions) (string, error) {\n\t// Check if SOCI version is at least 0.10.0 which is required for the convert operation\n\tif err := CheckSociVersion(\"0.10.0\"); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsociCmd, err := setupSociCommand(gOpts)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsociCmd.Args = append(sociCmd.Args, \"convert\")\n\n\tif sOpts.AllPlatforms {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--all-platforms\")\n\t} else if len(sOpts.Platforms) > 0 {\n\t\t// multiple values need to be passed as separate, repeating flags in soci as it uses urfave\n\t\t// https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag\n\t\tfor _, p := range sOpts.Platforms {\n\t\t\tsociCmd.Args = append(sociCmd.Args, \"--platform\", p)\n\t\t}\n\t}\n\n\tif sOpts.SpanSize != -1 {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--span-size\", strconv.FormatInt(sOpts.SpanSize, 10))\n\t}\n\n\tif sOpts.MinLayerSize != -1 {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--min-layer-size\", strconv.FormatInt(sOpts.MinLayerSize, 10))\n\t}\n\n\tsociCmd.Args = append(sociCmd.Args, srcRef, destRef)\n\n\tlog.L.Infof(\"Converting image from %s to %s using SOCI format\", srcRef, destRef)\n\n\terr = processSociIO(sociCmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\terr = sociCmd.Wait()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Get the converted image's digest\n\timg, err := client.GetImage(ctx, destRef)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get converted image: %w\", err)\n\t}\n\n\t// Return the full reference with digest\n\treturn fmt.Sprintf(\"%s@%s\", destRef, img.Target().Digest), nil\n}\n\n// CreateSociIndexV1 creates a SOCI index(`rawRef`)\nfunc CreateSociIndexV1(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string, sOpts types.SociOptions) error {\n\tsociCmd, err := setupSociCommand(gOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Global flags have to be put before subcommand before soci upgrades to urfave v3.\n\t// https://github.com/urfave/cli/issues/1113\n\tsociCmd.Args = append(sociCmd.Args, \"create\")\n\n\tif allPlatform {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--all-platforms\")\n\t}\n\tif len(platforms) > 0 {\n\t\t// multiple values need to be passed as separate, repeating flags in soci as it uses urfave\n\t\t// https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag\n\t\tfor _, p := range platforms {\n\t\t\tsociCmd.Args = append(sociCmd.Args, \"--platform\", p)\n\t\t}\n\t}\n\n\tif sOpts.SpanSize != -1 {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--span-size\", strconv.FormatInt(sOpts.SpanSize, 10))\n\t}\n\tif sOpts.MinLayerSize != -1 {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--min-layer-size\", strconv.FormatInt(sOpts.MinLayerSize, 10))\n\t}\n\t// --timeout, --debug, --content-store\n\tsociCmd.Args = append(sociCmd.Args, rawRef)\n\n\tlog.L.Debugf(\"running soci %v\", sociCmd.Args)\n\n\terr = processSociIO(sociCmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn sociCmd.Wait()\n}\n\n// PushSoci pushes a SOCI index(`rawRef`)\n// `hostsDirs` are used to resolve image `rawRef`\nfunc PushSoci(rawRef string, gOpts types.GlobalCommandOptions, allPlatform bool, platforms []string) error {\n\tlog.L.Debugf(\"pushing SOCI index: %s\", rawRef)\n\n\tsociCmd, err := setupSociCommand(gOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Global flags have to be put before subcommand before soci upgrades to urfave v3.\n\t// https://github.com/urfave/cli/issues/1113\n\tsociCmd.Args = append(sociCmd.Args, \"push\")\n\n\tif allPlatform {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--all-platforms\")\n\t}\n\tif len(platforms) > 0 {\n\t\t// multiple values need to be passed as separate, repeating flags in soci as it uses urfave\n\t\t// https://github.com/urfave/cli/blob/main/docs/v2/examples/flags.md#multiple-values-per-single-flag\n\t\tfor _, p := range platforms {\n\t\t\tsociCmd.Args = append(sociCmd.Args, \"--platform\", p)\n\t\t}\n\t}\n\tif gOpts.InsecureRegistry {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--skip-verify\")\n\t\tsociCmd.Args = append(sociCmd.Args, \"--plain-http\")\n\t}\n\tif len(gOpts.HostsDir) > 0 {\n\t\tsociCmd.Args = append(sociCmd.Args, \"--hosts-dir\")\n\t\tsociCmd.Args = append(sociCmd.Args, strings.Join(gOpts.HostsDir, \",\"))\n\t}\n\tsociCmd.Args = append(sociCmd.Args, rawRef)\n\n\tlog.L.Debugf(\"running soci %v\", sociCmd.Args)\n\n\terr = processSociIO(sociCmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn sociCmd.Wait()\n}\n\nfunc processSociIO(sociCmd *exec.Cmd) error {\n\tstdout, err := sociCmd.StdoutPipe()\n\tif err != nil {\n\t\tlog.L.Warn(\"soci: \" + err.Error())\n\t}\n\tstderr, err := sociCmd.StderrPipe()\n\tif err != nil {\n\t\tlog.L.Warn(\"soci: \" + err.Error())\n\t}\n\tif err := sociCmd.Start(); err != nil {\n\t\t// only return err if it's critical (soci command failed to start.)\n\t\treturn err\n\t}\n\n\tscanner := bufio.NewScanner(stdout)\n\tfor scanner.Scan() {\n\t\tlog.L.Info(\"soci: \" + scanner.Text())\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\tlog.L.Warn(\"soci: \" + err.Error())\n\t}\n\n\terrScanner := bufio.NewScanner(stderr)\n\tfor errScanner.Scan() {\n\t\tlog.L.Info(\"soci: \" + errScanner.Text())\n\t}\n\tif err := errScanner.Err(); err != nil {\n\t\tlog.L.Warn(\"soci: \" + err.Error())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/statsutil/stats.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage statsutil\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tunits \"github.com/docker/go-units\"\n)\n\ntype SystemInfo struct {\n\tOnlineCPUs  uint32\n\tSystemUsage uint64\n}\n\n// StatsEntry represents the statistics data collected from a container\ntype StatsEntry struct {\n\tName             string\n\tID               string\n\tCPUPercentage    float64\n\tMemory           float64\n\tMemoryLimit      float64\n\tMemoryPercentage float64\n\tNetworkRx        float64\n\tNetworkTx        float64\n\tBlockRead        float64\n\tBlockWrite       float64\n\tPidsCurrent      uint64\n\tIsInvalid        bool\n}\n\n// FormattedStatsEntry represents a formatted StatsEntry\ntype FormattedStatsEntry struct {\n\tName     string\n\tID       string\n\tCPUPerc  string\n\tMemUsage string\n\tMemPerc  string\n\tNetIO    string\n\tBlockIO  string\n\tPIDs     string\n}\n\n// Stats represents an entity to store containers statistics synchronously\ntype Stats struct {\n\tmutex sync.RWMutex\n\tStatsEntry\n\terr error\n}\n\n// ContainerStats represents the runtime container stats\ntype ContainerStats struct {\n\tTime                        time.Time\n\tCgroupCPU, Cgroup2CPU       uint64\n\tCgroupSystem, Cgroup2System uint64\n}\n\n// NewStats is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/formatter_stats.go#L113-L116\nfunc NewStats(containerID string, containerName string) *Stats {\n\treturn &Stats{StatsEntry: StatsEntry{ID: containerID, Name: containerName}}\n}\n\n// SetStatistics is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/formatter_stats.go#L87-L93\nfunc (cs *Stats) SetStatistics(s StatsEntry) {\n\tcs.mutex.Lock()\n\tdefer cs.mutex.Unlock()\n\t// The statsEntry ID and Name fields are already populated within the cs.StatsEntry\n\tcStatsName := cs.StatsEntry.Name\n\tcStatsID := cs.StatsEntry.ID\n\tcs.StatsEntry = s\n\tcs.StatsEntry.Name = cStatsName\n\tcs.StatsEntry.ID = cStatsID\n}\n\n// GetStatistics is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/formatter_stats.go#L95-L100\nfunc (cs *Stats) GetStatistics() StatsEntry {\n\tcs.mutex.Lock()\n\tdefer cs.mutex.Unlock()\n\treturn cs.StatsEntry\n}\n\n// GetError is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/formatter_stats.go#L51-L57\nfunc (cs *Stats) GetError() error {\n\tcs.mutex.Lock()\n\tdefer cs.mutex.Unlock()\n\treturn cs.err\n}\n\n// SetErrorAndReset is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/formatter_stats.go#L59-L75\nfunc (cs *Stats) SetErrorAndReset(err error) {\n\tcs.mutex.Lock()\n\tdefer cs.mutex.Unlock()\n\tcs.CPUPercentage = 0\n\tcs.Memory = 0\n\tcs.MemoryPercentage = 0\n\tcs.MemoryLimit = 0\n\tcs.NetworkRx = 0\n\tcs.NetworkTx = 0\n\tcs.BlockRead = 0\n\tcs.BlockWrite = 0\n\tcs.PidsCurrent = 0\n\tcs.err = err\n\tcs.IsInvalid = true\n}\n\n// SetError is from https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/command/container/formatter_stats.go#L77-L85\nfunc (cs *Stats) SetError(err error) {\n\tcs.mutex.Lock()\n\tdefer cs.mutex.Unlock()\n\tcs.err = err\n\tif err != nil {\n\t\tcs.IsInvalid = true\n\t}\n}\n\n// Rendering a FormattedStatsEntry from StatsEntry\nfunc RenderEntry(in *StatsEntry, noTrunc bool) FormattedStatsEntry {\n\treturn FormattedStatsEntry{\n\t\tName:     in.EntryName(noTrunc),\n\t\tID:       in.EntryID(noTrunc),\n\t\tCPUPerc:  in.CPUPerc(),\n\t\tMemUsage: in.MemUsage(),\n\t\tMemPerc:  in.MemPerc(),\n\t\tNetIO:    in.NetIO(),\n\t\tBlockIO:  in.BlockIO(),\n\t\tPIDs:     in.PIDs(),\n\t}\n}\n\n/*\na set of functions to format container stats\n*/\nfunc (s *StatsEntry) EntryName(noTrunc bool) string {\n\tif len(s.Name) > 1 {\n\t\tif !noTrunc {\n\t\t\tvar truncLen int\n\t\t\tif strings.HasPrefix(s.Name, \"k8s://\") {\n\t\t\t\ttruncLen = 24\n\t\t\t} else {\n\t\t\t\ttruncLen = 12\n\t\t\t}\n\t\t\tif len(s.Name) > truncLen {\n\t\t\t\treturn s.Name[:truncLen]\n\t\t\t}\n\t\t}\n\t\treturn s.Name\n\t}\n\treturn \"--\"\n}\n\nfunc (s *StatsEntry) EntryID(noTrunc bool) string {\n\tif !noTrunc {\n\t\tif len(s.ID) > 12 {\n\t\t\treturn s.ID[:12]\n\t\t}\n\t}\n\treturn s.ID\n}\n\nfunc (s *StatsEntry) CPUPerc() string {\n\tif s.IsInvalid {\n\t\treturn \"--\"\n\t}\n\treturn fmt.Sprintf(\"%.2f%%\", s.CPUPercentage)\n}\n\nfunc (s *StatsEntry) MemUsage() string {\n\tif s.IsInvalid {\n\t\treturn \"-- / --\"\n\t}\n\treturn fmt.Sprintf(\"%s / %s\", units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit))\n}\n\nfunc (s *StatsEntry) MemPerc() string {\n\tif s.IsInvalid {\n\t\treturn \"--\"\n\t}\n\treturn fmt.Sprintf(\"%.2f%%\", s.MemoryPercentage)\n}\n\nfunc (s *StatsEntry) NetIO() string {\n\tif s.IsInvalid {\n\t\treturn \"--\"\n\t}\n\treturn fmt.Sprintf(\"%s / %s\", units.HumanSizeWithPrecision(s.NetworkRx, 3), units.HumanSizeWithPrecision(s.NetworkTx, 3))\n}\n\nfunc (s *StatsEntry) BlockIO() string {\n\tif s.IsInvalid {\n\t\treturn \"--\"\n\t}\n\treturn fmt.Sprintf(\"%s / %s\", units.HumanSizeWithPrecision(s.BlockRead, 3), units.HumanSizeWithPrecision(s.BlockWrite, 3))\n}\n\nfunc (s *StatsEntry) PIDs() string {\n\tif s.IsInvalid {\n\t\treturn \"--\"\n\t}\n\treturn strconv.FormatUint(s.PidsCurrent, 10)\n}\n"
  },
  {
    "path": "pkg/statsutil/stats_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage statsutil\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/vishvananda/netlink\"\n\n\tv1 \"github.com/containerd/cgroups/v3/cgroup1/stats\"\n\tv2 \"github.com/containerd/cgroups/v3/cgroup2/stats\"\n)\n\nfunc calculateMemPercent(limit float64, usedNo float64) float64 {\n\t// Limit will never be 0 unless the container is not running and we haven't\n\t// got any data from cgroup\n\tif limit != 0 {\n\t\treturn usedNo / limit * 100.0\n\t}\n\treturn 0\n}\n\nfunc SetCgroupStatsFields(previousStats *ContainerStats, data *v1.Metrics, links []netlink.Link, systemInfo SystemInfo) (StatsEntry, error) {\n\tcpuPercent := calculateCgroupCPUPercent(previousStats, data, systemInfo)\n\tblkRead, blkWrite := calculateCgroupBlockIO(data)\n\tmem := calculateCgroupMemUsage(data)\n\tmemLimit := getCgroupMemLimit(float64(data.Memory.Usage.Limit))\n\tmemPercent := calculateMemPercent(memLimit, mem)\n\tpidsStatsCurrent := data.Pids.Current\n\tnetRx, netTx := calculateCgroupNetwork(links)\n\n\treturn StatsEntry{\n\t\tCPUPercentage:    cpuPercent,\n\t\tMemory:           mem,\n\t\tMemoryPercentage: memPercent,\n\t\tMemoryLimit:      memLimit,\n\t\tNetworkRx:        netRx,\n\t\tNetworkTx:        netTx,\n\t\tBlockRead:        float64(blkRead),\n\t\tBlockWrite:       float64(blkWrite),\n\t\tPidsCurrent:      pidsStatsCurrent,\n\t}, nil\n\n}\n\nfunc SetCgroup2StatsFields(previousStats *ContainerStats, metrics *v2.Metrics, links []netlink.Link) (StatsEntry, error) {\n\tcpuPercent := calculateCgroup2CPUPercent(previousStats, metrics)\n\tblkRead, blkWrite := calculateCgroup2IO(metrics)\n\tmem := calculateCgroup2MemUsage(metrics)\n\tmemLimit := getCgroupMemLimit(float64(metrics.Memory.UsageLimit))\n\tmemPercent := calculateMemPercent(memLimit, mem)\n\tpidsStatsCurrent := metrics.Pids.Current\n\tnetRx, netTx := calculateCgroupNetwork(links)\n\n\treturn StatsEntry{\n\t\tCPUPercentage:    cpuPercent,\n\t\tMemory:           mem,\n\t\tMemoryPercentage: memPercent,\n\t\tMemoryLimit:      memLimit,\n\t\tNetworkRx:        netRx,\n\t\tNetworkTx:        netTx,\n\t\tBlockRead:        float64(blkRead),\n\t\tBlockWrite:       float64(blkWrite),\n\t\tPidsCurrent:      pidsStatsCurrent,\n\t}, nil\n\n}\n\nfunc getCgroupMemLimit(memLimit float64) float64 {\n\tif memLimit == float64(^uint64(0)) {\n\t\treturn getHostMemLimit()\n\t}\n\treturn memLimit\n}\n\nfunc getHostMemLimit() float64 {\n\tfile, err := os.Open(\"/proc/meminfo\")\n\tif err != nil {\n\t\treturn float64(^uint64(0))\n\t}\n\tdefer file.Close()\n\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tif strings.HasPrefix(scanner.Text(), \"MemTotal:\") {\n\t\t\tfields := strings.Fields(scanner.Text())\n\t\t\tif len(fields) >= 2 {\n\t\t\t\tmemKb, err := strconv.ParseUint(fields[1], 10, 64)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn float64(memKb * 1024) // kB to bytes\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn float64(^uint64(0))\n}\n\nfunc calculateCgroupCPUPercent(previousStats *ContainerStats, metrics *v1.Metrics, systemInfo SystemInfo) float64 {\n\tvar (\n\t\tcpuPercent = 0.0\n\t\t// calculate the change for the cpu usage of the container in between readings\n\t\tcpuDelta = float64(metrics.CPU.Usage.Total) - float64(previousStats.CgroupCPU)\n\t\t// calculate the change for the entire system between readings\n\t\tsystemDelta = float64(systemInfo.SystemUsage) - float64(previousStats.CgroupSystem)\n\t\tonlineCPUs  = systemInfo.OnlineCPUs\n\t)\n\n\tif onlineCPUs == 0 {\n\t\tonlineCPUs = uint32(len(metrics.CPU.Usage.PerCPU))\n\t}\n\tif systemDelta > 0.0 && cpuDelta > 0.0 {\n\t\tcpuPercent = (cpuDelta / systemDelta) * float64(onlineCPUs) * 100.0\n\t}\n\treturn cpuPercent\n}\n\n// PercpuUsage is not supported in CgroupV2\nfunc calculateCgroup2CPUPercent(previousStats *ContainerStats, metrics *v2.Metrics) float64 {\n\tvar (\n\t\tcpuPercent = 0.0\n\t\t// calculate the change for the cpu usage of the container in between readings\n\t\tcpuDelta = float64(metrics.CPU.UsageUsec*1000) - float64(previousStats.Cgroup2CPU)\n\t\t// calculate the change for the entire system between readings\n\t\t_ = float64(metrics.CPU.SystemUsec*1000) - float64(previousStats.Cgroup2System)\n\t\t// time duration\n\t\ttimeDelta = time.Since(previousStats.Time)\n\t)\n\tif cpuDelta > 0.0 {\n\t\tcpuPercent = cpuDelta / float64(timeDelta.Nanoseconds()) * 100.0\n\t}\n\treturn cpuPercent\n}\n\nfunc calculateCgroupMemUsage(metrics *v1.Metrics) float64 {\n\tif v := metrics.Memory.TotalInactiveFile; v < metrics.Memory.Usage.Usage {\n\t\treturn float64(metrics.Memory.Usage.Usage - v)\n\t}\n\treturn float64(metrics.Memory.Usage.Usage)\n}\n\nfunc calculateCgroup2MemUsage(metrics *v2.Metrics) float64 {\n\tif v := metrics.Memory.InactiveFile; v < metrics.Memory.Usage {\n\t\treturn float64(metrics.Memory.Usage - v)\n\t}\n\treturn float64(metrics.Memory.Usage)\n}\n\nfunc calculateCgroupBlockIO(metrics *v1.Metrics) (uint64, uint64) {\n\tvar blkRead, blkWrite uint64\n\tfor _, bioEntry := range metrics.Blkio.IoServiceBytesRecursive {\n\t\tif len(bioEntry.Op) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch bioEntry.Op[0] {\n\t\tcase 'r', 'R':\n\t\t\tblkRead = blkRead + bioEntry.Value\n\t\tcase 'w', 'W':\n\t\t\tblkWrite = blkWrite + bioEntry.Value\n\t\t}\n\t}\n\treturn blkRead, blkWrite\n}\n\nfunc calculateCgroup2IO(metrics *v2.Metrics) (uint64, uint64) {\n\tvar ioRead, ioWrite uint64\n\n\tfor _, iOEntry := range metrics.Io.Usage {\n\t\tif iOEntry.Rios == 0 && iOEntry.Wios == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif iOEntry.Rios != 0 {\n\t\t\tioRead = ioRead + iOEntry.Rbytes\n\t\t}\n\n\t\tif iOEntry.Wios != 0 {\n\t\t\tioWrite = ioWrite + iOEntry.Wbytes\n\t\t}\n\t}\n\n\treturn ioRead, ioWrite\n}\n\nfunc calculateCgroupNetwork(links []netlink.Link) (float64, float64) {\n\tvar rx, tx float64\n\n\tfor _, l := range links {\n\t\tstats := l.Attrs().Statistics\n\t\tif stats != nil {\n\t\t\trx += float64(stats.RxBytes)\n\t\t\ttx += float64(stats.TxBytes)\n\t\t}\n\t}\n\treturn rx, tx\n}\n"
  },
  {
    "path": "pkg/store/filestore.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\n// TODO: implement a read-lock in lockutil, in addition to the current exclusive write-lock\n// This might improve performance in case of (mostly read) massively parallel concurrent scenarios\n\nconst (\n\t// Default filesystem permissions to use when creating dir or files\n\tdefaultFilePerm = 0o600\n\tdefaultDirPerm  = 0o700\n)\n\n// New returns a filesystem based Store implementation that satisfies both Manager and Locker\n// Note that atomicity is \"guaranteed\" by `os.Rename`, which arguably is not *always* atomic.\n// In particular, operating-system crashes may break that promise, and windows behavior is probably questionable.\n// That being said, this is still a much better solution than writing directly to the destination file.\nfunc New(rootPath string, dirPerm os.FileMode, filePerm os.FileMode) (Store, error) {\n\tif rootPath == \"\" {\n\t\treturn nil, errors.Join(ErrInvalidArgument, fmt.Errorf(\"FileStore rootPath cannot be empty\"))\n\t}\n\n\tif dirPerm == 0 {\n\t\tdirPerm = defaultDirPerm\n\t}\n\n\tif filePerm == 0 {\n\t\tfilePerm = defaultFilePerm\n\t}\n\n\tif err := os.MkdirAll(rootPath, dirPerm); err != nil {\n\t\treturn nil, errors.Join(ErrSystemFailure, err)\n\t}\n\n\treturn &fileStore{\n\t\tdir:      rootPath,\n\t\tdirPerm:  dirPerm,\n\t\tfilePerm: filePerm,\n\t}, nil\n}\n\ntype fileStore struct {\n\tmutex    sync.RWMutex\n\tdir      string\n\tlocked   *os.File\n\tdirPerm  os.FileMode\n\tfilePerm os.FileMode\n}\n\nfunc (vs *fileStore) Lock() error {\n\tvs.mutex.Lock()\n\n\tdirFile, err := filesystem.Lock(vs.dir)\n\tif err != nil {\n\t\treturn errors.Join(ErrLockFailure, err)\n\t}\n\n\tvs.locked = dirFile\n\n\treturn nil\n}\n\nfunc (vs *fileStore) Release() error {\n\tif vs.locked == nil {\n\t\treturn errors.Join(ErrFaultyImplementation, fmt.Errorf(\"cannot unlock already unlocked volume store %q\", vs.dir))\n\t}\n\n\tdefer vs.mutex.Unlock()\n\n\tdefer func() {\n\t\tvs.locked = nil\n\t}()\n\n\tif err := filesystem.Unlock(vs.locked); err != nil {\n\t\treturn errors.Join(ErrLockFailure, err)\n\t}\n\n\treturn nil\n}\n\nfunc (vs *fileStore) WithLock(fun func() error) (err error) {\n\tif err = vs.Lock(); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\terr = errors.Join(vs.Release(), err)\n\t}()\n\n\treturn fun()\n}\n\nfunc (vs *fileStore) Get(key ...string) ([]byte, error) {\n\tif vs.locked == nil {\n\t\treturn nil, errors.Join(ErrFaultyImplementation, fmt.Errorf(\"operations on the store must use locking\"))\n\t}\n\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn nil, err\n\t}\n\n\tpath := filepath.Join(append([]string{vs.dir}, key...)...)\n\n\tst, err := os.Stat(path)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn nil, errors.Join(ErrNotFound, fmt.Errorf(\"%q does not exist\", filepath.Join(key...)))\n\t\t}\n\n\t\treturn nil, errors.Join(ErrSystemFailure, err)\n\t}\n\n\tif st.IsDir() {\n\t\treturn nil, errors.Join(ErrFaultyImplementation, fmt.Errorf(\"%q is a directory and cannot be read as a file\", path))\n\t}\n\n\tcontent, err := filesystem.ReadFile(filepath.Join(append([]string{vs.dir}, key...)...))\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrSystemFailure, err)\n\t}\n\n\treturn content, nil\n}\n\nfunc (vs *fileStore) Exists(key ...string) (bool, error) {\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn false, err\n\t}\n\n\tpath := filepath.Join(append([]string{vs.dir}, key...)...)\n\n\t_, err := os.Stat(filepath.Join(path))\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\treturn false, errors.Join(ErrSystemFailure, err)\n\t}\n\n\treturn true, nil\n}\n\nfunc (vs *fileStore) Set(data []byte, key ...string) error {\n\tif vs.locked == nil {\n\t\treturn errors.Join(ErrFaultyImplementation, fmt.Errorf(\"operations on the store must use locking\"))\n\t}\n\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn err\n\t}\n\n\tfileName := key[len(key)-1]\n\tparent := vs.dir\n\n\tif len(key) > 1 {\n\t\tparent = filepath.Join(append([]string{parent}, key[0:len(key)-1]...)...)\n\t\terr := os.MkdirAll(parent, vs.dirPerm)\n\t\tif err != nil {\n\t\t\treturn errors.Join(ErrSystemFailure, err)\n\t\t}\n\t}\n\n\tdest := filepath.Join(parent, fileName)\n\tst, err := os.Stat(dest)\n\tif err == nil {\n\t\tif st.IsDir() {\n\t\t\treturn errors.Join(ErrFaultyImplementation, fmt.Errorf(\"%q is a directory and cannot be written to\", dest))\n\t\t}\n\t}\n\n\tif err := filesystem.WriteFileWithRename(filepath.Join(parent, fileName), data, vs.filePerm); err != nil {\n\t\treturn errors.Join(ErrSystemFailure, err)\n\t}\n\n\treturn nil\n}\n\nfunc (vs *fileStore) List(key ...string) ([]string, error) {\n\tif vs.locked == nil {\n\t\treturn nil, errors.Join(ErrFaultyImplementation, fmt.Errorf(\"operations on the store must use locking\"))\n\t}\n\n\t// Unlike Get, Set and Delete, List can have zero length key\n\tfor _, k := range key {\n\t\tif err := filesystem.ValidatePathComponent(k); err != nil {\n\t\t\treturn nil, errors.Join(ErrInvalidArgument, err)\n\t\t}\n\t}\n\n\tpath := filepath.Join(append([]string{vs.dir}, key...)...)\n\n\tst, err := os.Stat(path)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn nil, errors.Join(ErrNotFound, err)\n\t\t}\n\n\t\treturn nil, errors.Join(ErrSystemFailure, err)\n\t}\n\n\tif !st.IsDir() {\n\t\treturn nil, errors.Join(ErrFaultyImplementation, fmt.Errorf(\"%q is not a directory and cannot be enumerated\", path))\n\t}\n\n\tdirEntries, err := os.ReadDir(path)\n\tif err != nil {\n\t\treturn nil, errors.Join(ErrSystemFailure, err)\n\t}\n\n\tentries := []string{}\n\tfor _, dirEntry := range dirEntries {\n\t\tentries = append(entries, dirEntry.Name())\n\t}\n\n\treturn entries, nil\n}\n\nfunc (vs *fileStore) Delete(key ...string) error {\n\tif vs.locked == nil {\n\t\treturn errors.Join(ErrFaultyImplementation, fmt.Errorf(\"operations on the store must use locking\"))\n\t}\n\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn err\n\t}\n\n\tpath := filepath.Join(append([]string{vs.dir}, key...)...)\n\n\t_, err := os.Stat(path)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn errors.Join(ErrNotFound, err)\n\t\t}\n\n\t\treturn errors.Join(ErrSystemFailure, err)\n\t}\n\n\tif err = os.RemoveAll(path); err != nil {\n\t\treturn errors.Join(ErrSystemFailure, err)\n\t}\n\n\treturn nil\n}\n\nfunc (vs *fileStore) Location(key ...string) (string, error) {\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.Join(append([]string{vs.dir}, key...)...), nil\n}\n\nfunc (vs *fileStore) GroupEnsure(key ...string) error {\n\tif vs.locked == nil {\n\t\treturn errors.Join(ErrFaultyImplementation, fmt.Errorf(\"operations on the store must use locking\"))\n\t}\n\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn err\n\t}\n\n\tpath := filepath.Join(append([]string{vs.dir}, key...)...)\n\n\tif err := os.MkdirAll(path, vs.dirPerm); err != nil {\n\t\treturn errors.Join(ErrSystemFailure, err)\n\t}\n\n\treturn nil\n}\n\nfunc (vs *fileStore) GroupSize(key ...string) (int64, error) {\n\tif vs.locked == nil {\n\t\treturn 0, errors.Join(ErrFaultyImplementation, fmt.Errorf(\"operations on the store must use locking\"))\n\t}\n\n\tif err := validateAllPathComponents(key...); err != nil {\n\t\treturn 0, err\n\t}\n\n\tpath := filepath.Join(append([]string{vs.dir}, key...)...)\n\n\tst, err := os.Stat(path)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn 0, errors.Join(ErrNotFound, err)\n\t\t}\n\n\t\treturn 0, errors.Join(ErrSystemFailure, err)\n\t}\n\n\tif !st.IsDir() {\n\t\treturn 0, errors.Join(ErrFaultyImplementation, fmt.Errorf(\"%q is not a directory\", path))\n\t}\n\n\tvar size int64\n\tvar walkFn = func(_ string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !info.IsDir() {\n\t\t\tsize += info.Size()\n\t\t}\n\t\treturn err\n\t}\n\n\terr = filepath.Walk(path, walkFn)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn size, nil\n}\n\n// validateAllPathComponents will enforce validation for a slice of components\nfunc validateAllPathComponents(pathComponent ...string) error {\n\tif len(pathComponent) == 0 {\n\t\treturn errors.Join(ErrInvalidArgument, errors.New(\"you must specify an identifier\"))\n\t}\n\n\tfor _, key := range pathComponent {\n\t\tif err := filesystem.ValidatePathComponent(key); err != nil {\n\t\t\treturn errors.Join(ErrInvalidArgument, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc IsFilesystemSafe(identifier string) error {\n\tif err := filesystem.ValidatePathComponent(identifier); err != nil {\n\t\treturn errors.Join(ErrInvalidArgument, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/store/filestore_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\nfunc TestFileStoreBasics(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\t// Creation\n\ttempStore, err := New(tempDir, 0, 0)\n\tassert.NilError(t, err, \"temporary store creation should succeed\")\n\n\t// Lock acquisition\n\terr = tempStore.Lock()\n\tassert.NilError(t, err, \"acquiring a lock should succeed\")\n\terr = tempStore.Release()\n\tassert.NilError(t, err, \"releasing a lock should succeed\")\n\n\t// Non-existent keys\n\t_ = tempStore.Lock()\n\tdefer tempStore.Release()\n\n\t_, err = tempStore.Get(\"nonexistent\")\n\tassert.ErrorIs(t, err, ErrNotFound, \"getting a non existent key should ErrNotFound\")\n\n\terr = tempStore.Delete(\"nonexistent\")\n\tassert.ErrorIs(t, err, ErrNotFound, \"deleting a non existent key should ErrNotFound\")\n\n\t_, err = tempStore.List(\"nonexistent\")\n\tassert.ErrorIs(t, err, ErrNotFound, \"listing a non existent key should ErrNotFound\")\n\n\tdoesExist, err := tempStore.Exists(\"nonexistent\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, !doesExist, \"should not exist\")\n\n\t// Listing empty store\n\tresult, err := tempStore.List()\n\tassert.NilError(t, err, \"listing store root should succeed\")\n\tassert.Assert(t, len(result) == 0, \"list empty store return zero length slice\")\n\n\t// Invalid keys\n\t_, err = tempStore.Get(\"..\")\n\tassert.ErrorIs(t, err, filesystem.ErrInvalidPath, \"unsupported characters or patterns should return filesystem.ErrInvalidPath\")\n\n\terr = tempStore.Set([]byte(\"foo\"), \"..\")\n\tassert.ErrorIs(t, err, filesystem.ErrInvalidPath, \"unsupported characters or patterns should return filesystem.ErrInvalidPath\")\n\n\terr = tempStore.Delete(\"..\")\n\tassert.ErrorIs(t, err, filesystem.ErrInvalidPath, \"unsupported characters or patterns should return filesystem.ErrInvalidPath\")\n\n\t_, err = tempStore.List(\"..\")\n\tassert.ErrorIs(t, err, filesystem.ErrInvalidPath, \"unsupported characters or patterns should return filesystem.ErrInvalidPath\")\n\n\t// Writing, reading, listing, deleting\n\terr = tempStore.Set([]byte(\"foo\"), \"something\")\n\tassert.NilError(t, err, \"write should be successful\")\n\n\tdoesExist, err = tempStore.Exists(\"something\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, doesExist, \"should exist\")\n\n\tdata, err := tempStore.Get(\"something\")\n\tassert.NilError(t, err, \"read should be successful\")\n\tassert.Assert(t, string(data) == \"foo\", \"written data should be read back\")\n\n\tresult, err = tempStore.List()\n\tassert.NilError(t, err, \"listing store root should succeed\")\n\tassert.Assert(t, len(result) == 1, \"list store with one element should return it\")\n\n\t// Read from the list key obtained\n\tdata, err = tempStore.Get(result[0])\n\tassert.NilError(t, err, \"read should be successful\")\n\tassert.Assert(t, string(data) == \"foo\", \"written data should be read back\")\n\n\terr = tempStore.Delete(\"something\")\n\tassert.NilError(t, err, \"delete should be successful\")\n\n\tdoesExist, err = tempStore.Exists(\"something\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, !doesExist, \"should not exist\")\n\n\tresult, err = tempStore.List()\n\tassert.NilError(t, err, \"listing store root should succeed\")\n\tassert.Assert(t, len(result) == 0, \"list store should return it empty slice\")\n}\n\nfunc TestFileStoreGroups(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\t// Creation\n\ttempStore, err := New(tempDir, 0, 0)\n\tassert.NilError(t, err, \"temporary store creation should succeed\")\n\n\t_ = tempStore.Lock()\n\tdefer tempStore.Release()\n\n\terr = tempStore.Set([]byte(\"foo\"), \"group\", \"subgroup\", \"actualkey\")\n\tassert.NilError(t, err, \"write should be successful\")\n\n\tdoesExist, err := tempStore.Exists(\"group\", \"subgroup\", \"actualkey\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, doesExist, \"should exist\")\n\n\tdata, err := tempStore.Get(\"group\", \"subgroup\", \"actualkey\")\n\tassert.NilError(t, err, \"read should be successful\")\n\tassert.Assert(t, string(data) == \"foo\", \"written data should be read back\")\n\n\tresult, err := tempStore.List()\n\tassert.NilError(t, err, \"listing store root should succeed\")\n\tassert.Assert(t, len(result) == 1)\n\tassert.Assert(t, result[0] == \"group\")\n\n\tresult, err = tempStore.List(\"group\")\n\tassert.NilError(t, err, \"listing store root should succeed\")\n\tassert.Assert(t, len(result) == 1)\n\tassert.Assert(t, result[0] == \"subgroup\")\n\n\tresult, err = tempStore.List(\"group\", \"subgroup\")\n\tassert.NilError(t, err, \"listing store root should succeed\")\n\tassert.Assert(t, len(result) == 1)\n\tassert.Assert(t, result[0] == \"actualkey\")\n\n\terr = tempStore.Delete(\"group\", \"subgroup\", \"actualkey\")\n\tassert.NilError(t, err, \"delete should be successful\")\n\n\tdoesExist, err = tempStore.Exists(\"group\", \"subgroup\", \"actualkey\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, !doesExist, \"should not exist\")\n\n\tdoesExist, err = tempStore.Exists(\"group\", \"subgroup\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, doesExist, \"should exist\")\n\n\terr = tempStore.Delete(\"group\", \"subgroup\")\n\tassert.NilError(t, err, \"delete should be successful\")\n\n\tdoesExist, err = tempStore.Exists(\"group\", \"subgroup\")\n\tassert.NilError(t, err, \"exist should not error\")\n\tassert.Assert(t, !doesExist, \"should not exist\")\n\n}\n\nfunc TestFileStoreConcurrent(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\t// Creation\n\ttempStore, err := New(tempDir, 0, 0)\n\tassert.NilError(t, err, \"temporary store creation should succeed\")\n\n\tgo func() {\n\t\tlErr := tempStore.WithLock(func() error {\n\t\t\terr := tempStore.Set([]byte(\"routine 1\"), \"concurrentkey\")\n\t\t\tassert.NilError(t, err, \"writing should not error\")\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tresult, err := tempStore.Get(\"concurrentkey\")\n\t\t\tassert.NilError(t, err, \"reading should not error\")\n\t\t\tassert.Assert(t, string(result) == \"routine 1\")\n\t\t\treturn nil\n\t\t})\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\tgo func() {\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\tlErr := tempStore.WithLock(func() error {\n\t\t\terr := tempStore.Set([]byte(\"routine 2\"), \"concurrentkey\")\n\t\t\tassert.NilError(t, err, \"writing should not error\")\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tresult, err := tempStore.Get(\"concurrentkey\")\n\t\t\tassert.NilError(t, err, \"reading should not error\")\n\t\t\tassert.Assert(t, string(result) == \"routine 2\")\n\t\t\treturn nil\n\t\t})\n\t\tassert.NilError(t, lErr, \"locking should not error\")\n\t}()\n\n\tlErr := tempStore.WithLock(func() error {\n\t\terr := tempStore.Set([]byte(\"main routine 1\"), \"concurrentkey\")\n\t\tassert.NilError(t, err, \"writing should not error\")\n\t\ttime.Sleep(1 * time.Second)\n\t\tresult, err := tempStore.Get(\"concurrentkey\")\n\t\tassert.NilError(t, err, \"reading should not error\")\n\t\tassert.Assert(t, string(result) == \"main routine 1\")\n\t\treturn nil\n\t})\n\tassert.NilError(t, lErr, \"locking should not error\")\n\n\ttime.Sleep(750 * time.Millisecond)\n\n\tlErr = tempStore.WithLock(func() error {\n\t\terr := tempStore.Set([]byte(\"main routine 2\"), \"concurrentkey\")\n\t\tassert.NilError(t, err, \"writing should not error\")\n\t\ttime.Sleep(1 * time.Second)\n\t\tresult, err := tempStore.Get(\"concurrentkey\")\n\t\tassert.NilError(t, err, \"reading should not error\")\n\t\tassert.Assert(t, string(result) == \"main routine 2\")\n\t\treturn nil\n\t})\n\tassert.NilError(t, lErr, \"locking should not error\")\n}\n"
  },
  {
    "path": "pkg/store/store.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// Package store provides a concurrency-safe lightweight storage solution with a simple interface.\n// Embedders should call `Lock` and `defer Release` (or WithLock(func()error)) to wrap operations,\n// or series of operations, to ensure secure use.\n// Furthermore, a Store implementation must do atomic writes, providing guarantees that interrupted partial writes\n// never get committed.\n// The Store interface itself is meant to be generic, and alternative stores (memory based, or content-addressable)\n// may be implemented that satisfies it.\n// This package also provides the default, file based implementation that we are using.\npackage store\n\nimport (\n\t\"errors\"\n\n\t\"github.com/containerd/errdefs\"\n)\n\nvar (\n\t// ErrInvalidArgument may be returned by Get, Set, List, or Delete by specific SafeStore implementations\n\t// (eg: filesystem), when they want to impose implementation dependent restrictions on the identifiers\n\t// (filesystems typically do).\n\tErrInvalidArgument = errdefs.ErrInvalidArgument\n\t// ErrNotFound may be returned by Get or Delete when the requested key is not present in the store\n\tErrNotFound = errdefs.ErrNotFound\n\t// ErrSystemFailure may be returned by implementations when an internal failure occurs.\n\t// For example, for a filesystem implementation, failure to create a file will be wrapped by this error.\n\tErrSystemFailure = errors.New(\"system failure\")\n\t// ErrLockFailure may be returned by ReadLock, WriteLock, or Unlock, when the underlying locking mechanism fails.\n\t// In the case of the filesystem implementation, inability to lock the directory will return it.\n\tErrLockFailure = errors.New(\"lock failure\")\n\t// ErrFaultyImplementation may be returned by Get or Set when the target key exists and is a dir,\n\t// or by List when the target key is a file\n\t// This is indicative the code using the store is not consistent with what it treats as group, and what it treats as key\n\t// and is definitely a bug in that code\n\t// Missing lock will also trigger this when detected.\n\tErrFaultyImplementation = errors.New(\"code needs to be fixed\")\n)\n\n// Store represents a store that is able to grant an exclusive lock (ensuring concurrency safety,\n// both between go routines and across multiple binaries invocations), and is performing atomic operations.\n// Note that Store allows manipulating nested objects:\n// - Set([]byte(\"mykeyvalue\"), \"group\", \"subgroup\", \"my key1\")\n// - Set([]byte(\"mykeyvalue\"), \"group\", \"subgroup\", \"my key2\")\n// - Get(\"group\", \"subgroup\", \"my key1\")\n// - List(\"group\", \"subgroup\")\n// Note that both Delete and Exists can be applied indifferently to specific keys, or groups.\ntype Store interface {\n\tLocker\n\tManager\n}\n\n// Manager describes operations that can be performed on the store\ntype Manager interface {\n\t// List will return a slice of all subgroups (eg: subdirectories), or keys (eg: files), under a specific group (eg: dir)\n\t// Note that `key...` may be omitted, in which case, all objects' names at the root of the store are returned.\n\t// Example, in the volumestore, List() will return all existing volumes names\n\tList(key ...string) ([]string, error)\n\t// Exists checks that a given key exists\n\t// Example: Exists(\"meta.json\")\n\tExists(key ...string) (bool, error)\n\t// Get returns the content of a key\n\tGet(key ...string) ([]byte, error)\n\t// Set saves bytes to a key\n\tSet(data []byte, key ...string) error\n\t// Delete removes a key or a group\n\tDelete(key ...string) error\n\t// Location returns the absolute path to a certain resource\n\t// Note that this technically \"leaks\" (filesystem) implementation details up.\n\t// It is necessary though when we are going to pass these filepath to containerd for eg.\n\tLocation(key ...string) (string, error)\n\n\t// GroupSize will return the combined size of all objects stored under the group (eg: dir)\n\tGroupSize(key ...string) (int64, error)\n\t// GroupEnsure ensures that a given group (eg: directory) exists\n\tGroupEnsure(key ...string) error\n}\n\n// Locker describes a locking mechanism that can be used to encapsulate operations in a safe way\ntype Locker interface {\n\tLock() error\n\tRelease() error\n\tWithLock(fun func() error) (err error)\n}\n"
  },
  {
    "path": "pkg/strutil/strutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n/*\n   Portions from https://github.com/moby/moby/blob/v20.10.0-rc2/runconfig/opts/parse.go\n   Copyright (C) Docker/Moby authors.\n   Licensed under the Apache License, Version 2.0\n   NOTICE: https://github.com/moby/moby/blob/v20.10.0-rc2/NOTICE\n*/\n\n/*\n   Portions from https://github.com/moby/buildkit/blob/v0.9.1/cmd/buildkitd/config.go#L35-L42\n   Copyright (C) BuildKit authors.\n   Licensed under the Apache License, Version 2.0\n*/\n\npackage strutil\n\nimport (\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/containerd/errdefs\"\n)\n\n// ConvertKVStringsToMap is from https://github.com/moby/moby/blob/v20.10.0-rc2/runconfig/opts/parse.go\n//\n// ConvertKVStringsToMap converts [\"key=value\"] to {\"key\":\"value\"}\nfunc ConvertKVStringsToMap(values []string) map[string]string {\n\tresult := make(map[string]string, len(values))\n\tfor _, value := range values {\n\t\tkv := strings.SplitN(value, \"=\", 2)\n\t\tif len(kv) == 1 {\n\t\t\tresult[kv[0]] = \"\"\n\t\t} else {\n\t\t\tresult[kv[0]] = kv[1]\n\t\t}\n\t}\n\n\treturn result\n}\n\n// InStringSlice checks whether a string is inside a string slice.\n// Comparison is case insensitive.\n//\n// From https://github.com/containerd/containerd/blob/7c6d710bcfc81a30ac1e8cbb2e6a4c294184f7b7/pkg/cri/util/strings.go#L21-L30\nfunc InStringSlice(ss []string, str string) bool {\n\tfor _, s := range ss {\n\t\tif strings.EqualFold(s, str) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc DedupeStrSlice(in []string) []string {\n\tm := make(map[string]struct{})\n\tvar res []string\n\tfor _, s := range in {\n\t\tif _, ok := m[s]; !ok {\n\t\t\tres = append(res, s)\n\t\t\tm[s] = struct{}{}\n\t\t}\n\t}\n\treturn res\n}\n\n// SliceToSet converts a slice of strings into a set.\n// In Go, a set is often represented as a map with keys as the set elements and values as boolean.\n// This function iterates over the slice, adding each string as a key in the map.\n// The corresponding map value is set to true, serving as a placeholder.\n// The resulting map can be used to quickly check the presence of an element in the set.\nfunc SliceToSet(in []string) map[string]bool {\n\tset := make(map[string]bool)\n\tfor _, s := range in {\n\t\tset[s] = true\n\t}\n\treturn set\n}\n\n// ParseCSVMap parses a string like \"foo=x,bar=y\" into a map\nfunc ParseCSVMap(s string) (map[string]string, error) {\n\tcsvR := csv.NewReader(strings.NewReader(s))\n\t// s can contains quotes, but the csv reader needs LazyQuotes to recognize quotes as values.\n\tcsvR.LazyQuotes = true\n\tra, err := csvR.ReadAll()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot parse %q: %w\", s, err)\n\t}\n\tif len(ra) != 1 {\n\t\treturn nil, fmt.Errorf(\"expected a single line, got %d lines: %w\", len(ra), errdefs.ErrInvalidArgument)\n\t}\n\tfields := ra[0]\n\tm := make(map[string]string)\n\tfor _, field := range fields {\n\t\tkv := strings.SplitN(field, \"=\", 2)\n\t\tif len(kv) == 2 {\n\t\t\tm[kv[0]] = kv[1]\n\t\t} else {\n\t\t\tm[kv[0]] = \"\"\n\t\t}\n\t}\n\treturn m, nil\n}\n\nfunc TrimStrSliceRight(base, extra []string) []string {\n\tfor i := 0; i < len(base); i++ {\n\t\tif reflect.DeepEqual(base[i:], extra) {\n\t\t\treturn base[:i]\n\t\t}\n\t}\n\treturn base\n}\n\nfunc ReverseStrSlice(in []string) []string {\n\tout := make([]string, len(in))\n\tfor i, v := range in {\n\t\tout[len(in)-i-1] = v\n\t}\n\treturn out\n}\n\n// ParseBoolOrAuto returns (nil, nil) if s is \"auto\"\n// https://github.com/moby/buildkit/blob/v0.9.1/cmd/buildkitd/config.go#L35-L42\nfunc ParseBoolOrAuto(s string) (*bool, error) {\n\tif s == \"\" || strings.ToLower(s) == \"auto\" {\n\t\treturn nil, nil\n\t}\n\tb, err := strconv.ParseBool(s)\n\treturn &b, err\n}\n"
  },
  {
    "path": "pkg/strutil/strutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage strutil\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestDedupeStrSlice(t *testing.T) {\n\tassert.DeepEqual(t,\n\t\t[]string{\"apple\", \"banana\", \"chocolate\"},\n\t\tDedupeStrSlice([]string{\"apple\", \"banana\", \"apple\", \"chocolate\"}))\n\n\tassert.DeepEqual(t,\n\t\t[]string{\"apple\", \"banana\", \"chocolate\"},\n\t\tDedupeStrSlice([]string{\"apple\", \"apple\", \"banana\", \"chocolate\", \"apple\"}))\n\n}\n\nfunc TestSliceToSet(t *testing.T) {\n\tassert.DeepEqual(t,\n\t\tmap[string]bool{\"apple\": true, \"banana\": true, \"chocolate\": true},\n\t\tSliceToSet([]string{\"apple\", \"banana\", \"apple\", \"chocolate\"}))\n\n\tassert.DeepEqual(t,\n\t\tmap[string]bool{\"apple\": true, \"banana\": true, \"chocolate\": true},\n\t\tSliceToSet([]string{\"apple\", \"apple\", \"banana\", \"chocolate\", \"apple\"}))\n\n}\n\nfunc TestTrimStrSliceRight(t *testing.T) {\n\tassert.DeepEqual(t,\n\t\t[]string{\"foo\", \"bar\", \"baz\"},\n\t\tTrimStrSliceRight([]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}, []string{\"qux\", \"quux\"}))\n\tassert.DeepEqual(t,\n\t\t[]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"},\n\t\tTrimStrSliceRight([]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}, []string{\"bar\", \"baz\"}))\n\tassert.DeepEqual(t,\n\t\t[]string{},\n\t\tTrimStrSliceRight([]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}, []string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}))\n\tassert.DeepEqual(t,\n\t\t[]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"},\n\t\tTrimStrSliceRight([]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}, []string{}))\n\tassert.DeepEqual(t,\n\t\t[]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"},\n\t\tTrimStrSliceRight([]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}, []string{\"aaa\"}))\n\tassert.DeepEqual(t,\n\t\t[]string{\"foo\", \"bar\", \"baz\", \"qux\"},\n\t\tTrimStrSliceRight([]string{\"foo\", \"bar\", \"baz\", \"qux\", \"quux\"}, []string{\"quux\"}))\n\n}\n\nfunc TestReverseStrSlice(t *testing.T) {\n\tassert.DeepEqual(t,\n\t\t[]string{\"foo\", \"bar\", \"baz\"},\n\t\tReverseStrSlice([]string{\"baz\", \"bar\", \"foo\"}))\n}\n\nfunc TestParseBoolOrAuto(t *testing.T) {\n\tvar xtrue = true\n\tvar xfalse = false\n\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    *bool\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"normal-1\",\n\t\t\targs: args{\n\t\t\t\ts: \"true\",\n\t\t\t},\n\t\t\twant:    &xtrue,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"normal-2\",\n\t\t\targs: args{\n\t\t\t\ts: \"false\",\n\t\t\t},\n\t\t\twant:    &xfalse,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"blank\",\n\t\t\targs: args{\n\t\t\t\ts: \"\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"auto\",\n\t\t\targs: args{\n\t\t\t\ts: \"auto\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseBoolOrAuto(tt.args.s)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseBoolOrAuto() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif tt.want != nil {\n\t\t\t\tif *got != *tt.want {\n\t\t\t\t\tt.Errorf(\"ParseBoolOrAuto() = %v, want %v\", got, tt.want)\n\t\t\t\t}\n\t\t\t} else if got != nil {\n\t\t\t\tt.Errorf(\"ParseBoolOrAuto() = %v, want %v\", got, nil)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConvertKVStringsToMap(t *testing.T) {\n\ttype args struct {\n\t\tvalues []string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant map[string]string\n\t}{\n\t\t{\n\t\t\tname: \"normal\",\n\t\t\targs: args{\n\t\t\t\tvalues: []string{\"foo=bar\", \"baz=qux\"},\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"foo\": \"bar\",\n\t\t\t\t\"baz\": \"qux\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"normal-1\",\n\t\t\targs: args{\n\t\t\t\tvalues: []string{\"foo\"},\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"foo\": \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"normal-2\",\n\t\t\targs: args{\n\t\t\t\tvalues: []string{\"foo=bar=baz\"},\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"foo\": \"bar=baz\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\targs: args{\n\t\t\t\tvalues: []string{},\n\t\t\t},\n\t\t\twant: map[string]string{},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ConvertKVStringsToMap(tt.args.values); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"ConvertKVStringsToMap() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInStringSlice(t *testing.T) {\n\ttype args struct {\n\t\tss  []string\n\t\tstr string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"normal\",\n\t\t\targs: args{\n\t\t\t\tss:  []string{\"foo\", \"bar\", \"baz\"},\n\t\t\t\tstr: \"bar\",\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"normal-1\",\n\t\t\targs: args{\n\t\t\t\tss:  []string{\"foo\", \"bar\", \"baz\"},\n\t\t\t\tstr: \"qux\",\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := InStringSlice(tt.args.ss, tt.args.str); got != tt.want {\n\t\t\t\tt.Errorf(\"InStringSlice() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseCSVMap(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    map[string]string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"normal\",\n\t\t\targs: args{\n\t\t\t\ts: \"foo=x,bar=y,baz=z,qux\",\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"foo\": \"x\",\n\t\t\t\t\"bar\": \"y\",\n\t\t\t\t\"baz\": \"z\",\n\t\t\t\t\"qux\": \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"normal-1\",\n\t\t\targs: args{\n\t\t\t\ts: \"\\\"foo=x,bar=y\\\",baz=z,qux\",\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"foo\": \"x,bar=y\",\n\t\t\t\t\"baz\": \"z\",\n\t\t\t\t\"qux\": \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"normal-2\",\n\t\t\targs: args{\n\t\t\t\ts: \"foo=\\\"x,bar=y,baz=\\\"z\\\",qux\",\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"foo\": \"\\\"x\",\n\t\t\t\t\"bar\": \"y\",\n\t\t\t\t\"baz\": \"\\\"z\\\"\",\n\t\t\t\t\"qux\": \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid\",\n\t\t\targs: args{\n\t\t\t\ts: \"sssssss\\nsss\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid-1\",\n\t\t\targs: args{\n\t\t\t\ts: \"sssssss\\n\\\"\\nsss\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseCSVMap(tt.args.s)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseCSVMap() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"ParseCSVMap() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/systemutil/socket_unix.go",
    "content": "//go:build unix\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage systemutil\n\nimport (\n\t\"path/filepath\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc IsSocketAccessible(s string) error {\n\tabs, err := filepath.Abs(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// set AT_EACCESS to allow running nerdctl as a setuid binary\n\treturn unix.Faccessat(-1, abs, unix.R_OK|unix.W_OK, unix.AT_EACCESS)\n}\n"
  },
  {
    "path": "pkg/systemutil/socket_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage systemutil\n\nimport (\n\t\"time\"\n\n\t\"github.com/Microsoft/go-winio\"\n)\n\nfunc IsSocketAccessible(s string) error {\n\t// test if we can access the pipe\n\ttimeout := 2 * time.Second\n\t_, err := winio.DialPipe(s, &timeout)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/tabutil/tabutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage tabutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype TabReader struct {\n\tindexs  map[string]*headerIndex\n\theaders []string\n}\n\ntype headerIndex struct {\n\tstart int\n\tend   int\n}\n\nfunc NewReader(header string) *TabReader {\n\theaders := strings.Split(strings.TrimSpace(header), \"\\t\")\n\treturn &TabReader{\n\t\tindexs:  make(map[string]*headerIndex),\n\t\theaders: headers,\n\t}\n}\n\nfunc (r *TabReader) ParseHeader(header string) error {\n\tif len(r.headers) == 0 {\n\t\treturn fmt.Errorf(\"no header\")\n\t}\n\tfor i := range r.headers {\n\t\tstart := strings.Index(header, r.headers[i])\n\t\tif start == -1 {\n\t\t\treturn fmt.Errorf(\"header %q not matched\", r.headers[i])\n\t\t}\n\t\tif i > 0 {\n\t\t\tr.indexs[r.headers[i-1]].end = start\n\t\t}\n\t\tr.indexs[r.headers[i]] = &headerIndex{start, -1}\n\t}\n\treturn nil\n}\n\nfunc (r *TabReader) ReadRow(row, key string) (string, bool) {\n\tidx, ok := r.indexs[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\tvar value string\n\tif idx.end == -1 {\n\t\tvalue = row[idx.start:]\n\t} else {\n\t\tvalue = row[idx.start:idx.end]\n\t}\n\treturn strings.TrimSpace(value), true\n}\n"
  },
  {
    "path": "pkg/tabutil/tabutil_test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage tabutil\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\nfunc TestTabReader(t *testing.T) {\n\ttabRows := strings.Split(`a    b    c\n1    2    3\n123  456  789`, \"\\n\")\n\treader := NewReader(\"a\\tb\\tc\\t\")\n\n\terr := reader.ParseHeader(tabRows[0])\n\tassert.NilError(t, err)\n\n\tvar (\n\t\tvalue string\n\t)\n\tvalue, _ = reader.ReadRow(tabRows[1], \"a\")\n\tassert.Equal(t, value, \"1\")\n\n\tvalue, _ = reader.ReadRow(tabRows[1], \"c\")\n\tassert.Equal(t, value, \"3\")\n\n\tvalue, _ = reader.ReadRow(tabRows[2], \"b\")\n\tassert.Equal(t, value, \"456\")\n}\n"
  },
  {
    "path": "pkg/tarutil/tarutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage tarutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/containerd/log\"\n)\n\n// FindTarBinary returns a path to the tar binary and whether it is GNU tar.\nfunc FindTarBinary() (string, bool, error) {\n\tisGNU := func(exe string) bool {\n\t\tv, err := exec.Command(exe, \"--version\").Output()\n\t\tif err != nil {\n\t\t\tlog.L.Warnf(\"Failed to detect whether %q is GNU tar or not\", exe)\n\t\t\treturn false\n\t\t}\n\t\tif !strings.Contains(string(v), \"GNU tar\") {\n\t\t\tlog.L.Warnf(\"%q does not seem GNU tar\", exe)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\tif v := os.Getenv(\"TAR\"); v != \"\" {\n\t\tif exe, err := exec.LookPath(v); err == nil {\n\t\t\treturn exe, isGNU(exe), nil\n\t\t}\n\t}\n\tif exe, err := exec.LookPath(\"gnutar\"); err == nil {\n\t\treturn exe, true, nil\n\t}\n\tif exe, err := exec.LookPath(\"gtar\"); err == nil {\n\t\treturn exe, true, nil\n\t}\n\tif exe, err := exec.LookPath(\"tar\"); err == nil {\n\t\treturn exe, isGNU(exe), nil\n\t}\n\treturn \"\", false, fmt.Errorf(\"failed to find `tar` binary\")\n}\n"
  },
  {
    "path": "pkg/taskutil/taskutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage taskutil\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"github.com/opencontainers/go-digest\"\n\t\"golang.org/x/term\"\n\n\t\"github.com/containerd/console\"\n\t\"github.com/containerd/containerd/api/types\"\n\tcontainerd \"github.com/containerd/containerd/v2/client\"\n\t\"github.com/containerd/containerd/v2/core/content\"\n\t\"github.com/containerd/containerd/v2/core/images\"\n\t\"github.com/containerd/containerd/v2/pkg/archive\"\n\t\"github.com/containerd/containerd/v2/pkg/cio\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/cioutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/consoleutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n)\n\n// TaskOptions contains options for creating a new task\ntype TaskOptions struct {\n\tAttachStreamOpt []string\n\tIsInteractive   bool\n\tIsTerminal      bool\n\tIsDetach        bool\n\tCon             console.Console\n\tLogURI          string\n\tDetachKeys      string\n\tNamespace       string\n\tDetachC         chan<- struct{}\n\tCheckpointDir   string\n}\n\n// NewTask is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/tasks/tasks_unix.go#L70-L108\nfunc NewTask(ctx context.Context, client *containerd.Client, container containerd.Container, opts TaskOptions) (containerd.Task, error) {\n\tvar (\n\t\tcheckpoint *types.Descriptor\n\t\tt          containerd.Task\n\t\terr        error\n\t)\n\n\tif opts.CheckpointDir != \"\" {\n\t\ttar := archive.Diff(ctx, \"\", opts.CheckpointDir)\n\t\tcs := client.ContentStore()\n\t\twriter, err := cs.Writer(ctx, content.WithRef(opts.CheckpointDir))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer writer.Close()\n\t\tsize, err := io.Copy(writer, tar)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlabels := map[string]string{\n\t\t\t\"containerd.io/gc.root\": time.Now().UTC().Format(time.RFC3339),\n\t\t}\n\t\tif err = writer.Commit(ctx, size, \"\", content.WithLabels(labels)); err != nil {\n\t\t\tif !errors.Is(err, errdefs.ErrAlreadyExists) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tcheckpoint = &types.Descriptor{\n\t\t\tMediaType: images.MediaTypeContainerd1Checkpoint,\n\t\t\tDigest:    writer.Digest().String(),\n\t\t\tSize:      size,\n\t\t}\n\t\tdefer func() {\n\t\t\tif checkpoint != nil {\n\t\t\t\t_ = cs.Delete(ctx, digest.Digest(checkpoint.Digest))\n\t\t\t}\n\t\t}()\n\t\tif err = tar.Close(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to close checkpoint tar stream: %w\", err)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to upload checkpoint to containerd: %w\", err)\n\t\t}\n\t}\n\tcloser := func() {\n\t\tif opts.DetachC != nil {\n\t\t\topts.DetachC <- struct{}{}\n\t\t}\n\t\t// t will be set by container.NewTask at the end of this function.\n\t\t//\n\t\t// We cannot use container.Task(ctx, cio.Load) to get the IO here\n\t\t// because the `cancel` field of the returned `*cio` is nil. [1]\n\t\t//\n\t\t// [1] https://github.com/containerd/containerd/blob/8f756bc8c26465bd93e78d9cd42082b66f276e10/cio/io.go#L358-L359\n\t\tio := t.IO()\n\t\tif io == nil {\n\t\t\tlog.G(ctx).Errorf(\"got a nil io\")\n\t\t\treturn\n\t\t}\n\t\tio.Cancel()\n\t}\n\tvar ioCreator cio.Creator\n\tif len(opts.AttachStreamOpt) != 0 {\n\t\tlog.G(ctx).Debug(\"attaching output instead of using the log-uri\")\n\t\t// when attaching a TTY we use writee for stdio and binary for log persistence\n\t\tif opts.IsTerminal {\n\t\t\tvar in io.Reader\n\t\t\tif opts.IsInteractive {\n\t\t\t\t// FIXME: check IsTerminal on Windows too\n\t\t\t\tif runtime.GOOS != \"windows\" && !term.IsTerminal(0) {\n\t\t\t\t\treturn nil, errors.New(\"the input device is not a TTY\")\n\t\t\t\t}\n\t\t\t\tvar err error\n\t\t\t\tin, err = consoleutil.NewDetachableStdin(opts.Con, opts.DetachKeys, closer)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tioCreator = cioutil.NewContainerIO(opts.Namespace, opts.LogURI, true, in, opts.Con, nil)\n\t\t} else {\n\t\t\tstreams := processAttachStreamsOpt(opts.AttachStreamOpt)\n\t\t\tioCreator = cioutil.NewContainerIO(opts.Namespace, opts.LogURI, false, streams.stdIn, streams.stdOut, streams.stdErr)\n\t\t}\n\n\t} else if opts.IsTerminal && opts.IsDetach {\n\t\tu, err := url.Parse(opts.LogURI)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar args []string\n\t\tfor k, vs := range u.Query() {\n\t\t\targs = append(args, k)\n\t\t\tif len(vs) > 0 {\n\t\t\t\targs = append(args, vs[0])\n\t\t\t}\n\t\t}\n\n\t\t// args[0]: _NERDCTL_INTERNAL_LOGGING\n\t\t// args[1]: /var/lib/nerdctl/1935db59\n\t\tif len(args) != 2 {\n\t\t\treturn nil, errors.New(\"parse logging path error\")\n\t\t}\n\t\tparsedPath := u.Path\n\t\t// For Windows, remove the leading slash\n\t\tif (runtime.GOOS == \"windows\") && (strings.HasPrefix(parsedPath, \"/\")) {\n\t\t\tparsedPath = strings.TrimLeft(parsedPath, \"/\")\n\t\t}\n\t\tioCreator = cio.TerminalBinaryIO(parsedPath, map[string]string{\n\t\t\targs[0]: args[1],\n\t\t})\n\t} else if opts.IsTerminal && !opts.IsDetach {\n\t\tif opts.Con == nil {\n\t\t\treturn nil, errors.New(\"got nil con with isTerminal=true\")\n\t\t}\n\t\tvar in io.Reader\n\t\tif opts.IsInteractive {\n\t\t\t// FIXME: check IsTerminal on Windows too\n\t\t\tif runtime.GOOS != \"windows\" && !term.IsTerminal(0) {\n\t\t\t\treturn nil, errors.New(\"the input device is not a TTY\")\n\t\t\t}\n\t\t\tvar err error\n\t\t\tin, err = consoleutil.NewDetachableStdin(opts.Con, opts.DetachKeys, closer)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tioCreator = cioutil.NewContainerIO(opts.Namespace, opts.LogURI, true, in, os.Stdout, os.Stderr)\n\t} else if opts.IsDetach && opts.LogURI != \"\" && opts.LogURI != \"none\" {\n\t\tu, err := url.Parse(opts.LogURI)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tioCreator = cio.LogURI(u)\n\t} else {\n\t\tvar in io.Reader\n\t\tif opts.IsInteractive {\n\t\t\tif sv, err := containerdutil.ServerSemVer(ctx, client); err != nil {\n\t\t\t\tlog.G(ctx).Warn(err)\n\t\t\t} else if sv.LessThan(semver.MustParse(\"1.6.0-0\")) {\n\t\t\t\tlog.G(ctx).Warnf(\"`nerdctl (run|exec) -i` without `-t` expects containerd 1.6 or later, got containerd %v\", sv)\n\t\t\t}\n\t\t\tvar stdinC io.ReadCloser = &StdinCloser{\n\t\t\t\tStdin: os.Stdin,\n\t\t\t\tCloser: func() {\n\t\t\t\t\tif t, err := container.Task(ctx, nil); err != nil {\n\t\t\t\t\t\tlog.G(ctx).WithError(err).Debugf(\"failed to get task for StdinCloser\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.CloseIO(ctx, containerd.WithStdinCloser)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t\tin = stdinC\n\t\t}\n\t\tioCreator = cioutil.NewContainerIO(opts.Namespace, opts.LogURI, false, in, os.Stdout, os.Stderr)\n\t}\n\n\ttaskOpts := []containerd.NewTaskOpts{\n\t\tfunc(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {\n\t\t\tinfo.Checkpoint = checkpoint\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tt, err = container.NewTask(ctx, ioCreator, taskOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn t, nil\n}\n\n// struct used to store streams specified with attachStreamOpt (-a, --attach)\ntype streams struct {\n\tstdIn  *os.File\n\tstdOut *os.File\n\tstdErr *os.File\n}\n\nfunc nullStream() *os.File {\n\tdevNull, err := os.Open(os.DevNull)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer devNull.Close()\n\n\treturn devNull\n}\n\nfunc processAttachStreamsOpt(streamsArr []string) streams {\n\tstdIn := os.Stdin\n\tstdOut := os.Stdout\n\tstdErr := os.Stderr\n\n\tfor i, str := range streamsArr {\n\t\tstreamsArr[i] = strings.ToUpper(str)\n\t}\n\n\tif !slices.Contains(streamsArr, \"STDIN\") {\n\t\tstdIn = nullStream()\n\t}\n\n\tif !slices.Contains(streamsArr, \"STDOUT\") {\n\t\tstdOut = nullStream()\n\t}\n\n\tif !slices.Contains(streamsArr, \"STDERR\") {\n\t\tstdErr = nullStream()\n\t}\n\n\treturn streams{\n\t\tstdIn:  stdIn,\n\t\tstdOut: stdOut,\n\t\tstdErr: stdErr,\n\t}\n}\n\n// StdinCloser is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/tasks/exec.go#L181-L194\ntype StdinCloser struct {\n\tmu     sync.Mutex\n\tStdin  *os.File\n\tCloser func()\n\tclosed bool\n}\n\nfunc (s *StdinCloser) Read(p []byte) (int, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.closed {\n\t\treturn 0, syscall.EBADF\n\t}\n\tn, err := s.Stdin.Read(p)\n\tif err != nil {\n\t\tif s.Closer != nil {\n\t\t\ts.Closer()\n\t\t\ts.closed = true\n\t\t}\n\t}\n\treturn n, err\n}\n\n// Close implements Closer\nfunc (s *StdinCloser) Close() error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.closed {\n\t\treturn nil\n\t}\n\tif s.Closer != nil {\n\t\ts.Closer()\n\t}\n\ts.closed = true\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/testutil/compose.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/compose-spec/compose-go/v2/loader\"\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n)\n\ntype ComposeDir struct {\n\tt            tig.T\n\tdir          string\n\tyamlBasePath string\n}\n\nfunc (cd *ComposeDir) WriteFile(name, content string) {\n\tif err := filesystem.WriteFile(filepath.Join(cd.dir, name), []byte(content), 0644); err != nil {\n\t\tcd.t.Log(fmt.Sprintf(\"Failed to create file %v\", err))\n\t\tcd.t.FailNow()\n\t}\n}\n\nfunc (cd *ComposeDir) YAMLFullPath() string {\n\treturn filepath.Join(cd.dir, cd.yamlBasePath)\n}\n\nfunc (cd *ComposeDir) Dir() string {\n\treturn cd.dir\n}\n\nfunc (cd *ComposeDir) ProjectName() string {\n\treturn filepath.Base(cd.dir)\n}\n\nfunc (cd *ComposeDir) CleanUp() {\n\tos.RemoveAll(cd.dir)\n}\n\nfunc NewComposeDir(t tig.T, dockerComposeYAML string) *ComposeDir {\n\ttmpDir, err := os.MkdirTemp(\"\", \"nerdctl-compose-test\")\n\tif err != nil {\n\t\tt.Log(fmt.Sprintf(\"Failed to create temp dir: %v\", err))\n\t\tt.FailNow()\n\t}\n\tcd := &ComposeDir{\n\t\tt:            t,\n\t\tdir:          tmpDir,\n\t\tyamlBasePath: \"docker-compose.yaml\",\n\t}\n\tcd.WriteFile(cd.yamlBasePath, dockerComposeYAML)\n\treturn cd\n}\n\n// Load is used only for unit testing.\nfunc LoadProject(fileName, projectName string, envMap map[string]string) (*compose.Project, error) {\n\tif envMap == nil {\n\t\tenvMap = make(map[string]string)\n\t}\n\tb, err := filesystem.ReadFile(fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\twd, err := filepath.Abs(filepath.Dir(fileName))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar files []compose.ConfigFile\n\tfiles = append(files, compose.ConfigFile{Filename: fileName, Content: b})\n\treturn loader.LoadWithContext(context.TODO(), compose.ConfigDetails{\n\t\tWorkingDir:  wd,\n\t\tConfigFiles: files,\n\t\tEnvironment: envMap,\n\t}, withProjectName(projectName))\n}\n\nfunc withProjectName(name string) func(*loader.Options) {\n\treturn func(lOpts *loader.Options) {\n\t\tlOpts.SetProjectName(name, true)\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/images.yaml",
    "content": "# Current schema (defined in images_linux.go) allows for ref, tag, (index) digest and platform variants.\n# Right now, digest and variants are not used for anything, but they should / could be in the future.\n# Also note that changing the schema should be easy and straight-forward for now, so,\n# this might evolve in the near future.\nalpine:\n  ref: \"ghcr.io/stargz-containers/alpine\"\n  tag: \"3.13-org\"\n  schemaversion: 2\n  mediatype: \"application/vnd.docker.distribution.manifest.list.v2+json\"\n  digest: \"sha256:ec14c7992a97fc11425907e908340c6c3d6ff602f5f13d899e6b7027c9b4133a\"\n  variants: [\"linux/amd64\", \"linux/arm64\"]\n  manifests:\n    linux/amd64:\n      mediatype: \"application/vnd.docker.distribution.manifest.v2+json\"\n      manifest: \"sha256:e103c1b4bf019dc290bcc7aca538dc2bf7a9d0fc836e186f5fa34945c5168310\"\n      config: \"sha256:49f356fa4513676c5e22e3a8404aad6c7262cc7aaed15341458265320786c58c\"\n      raw: \"ewogICAic2NoZW1hVmVyc2lvbiI6IDIsCiAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRvY2tlci5kaXN0cmlidXRpb24ubWFuaWZlc3QudjIranNvbiIsCiAgICJjb25maWciOiB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRvY2tlci5jb250YWluZXIuaW1hZ2UudjEranNvbiIsCiAgICAgICJzaXplIjogMTQ3MiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6NDlmMzU2ZmE0NTEzNjc2YzVlMjJlM2E4NDA0YWFkNmM3MjYyY2M3YWFlZDE1MzQxNDU4MjY1MzIwNzg2YzU4YyIKICAgfSwKICAgImxheWVycyI6IFsKICAgICAgewogICAgICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLAogICAgICAgICAic2l6ZSI6IDI4MTE5NDcsCiAgICAgICAgICJkaWdlc3QiOiAic2hhMjU2OmNhM2NkNDJhN2M5NTI1ZjZjZTNkNjRjMWE3MDk4MjYxM2E4MjM1ZjBjYzA1N2VjOTI0NDA1MjkyMTg1M2VmMTUiCiAgICAgIH0KICAgXQp9\"\n    linux/arm64:\n      manifest: \"sha256:071fa5de01a240dbef5be09d69f8fef2f89d68445d9175393773ee389b6f5935\"\n\nbusybox:\n  ref: \"ghcr.io/containerd/busybox\"\n  tag: \"1.36\"\n\ndocker_auth:\n  ref: \"ghcr.io/stargz-containers/cesanta/docker_auth\"\n  tag: \"1.7-org\"\n\nfluentd:\n  ref: \"fluentd\"\n  tag: \"v1.18.0-debian-1.0\"\n\ngolang:\n  ref: \"golang\"\n  tag: \"1.25.0-trixie\"\n\nkubo:\n  ref: \"ghcr.io/stargz-containers/ipfs/kubo\"\n  tag: \"v0.16.0-org\"\n\nmariadb:\n  ref: \"ghcr.io/stargz-containers/mariadb\"\n  tag: \"10.5-org\"\n\nnanoserver:\n  ref: \"mcr.microsoft.com/windows/nanoserver\"\n  tag: \"ltsc2022\"\n\nnginx:\n  ref: \"ghcr.io/stargz-containers/nginx\"\n  tag: \"1.19-alpine-org\"\n\nregistry:\n  ref: \"ghcr.io/stargz-containers/registry\"\n  tag: \"2-org\"\n\nstargz:\n  ref: \"ghcr.io/containerd/stargz-snapshotter\"\n  tag: \"0.15.1-kind\"\n\nwordpress:\n  ref: \"ghcr.io/stargz-containers/wordpress\"\n  tag: \"5.7-org\"\n\nfedora_esgz:\n  ref: \"ghcr.io/stargz-containers/fedora\"\n  tag: \"30-esgz\"\n\nffmpeg_soci:\n  ref: \"public.ecr.aws/soci-workshop-examples/ffmpeg\"\n  tag: \"latest\"\n\n# Large enough for testing soci index creation\nubuntu:\n  ref: \"public.ecr.aws/docker/library/ubuntu\"\n  tag: \"23.10\"\n\ncoredns:\n  ref: \"public.ecr.aws/eks-distro/coredns/coredns\"\n  tag: \"v1.12.2-eks-1-31-latest\"\n\n# Future: images to add or update soon.\n# busybox:1.37.0@sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f\n# debian:bookworm-slim@sha256:b1211f6d19afd012477bd34fdcabb6b663d680e0f4b0537da6e6b0fd057a3ec3\n# gitlab/gitlab-ee:17.11.0-ee.0@sha256:e0d9d5e0d0068f4b4bac3e15eb48313b5c3bb508425645f421bf2773a964c4ae\n# bitnami/harbor-portal:v2.13.0@sha256:636f39610b359369aeeddd7859cb56274d9a1bc3e467e21d74ea89e1516c1a0c\n# mariadb:11.7.2@sha256:81e893032978c4bf8ad43710b7a979774ed90787fa32d199162148ce28fe3b76\n# nginx:alpine3.21@sha256:65645c7bb6a0661892a8b03b89d0743208a18dd2f3f17a54ef4b76fb8e2f2a10\n# wordpress:6.8.0-php8.4-fpm-alpine@sha256:309b64fa4266d8a3fe6f0973ae3172fec1023c9b18242ccf1dffbff5dc8b81a8\n# Right now, v3 is breaking tests.\n# ghcr.io/distribution/distribution:3.0.0@sha256:4ba3adf47f5c866e9a29288c758c5328ef03396cb8f5f6454463655fa8bc83e2\n"
  },
  {
    "path": "pkg/testutil/images_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"go.yaml.in/yaml/v3\"\n)\n\n//go:embed images.yaml\nvar rawImagesList string\n\nvar testImagesOnce sync.Once\n\ntype manifestInfo struct {\n\tConfig    string `yaml:\"config,omitempty\"`\n\tManifest  string `yaml:\"manifest,omitempty\"`\n\tMediaType string `yaml:\"mediatype,omitempty\"`\n\tRaw       string `yaml:\"raw,omitempty\"`\n}\n\ntype TestImage struct {\n\tRef           string                  `yaml:\"ref\"`\n\tTag           string                  `yaml:\"tag,omitempty\"`\n\tSchemaVersion int                     `yaml:\"schemaversion,omitempty\"`\n\tMediaType     string                  `yaml:\"mediatype,omitempty\"`\n\tDigest        string                  `yaml:\"digest,omitempty\"`\n\tVariants      []string                `yaml:\"variants,omitempty\"`\n\tManifests     map[string]manifestInfo `yaml:\"manifests,omitempty\"`\n}\n\nvar testImages map[string]TestImage\n\n// internal helper to lookup TestImage by key, panics if not found\nfunc lookup(key string) TestImage {\n\ttestImagesOnce.Do(func() {\n\t\tif err := yaml.Unmarshal([]byte(rawImagesList), &testImages); err != nil {\n\t\t\tfmt.Printf(\"Error unmarshaling test images YAML file: %v\\n\", err)\n\t\t\tpanic(\"testing is broken\")\n\t\t}\n\t})\n\tim, ok := testImages[key]\n\tif !ok {\n\t\tfmt.Printf(\"Image %s was not found in images list\\n\", key)\n\t\tpanic(\"testing is broken\")\n\t}\n\treturn im\n}\n\nfunc GetTestImage(key string) string {\n\tim := lookup(key)\n\treturn im.Ref + \":\" + im.Tag\n}\n\nfunc GetTestImageWithoutTag(key string) string {\n\tim := lookup(key)\n\treturn im.Ref\n}\n\nfunc GetTestImageConfigDigest(key, platform string) string {\n\tim := lookup(key)\n\tpd, ok := im.Manifests[platform]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"platform %s not found for image %s\", platform, key))\n\t}\n\treturn pd.Config\n}\n\nfunc GetTestImageManifestDigest(key, platform string) string {\n\tim := lookup(key)\n\tpd, ok := im.Manifests[platform]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"platform %s not found for image %s\", platform, key))\n\t}\n\treturn pd.Manifest\n}\n\nfunc GetTestImageDigest(key string) string {\n\tim := lookup(key)\n\treturn im.Digest\n}\n\nfunc GetTestImageMediaType(key string) string {\n\tim := lookup(key)\n\treturn im.MediaType\n}\n\nfunc GetTestImageSchemaVersion(key string) int {\n\tim := lookup(key)\n\treturn im.SchemaVersion\n}\n\nfunc GetTestImagePlatformMediaType(key, platform string) string {\n\tim := lookup(key)\n\tpd, ok := im.Manifests[platform]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"platform %s not found for image %s\", platform, key))\n\t}\n\treturn pd.MediaType\n}\n\nfunc GetTestImageRaw(key, platform string) string {\n\tim := lookup(key)\n\tpd, ok := im.Manifests[platform]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"platform %s not found for image %s\", platform, key))\n\t}\n\treturn pd.Raw\n}\n"
  },
  {
    "path": "pkg/testutil/iptables/iptables_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage iptables\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/coreos/go-iptables/iptables\"\n)\n\n// ForwardExists check that at least 2 rules are present in the CNI-HOSTPORT-DNAT chain\n// and checks for regex matches in the list of rules\nfunc ForwardExists(t *testing.T, ipt *iptables.IPTables, chain, containerIP string, port int) bool {\n\trules, err := ipt.List(\"nat\", chain)\n\tif err != nil {\n\t\tt.Logf(\"error listing rules in chain: %q\\n\", err)\n\t\treturn false\n\t}\n\n\tif len(rules) < 1 {\n\t\tt.Logf(\"not enough rules: %d\", len(rules))\n\t\treturn false\n\t}\n\n\t// here we check if at least one of the rules in the chain\n\t// matches the required string to identify that the rule was applied\n\tfound := false\n\tmatchRule := `--dport ` + fmt.Sprintf(\"%d\", port) + ` .+ --to-destination ` + containerIP\n\tfor _, rule := range rules {\n\t\tfoundInRule, err := regexp.MatchString(matchRule, rule)\n\t\tif err != nil {\n\t\t\tt.Logf(\"error in match string: %q\\n\", err)\n\t\t\treturn false\n\t\t}\n\t\tif foundInRule {\n\t\t\tfound = foundInRule\n\t\t}\n\t}\n\treturn found\n}\n\n// GetRedirectedChain returns the chain where the traffic is being redirected.\n// This is how libcni manage its port maps.\n// Suppose you have the following rule:\n// -A CNI-HOSTPORT-DNAT -p tcp -m comment --comment \"dnat name: \\\"bridge\\\" id: \\\"default-YYYYYY\\\"\" -m multiport --dports 9999 -j CNI-DN-XXXXXX\n// So the chain where the traffic is redirected is CNI-DN-XXXXXX\n// Returns an empty string in case nothing was found.\nfunc GetRedirectedChain(t *testing.T, ipt *iptables.IPTables, chain, namespace, containerID string) string {\n\trules, err := ipt.List(\"nat\", chain)\n\tif err != nil {\n\t\tt.Logf(\"error listing rules in chain: %q\\n\", err)\n\t\treturn \"\"\n\t}\n\n\tif len(rules) < 1 {\n\t\tt.Logf(\"not enough rules: %d\", len(rules))\n\t\treturn \"\"\n\t}\n\n\tvar redirectedChain string\n\tre := regexp.MustCompile(`-j\\s+([^ ]+)`)\n\tfor _, rule := range rules {\n\t\t// first we verify the comment section is present: \"dnat name: \\\"bridge\\\" id: \\\"default-YYYYYY\\\"\"\n\t\tmatchesContainer, err := regexp.MatchString(namespace+\"-\"+containerID, rule)\n\t\tif err != nil {\n\t\t\tt.Logf(\"error in match string: %q\\n\", err)\n\t\t\treturn \"\"\n\t\t}\n\t\tif matchesContainer {\n\t\t\t// then we find the appropriate chain in the rule\n\t\t\tmatches := re.FindStringSubmatch(rule)\n\t\t\tfmt.Println(matches)\n\t\t\tif len(matches) >= 2 {\n\t\t\t\tredirectedChain = matches[1]\n\t\t\t}\n\t\t}\n\t}\n\tif redirectedChain == \"\" {\n\t\tt.Logf(\"no redirectced chain found\")\n\t\treturn \"\"\n\t}\n\treturn redirectedChain\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/ambient.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport \"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\nfunc environmentHasIPv6() bool {\n\treturn testutil.GetEnableIPv6()\n}\n\nfunc environmentHasKubernetes() bool {\n\treturn testutil.GetEnableKubernetes()\n}\n\nfunc environmentIsForFlaky() bool {\n\treturn testutil.GetFlakyEnvironment()\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/command.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform\"\n)\n\nconst defaultNamespace = testutil.Namespace\n\n// IMPORTANT note on file writing here:\n// Inside the context of a single test, there is no concurrency, as setup, command and cleanup operate in sequence\n// Furthermore, the tempdir is private by definition.\n// Writing files here in a non-safe manner is thus OK.\n\nfunc getTarget() string {\n\t// Indirecting to testutil for now\n\treturn testutil.GetTarget()\n}\n\nfunc isTargetNerdish() bool {\n\treturn !strings.HasPrefix(filepath.Base(testutil.GetTarget()), \"docker\")\n}\n\nfunc newNerdCommand(conf test.Config, t tig.T) *nerdCommand {\n\t// Decide what binary we are running\n\tvar err error\n\tvar binary string\n\ttrgt := getTarget()\n\n\tbinary, err = exec.LookPath(trgt)\n\tif err != nil {\n\t\tt.Log(fmt.Sprintf(\"unable to find binary %q: %v\", trgt, err))\n\t\tt.FailNow()\n\t}\n\n\tif isTargetNerdish() {\n\t\t// Any target but docker is considered a nerdctl variant, either gomodjail, or homegrown cli based on the\n\t\t// nerdctl codebase as a library.\n\t\t// Set the default namespace if we do not have one explicitly set already\n\t\tif conf.Read(Namespace) == \"\" {\n\t\t\tconf.Write(Namespace, defaultNamespace)\n\t\t}\n\t} else {\n\t\tif err = exec.Command(binary, \"compose\", \"version\").Run(); err != nil {\n\t\t\tt.Log(fmt.Sprintf(\"docker does not support compose: %v\", err))\n\t\t\tt.FailNow()\n\t\t}\n\t}\n\n\t// Create the base command, with the right binary, t\n\tret := &nerdCommand{\n\t\tGenericCommand: *(test.NewGenericCommand().(*test.GenericCommand)),\n\t}\n\n\tret.WithBinary(binary)\n\tret.WithWhitelist([]string{\n\t\t\"PATH\",\n\t\t\"HOME\",\n\t\t\"XDG_*\",\n\t\t// Windows needs ProgramData, AppData, etc\n\t\t\"Program*\",\n\t\t\"PROGRAM*\",\n\t\t\"APPDATA\",\n\t})\n\treturn ret\n}\n\ntype nerdCommand struct {\n\ttest.GenericCommand\n\n\thasWrittenToml         bool\n\thasWrittenDockerConfig bool\n}\n\nfunc (nc *nerdCommand) Run(expect *test.Expected) {\n\tnc.T().Helper()\n\tnc.prep()\n\tif !isTargetNerdish() {\n\t\t// We are not in the business of testing docker *error* output, so, spay expectation here\n\t\tif expect != nil {\n\t\t\texpect.Errors = nil\n\t\t}\n\t}\n\tnc.GenericCommand.Run(expect)\n}\n\nfunc (nc *nerdCommand) Background() {\n\tnc.prep()\n\tnc.GenericCommand.Background()\n}\n\n// Run does override the generic command run, as we are testing both docker and nerdctl\nfunc (nc *nerdCommand) prep() {\n\tnc.T().Helper()\n\n\t// If no DOCKER_CONFIG was explicitly provided, set ourselves inside the current working directory\n\tif nc.Env[\"DOCKER_CONFIG\"] == \"\" {\n\t\tnc.Env[\"DOCKER_CONFIG\"] = nc.GenericCommand.TempDir\n\t}\n\n\tif customDCConfig := nc.GenericCommand.Config.Read(DockerConfig); customDCConfig != \"\" {\n\t\tif !nc.hasWrittenDockerConfig {\n\t\t\tdest := filepath.Join(nc.Env[\"DOCKER_CONFIG\"], \"config.json\")\n\t\t\terr := filesystem.WriteFile(dest, []byte(customDCConfig), test.FilePermissionsDefault)\n\t\t\tassert.NilError(nc.T(), err, \"failed to write custom docker config json file for test\")\n\t\t\tnc.hasWrittenDockerConfig = true\n\t\t}\n\t}\n\n\t// For Docker, honor log level and return\n\tif !isTargetNerdish() {\n\t\t// Allow debugging with docker syntax\n\t\tif nc.Config.Read(Debug) != \"\" {\n\t\t\tnc.PrependArgs(\"--log-level=debug\")\n\t\t}\n\t\treturn\n\t}\n\n\t// nerd-ish cli get the following additionally.\n\n\t// Set the namespace\n\tif nc.Config.Read(Namespace) != \"\" {\n\t\tnc.PrependArgs(\"--namespace=\" + string(nc.Config.Read(Namespace)))\n\t}\n\n\tif nc.Config.Read(stargz) == enabled {\n\t\tnc.Env[\"CONTAINERD_SNAPSHOTTER\"] = \"stargz\"\n\t}\n\n\tif nc.Config.Read(ipfs) == enabled {\n\t\tvar ipfsPath string\n\t\tif rootlessutil.IsRootless() {\n\t\t\tvar err error\n\t\t\tipfsPath, err = platform.DataHome()\n\t\t\tipfsPath = filepath.Join(ipfsPath, \"ipfs\")\n\t\t\tassert.NilError(nc.T(), err)\n\t\t} else {\n\t\t\tipfsPath = filepath.Join(os.Getenv(\"HOME\"), \".ipfs\")\n\t\t}\n\n\t\tnc.Env[\"IPFS_PATH\"] = ipfsPath\n\t}\n\n\t// If no NERDCTL_TOML was explicitly provided, set it to the private dir\n\tif nc.Env[\"NERDCTL_TOML\"] == \"\" {\n\t\tnc.Env[\"NERDCTL_TOML\"] = filepath.Join(nc.GenericCommand.TempDir, \"nerdctl.toml\")\n\t}\n\n\t// If we have custom toml content, write it if it does not exist already\n\tif nc.Config.Read(NerdctlToml) != \"\" {\n\t\tif !nc.hasWrittenToml {\n\t\t\tdest := nc.Env[\"NERDCTL_TOML\"]\n\t\t\terr := filesystem.WriteFile(dest, []byte(nc.Config.Read(NerdctlToml)), test.FilePermissionsDefault)\n\t\t\tassert.NilError(nc.T(), err, \"failed to write NerdctlToml\")\n\t\t\tnc.hasWrittenToml = true\n\t\t}\n\t}\n\n\tif nc.Config.Read(HostsDir) != \"\" {\n\t\tnc.PrependArgs(\"--hosts-dir=\" + string(nc.Config.Read(HostsDir)))\n\t}\n\tif nc.Config.Read(DataRoot) != \"\" {\n\t\tnc.PrependArgs(\"--data-root=\" + string(nc.Config.Read(DataRoot)))\n\t}\n\tif nc.Config.Read(Debug) != \"\" {\n\t\tnc.PrependArgs(\"--debug-full\")\n\t}\n}\n\nfunc (nc *nerdCommand) Clone() test.TestableCommand {\n\treturn &nerdCommand{\n\t\tGenericCommand:         *(nc.GenericCommand.Clone().(*test.GenericCommand)),\n\t\thasWrittenToml:         nc.hasWrittenToml,\n\t\thasWrittenDockerConfig: nc.hasWrittenDockerConfig,\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/hoststoml/hoststoml.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage hoststoml\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"github.com/pelletier/go-toml/v2\"\n)\n\ntype hostsTomlHost struct {\n\tCA         string     `toml:\"ca,omitempty\"`\n\tSkipVerify bool       `toml:\"skip_verify,omitempty\"`\n\tClient     [][]string `toml:\"client,omitempty\"`\n}\n\n// See https://github.com/containerd/containerd/blob/main/docs/hosts.md\ntype HostsToml struct {\n\tCA         string                    `toml:\"ca,omitempty\"`\n\tSkipVerify bool                      `toml:\"skip_verify,omitempty\"`\n\tClient     [][]string                `toml:\"client,omitempty\"`\n\tHeaders    map[string]string         `toml:\"header,omitempty\"`\n\tServer     string                    `toml:\"server,omitempty\"`\n\tEndpoints  map[string]*hostsTomlHost `toml:\"host,omitempty\"`\n}\n\nfunc (ht *HostsToml) Save(dir string, hostIP string, port int) error {\n\tvar err error\n\tvar r *os.File\n\n\thostSubDir := hostIP\n\tif port != 0 {\n\t\thostSubDir = net.JoinHostPort(hostIP, strconv.Itoa(port))\n\t}\n\n\thostsSubDir := filepath.Join(dir, hostSubDir)\n\terr = os.MkdirAll(hostsSubDir, 0700)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r, err = os.Create(filepath.Join(dir, hostSubDir, \"hosts.toml\")); err == nil {\n\t\tdefer r.Close()\n\t\tenc := toml.NewEncoder(r)\n\t\tenc.SetIndentTables(true)\n\t\terr = enc.Encode(ht)\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/platform/platform_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platform\n\nfunc DataHome() (string, error) {\n\tpanic(\"not supported\")\n}\n\nvar (\n\t// The following are here solely for darwin to compile / lint. They are not used, as the corresponding tests are running only on linux.\n\tRegistryImageStable = \"registry:2\"\n\tKuboImage           = \"ipfs/kubo:v0.16.0\"\n\tDockerAuthImage     = \"cesanta/docker_auth:1.7\"\n)\n"
  },
  {
    "path": "pkg/testutil/nerdtest/platform/platform_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platform\n\nfunc DataHome() (string, error) {\n\tpanic(\"not supported\")\n}\n\nvar (\n\t// The following are here solely for freebsd to compile / lint. They are not used, as the corresponding tests are running only on linux.\n\tRegistryImageStable = \"registry:2\"\n\tKuboImage           = \"ipfs/kubo:v0.16.0\"\n\tDockerAuthImage     = \"cesanta/docker_auth:1.7\"\n)\n"
  },
  {
    "path": "pkg/testutil/nerdtest/platform/platform_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platform\n\nimport (\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nfunc DataHome() (string, error) {\n\treturn rootlessutil.XDGDataHome()\n}\n\nvar (\n\tRegistryImageStable = testutil.RegistryImageStable\n\tKuboImage           = testutil.KuboImage\n\tDockerAuthImage     = testutil.DockerAuthImage\n)\n"
  },
  {
    "path": "pkg/testutil/nerdtest/platform/platform_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage platform\n\nfunc DataHome() (string, error) {\n\tpanic(\"not supported\")\n}\n\nvar (\n\tRegistryImageStable = \"there-is-no-such-test-on-windows\"\n\tKuboImage           = \"there-is-no-such-test-on-windows\"\n\tDockerAuthImage     = \"there-is-no-such-test-on-windows\"\n)\n"
  },
  {
    "path": "pkg/testutil/nerdtest/registry/cesanta.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage registry\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.yaml.in/yaml/v3\"\n\t\"golang.org/x/crypto/bcrypt\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\t\"github.com/containerd/nerdctl/mod/tigron/utils/testca\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\ntype CesantaConfigServer struct {\n\tAddr        string `yaml:\"addr,omitempty\"`\n\tCertificate string\n\tKey         string\n}\n\ntype CesantaConfigToken struct {\n\tIssuer      string `yaml:\"issuer,omitempty\"`\n\tCertificate string `yaml:\"certificate,omitempty\"`\n\tKey         string `yaml:\"key,omitempty\"`\n\tExpiration  int    `yaml:\"expiration,omitempty\"`\n}\n\ntype CesantaConfigUser struct {\n\tPassword string `yaml:\"password,omitempty\"`\n}\n\ntype CesantaMatchConditions struct {\n\tAccount string `yaml:\"account,omitempty\"`\n}\n\ntype CesantaConfigACLEntry struct {\n\tMatch   CesantaMatchConditions `yaml:\"match\"`\n\tActions []string               `yaml:\"actions,flow\"`\n}\n\ntype CesantaConfigACL []CesantaConfigACLEntry\n\ntype CesantaConfig struct {\n\tServer CesantaConfigServer          `yaml:\"server\"`\n\tToken  CesantaConfigToken           `yaml:\"token\"`\n\tUsers  map[string]CesantaConfigUser `yaml:\"users,omitempty\"`\n\tACL    CesantaConfigACL             `yaml:\"acl,omitempty\"`\n}\n\nfunc (cc *CesantaConfig) Save(path string) error {\n\tvar err error\n\tvar r *os.File\n\tif r, err = os.Create(path); err == nil {\n\t\tdefer r.Close()\n\t\terr = yaml.NewEncoder(r).Encode(cc)\n\t}\n\treturn err\n}\n\n// FIXME: this is a copy of the utility method EnsureContainerStarted\n// We cannot reference it (circular dep), so the copy.\n// To be fixed later when we will be done migrating test helpers to the new framework and we can split them\n// in meaningful subpackages.\n\nfunc ensureContainerStarted(helpers test.Helpers, con string) {\n\tstarted := false\n\tfor i := 0; i < 5 && !started; i++ {\n\t\thelpers.Command(\"container\", \"inspect\", con).\n\t\t\tRun(&test.Expected{\n\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tvar dc []dockercompat.Container\n\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\tif err != nil || len(dc) == 0 {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tassert.Equal(t, len(dc), 1, \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\tstarted = dc[0].State.Running\n\t\t\t\t},\n\t\t\t})\n\t\ttime.Sleep(time.Second)\n\t}\n\n\tif !started {\n\t\tins := helpers.Capture(\"container\", \"inspect\", con)\n\t\tlgs := helpers.Capture(\"logs\", con)\n\t\tps := helpers.Capture(\"ps\", \"-a\")\n\t\thelpers.T().Log(ins)\n\t\thelpers.T().Log(lgs)\n\t\thelpers.T().Log(ps)\n\t\thelpers.T().Log(fmt.Sprintf(\"container %s still not running after %d retries\", con, 5))\n\t\thelpers.T().FailNow()\n\t}\n}\n\nfunc NewCesantaAuthServer(data test.Data, helpers test.Helpers, ca *testca.Cert, port int, user, pass string, tls bool) *TokenAuthServer {\n\t// listen on 0.0.0.0 to enable 127.0.0.1\n\tlistenIP := net.ParseIP(\"0.0.0.0\")\n\thostIP, err := nettestutil.NonLoopbackIPv4()\n\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed finding ipv4 non loopback interface: %w\", err))\n\tbpass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)\n\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed bcrypt encrypting password: %w\", err))\n\t// Prepare configuration file for authentication server\n\t// Details: https://github.com/cesanta/docker_auth/blob/1.7.1/examples/simple.yml\n\tcc := &CesantaConfig{\n\t\tServer: CesantaConfigServer{\n\t\t\tAddr: \":5100\",\n\t\t},\n\t\tToken: CesantaConfigToken{\n\t\t\tIssuer:     \"Cesanta auth server\",\n\t\t\tExpiration: 900,\n\t\t},\n\t\tUsers: map[string]CesantaConfigUser{\n\t\t\tuser: {\n\t\t\t\tPassword: string(bpass),\n\t\t\t},\n\t\t},\n\t\tACL: CesantaConfigACL{\n\t\t\t{\n\t\t\t\tMatch: CesantaMatchConditions{\n\t\t\t\t\tAccount: user,\n\t\t\t\t},\n\t\t\t\tActions: []string{\"*\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tscheme := \"http\"\n\tif tls {\n\t\tscheme = \"https\"\n\t\tcc.Server.Certificate = \"/auth/domain.crt\"\n\t\tcc.Server.Key = \"/auth/domain.key\"\n\t} else {\n\t\tcc.Token.Certificate = \"/auth/domain.crt\"\n\t\tcc.Token.Key = \"/auth/domain.key\"\n\t}\n\n\tconfigFileName := data.Temp().Path(\"authconfig\")\n\terr = cc.Save(configFileName)\n\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed writing configuration: %w\", err))\n\n\tcert := ca.GenerateServerX509(data, helpers, hostIP.String())\n\t// FIXME: this will fail in many circumstances. Review strategy on how to acquire a free port.\n\t// We probably have better code for that already somewhere.\n\tport, err = portlock.Acquire(port)\n\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed acquiring port: %w\", err))\n\tcontainerName := data.Identifier(fmt.Sprintf(\"cesanta-auth-server-%d-%t\", port, tls))\n\t// Cleanup possible leftovers first\n\thelpers.Ensure(\"rm\", \"-f\", containerName)\n\n\tcleanup := func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\"rm\", \"-f\", containerName)\n\t\terrPortRelease := portlock.Release(port)\n\t\tif errPortRelease != nil {\n\t\t\thelpers.T().Log(fmt.Sprintf(\"Failed to release port %d: %s\", port, errPortRelease))\n\t\t}\n\t}\n\n\tsetup := func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(\n\t\t\t\"run\",\n\t\t\t\"--pull=never\",\n\t\t\t\"-d\",\n\t\t\t\"-p\", fmt.Sprintf(\"%s:%d:5100\", listenIP, port),\n\t\t\t\"--name\", containerName,\n\t\t\t\"-v\", cert.CertPath+\":/auth/domain.crt\",\n\t\t\t\"-v\", cert.KeyPath+\":/auth/domain.key\",\n\t\t\t\"-v\", configFileName+\":/config/auth_config.yml\",\n\t\t\tplatform.DockerAuthImage,\n\t\t\t\"/config/auth_config.yml\",\n\t\t)\n\t\tensureContainerStarted(helpers, containerName)\n\t\t_, err = nettestutil.HTTPGet(fmt.Sprintf(\"%s://%s/auth\",\n\t\t\tscheme,\n\t\t\tnet.JoinHostPort(hostIP.String(), strconv.Itoa(port)),\n\t\t),\n\t\t\t5,\n\t\t\ttrue)\n\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed starting auth container in a timely manner: %w\", err))\n\n\t}\n\n\treturn &TokenAuthServer{\n\t\tIP:       hostIP,\n\t\tPort:     port,\n\t\tScheme:   scheme,\n\t\tCertPath: cert.CertPath,\n\t\tAuth: &TokenAuth{\n\t\t\tAddress:  scheme + \"://\" + net.JoinHostPort(hostIP.String(), strconv.Itoa(port)),\n\t\t\tCertPath: cert.CertPath,\n\t\t},\n\t\tSetup:   setup,\n\t\tCleanup: cleanup,\n\t\tLogs: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.T().Log(helpers.Err(\"logs\", containerName))\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/registry/common.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage registry\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n)\n\n// Auth describes a struct able to serialize authenticator information into arguments to be fed to a registry container run\ntype Auth interface {\n\tParams(data test.Data) []string\n}\n\ntype NoAuth struct {\n}\n\nfunc (na *NoAuth) Params(data test.Data) []string {\n\treturn []string{}\n}\n\ntype TokenAuth struct {\n\tAddress  string\n\tCertPath string\n}\n\n// FIXME: this is specific to Docker Registry\n// Like need something else for Harbor and Gitlab\nfunc (ta *TokenAuth) Params(data test.Data) []string {\n\treturn []string{\n\t\t\"--env\", \"REGISTRY_AUTH=token\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_REALM=\" + ta.Address + \"/auth\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_SERVICE=Docker registry\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_ISSUER=Cesanta auth server\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/auth/domain.crt\",\n\t\t\"-v\", ta.CertPath + \":/auth/domain.crt\",\n\t}\n}\n\ntype BasicAuth struct {\n\tRealm    string\n\tHtFile   string\n\tUsername string\n\tPassword string\n}\n\nfunc (ba *BasicAuth) Params(data test.Data) []string {\n\tif ba.Realm == \"\" {\n\t\tba.Realm = \"Basic Realm\"\n\t}\n\tif ba.HtFile == \"\" && ba.Username != \"\" && ba.Password != \"\" {\n\t\tpass := ba.Password\n\t\tencryptedPass, _ := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)\n\t\tba.HtFile = data.Temp().Save(fmt.Sprintf(`%s:%s`, ba.Username, string(encryptedPass[:])), \"htpasswd\")\n\t}\n\tret := []string{\n\t\t\"--env\", \"REGISTRY_AUTH=htpasswd\",\n\t\t\"--env\", \"REGISTRY_AUTH_HTPASSWD_REALM=\" + ba.Realm,\n\t\t\"--env\", \"REGISTRY_AUTH_HTPASSWD_PATH=/htpasswd\",\n\t}\n\tif ba.HtFile != \"\" {\n\t\tret = append(ret, \"-v\", ba.HtFile+\":/htpasswd\")\n\t}\n\treturn ret\n}\n\ntype TokenAuthServer struct {\n\tScheme   string\n\tIP       net.IP\n\tPort     int\n\tCertPath string\n\tCleanup  func(data test.Data, helpers test.Helpers)\n\tSetup    func(data test.Data, helpers test.Helpers)\n\tLogs     func(data test.Data, helpers test.Helpers)\n\tAuth     Auth\n}\n\ntype Server struct {\n\tScheme   string\n\tIP       net.IP\n\tPort     int\n\tCleanup  func(data test.Data, helpers test.Helpers)\n\tSetup    func(data test.Data, helpers test.Helpers)\n\tLogs     func(data test.Data, helpers test.Helpers)\n\tHostsDir string // contains \"<HostIP>:<ListenPort>/hosts.toml\"\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/registry/docker.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage registry\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/utils/testca\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/hoststoml\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\nfunc NewDockerRegistry(data test.Data, helpers test.Helpers, currentCA *testca.Cert, port int, auth Auth) *Server {\n\t// listen on 0.0.0.0 to enable 127.0.0.1\n\tlistenIP := net.ParseIP(\"0.0.0.0\")\n\thostIP, err := nettestutil.NonLoopbackIPv4()\n\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed finding ipv4 non loopback interface: %w\", err))\n\t// XXX RELEASE PORT IN CLEANUP HERE\n\t// FIXME: this will fail in many circumstances. Review strategy on how to acquire a free port.\n\t// We probably have better code for that already somewhere.\n\tport, err = portlock.Acquire(port)\n\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed acquiring port: %w\", err))\n\n\tcontainerName := data.Identifier(fmt.Sprintf(\"docker-registry-server-%d-%t\", port, currentCA != nil))\n\t// Cleanup possible leftovers first\n\thelpers.Ensure(\"rm\", \"-f\", containerName)\n\n\targs := []string{\n\t\t\"run\",\n\t\t\"--pull=never\",\n\t\t\"-d\",\n\t\t\"-p\", fmt.Sprintf(\"%s:%d:5000\", listenIP, port),\n\t\t\"--name\", containerName,\n\t}\n\tscheme := \"http\"\n\tvar cert *testca.Cert\n\tif currentCA != nil {\n\t\tscheme = \"https\"\n\t\tcert = currentCA.GenerateServerX509(data, helpers, hostIP.String(), \"127.0.0.1\", \"localhost\", \"::1\")\n\t\targs = append(args,\n\t\t\t\"--env\", \"REGISTRY_HTTP_TLS_CERTIFICATE=/registry/domain.crt\",\n\t\t\t\"--env\", \"REGISTRY_HTTP_TLS_KEY=/registry/domain.key\",\n\t\t\t\"-v\", cert.CertPath+\":/registry/domain.crt\",\n\t\t\t\"-v\", cert.KeyPath+\":/registry/domain.key\",\n\t\t)\n\t}\n\n\t// Attach authentication params returns by authenticator\n\targs = append(args, auth.Params(data)...)\n\n\tregistryImage := platform.RegistryImageStable\n\targs = append(args, registryImage)\n\n\tcleanup := func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", containerName)\n\t\terrPortRelease := portlock.Release(port)\n\n\t\tassert.NilError(helpers.T(), errPortRelease, fmt.Errorf(\"failed releasing port: %w\", err))\n\t}\n\n\t// FIXME: in the future, we will want to further manipulate hosts toml file from the test\n\t// This should then return the struct, instead of saving it on its own\n\thostsDir, err := func() (string, error) {\n\t\thDir := data.Temp().Dir(\"certs.d\")\n\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating directory certs.d: %w\", err))\n\n\t\tif currentCA != nil {\n\t\t\thostTomlContent := &hoststoml.HostsToml{\n\t\t\t\tCA: currentCA.CertPath,\n\t\t\t}\n\n\t\t\terr = hostTomlContent.Save(hDir, hostIP.String(), port)\n\t\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating hosts.toml file: %w\", err))\n\n\t\t\terr = hostTomlContent.Save(hDir, \"127.0.0.1\", port)\n\t\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating hosts.toml file: %w\", err))\n\n\t\t\terr = hostTomlContent.Save(hDir, \"localhost\", port)\n\t\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating hosts.toml file: %w\", err))\n\n\t\t\tif port == 443 {\n\t\t\t\terr = hostTomlContent.Save(hDir, hostIP.String(), 0)\n\t\t\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating hosts.toml file: %w\", err))\n\n\t\t\t\terr = hostTomlContent.Save(hDir, \"127.0.0.1\", 0)\n\t\t\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating hosts.toml file: %w\", err))\n\n\t\t\t\terr = hostTomlContent.Save(hDir, \"localhost\", 0)\n\t\t\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed creating hosts.toml file: %w\", err))\n\n\t\t\t}\n\t\t}\n\n\t\treturn hDir, nil\n\t}()\n\n\tsetup := func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(args...)\n\t\tensureContainerStarted(helpers, containerName)\n\t\t_, err = nettestutil.HTTPGet(fmt.Sprintf(\"%s://%s/v2/\",\n\t\t\tscheme,\n\t\t\tnet.JoinHostPort(hostIP.String(), strconv.Itoa(port)),\n\t\t),\n\t\t\t5,\n\t\t\ttrue)\n\t\tassert.NilError(helpers.T(), err, fmt.Errorf(\"failed starting docker registry in a timely manner: %w\", err))\n\t}\n\n\treturn &Server{\n\t\tScheme:  scheme,\n\t\tIP:      hostIP,\n\t\tPort:    port,\n\t\tCleanup: cleanup,\n\t\tSetup:   setup,\n\t\tLogs: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.T().Log(helpers.Err(\"logs\", containerName))\n\t\t},\n\t\tHostsDir: hostsDir,\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/registry/kubo.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage registry\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/utils/testca\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n)\n\nfunc NewKuboRegistry(data test.Data, helpers test.Helpers, t *testing.T, currentCA *testca.Cert, port int, auth Auth) *Server {\n\t// listen on 0.0.0.0 to enable 127.0.0.1\n\tlistenIP := net.ParseIP(\"0.0.0.0\")\n\thostIP, err := nettestutil.NonLoopbackIPv4()\n\tassert.NilError(t, err, fmt.Errorf(\"failed finding ipv4 non loopback interface: %w\", err))\n\tport, err = portlock.Acquire(port)\n\tassert.NilError(t, err, fmt.Errorf(\"failed acquiring port: %w\", err))\n\n\tcontainerName := data.Identifier(fmt.Sprintf(\"kubo-registry-server-%d-%t\", port, currentCA != nil))\n\t// Cleanup possible leftovers first\n\thelpers.Ensure(\"rm\", \"-f\", containerName)\n\n\targs := []string{\n\t\t\"run\",\n\t\t\"--pull=never\",\n\t\t\"-d\",\n\t\t\"-p\", fmt.Sprintf(\"%s:%d:%d\", listenIP, port, port),\n\t\t\"--name\", containerName,\n\t\t\"--entrypoint=/bin/sh\",\n\t\tplatform.KuboImage,\n\t\t\"-c\", \"--\",\n\t\tfmt.Sprintf(\"ipfs init && ipfs config Addresses.API /ip4/0.0.0.0/tcp/%d && ipfs daemon --offline\", port),\n\t}\n\n\tscheme := \"http\"\n\n\tcleanup := func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Anyhow(\"rm\", \"-f\", containerName)\n\t\terrPortRelease := portlock.Release(port)\n\n\t\tassert.NilError(t, errPortRelease, fmt.Errorf(\"failed releasing port: %w\", err))\n\t}\n\n\tsetup := func(data test.Data, helpers test.Helpers) {\n\t\thelpers.Ensure(args...)\n\t\tensureContainerStarted(helpers, containerName)\n\t\t_, err = nettestutil.HTTPGet(fmt.Sprintf(\"%s://%s/api/v0\",\n\t\t\tscheme,\n\t\t\tnet.JoinHostPort(hostIP.String(), strconv.Itoa(port)),\n\t\t),\n\t\t\t5,\n\t\t\ttrue)\n\t\tlogs := helpers.Capture(\"logs\", containerName)\n\t\tassert.NilError(t, err, fmt.Errorf(\"failed starting kubo registry in a timely manner: %w - logs: %s\", err, logs))\n\t}\n\n\treturn &Server{\n\t\tIP:      hostIP,\n\t\tPort:    port,\n\t\tScheme:  scheme,\n\t\tCleanup: cleanup,\n\t\tSetup:   setup,\n\t\tLogs: func(data test.Data, helpers test.Helpers) {\n\t\t\thelpers.T().Log(helpers.Err(\"logs\", containerName))\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/requirements.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/clientutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/containerdutil\"\n\tncdefaults \"github.com/containerd/nerdctl/v2/pkg/defaults\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/netutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/snapshotterutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform\"\n)\n\nvar BuildkitHost test.ConfigKey = \"BuildkitHost\"\n\n// These are used for ambient requirements\nvar ipv6 test.ConfigKey = \"IPv6Test\"\nvar kubernetes test.ConfigKey = \"KubeTest\"\nvar flaky test.ConfigKey = \"FlakyTest\"\nvar only test.ConfigValue = \"Only\"\n\n// These are used for down the road configuration and custom behavior inside command\nvar modePrivate test.ConfigKey = \"PrivateMode\"\nvar stargz test.ConfigKey = \"Stargz\"\nvar ipfs test.ConfigKey = \"IPFS\"\nvar enabled test.ConfigValue = \"Enabled\"\n\n// OnlyIPv6 marks a test as suitable to be run exclusively inside an ipv6 environment\n// This is an ambient requirement\nvar OnlyIPv6 = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\thelpers.Write(ipv6, only)\n\t\tret = environmentHasIPv6()\n\t\tif !ret {\n\t\t\tmess = \"runner skips IPv6 compatible tests in the non-IPv6 environment\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\n// OnlyKubernetes marks a test as meant to be tested on Kubernetes\n// This is an ambient requirement\nvar OnlyKubernetes = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\thelpers.Write(kubernetes, only)\n\t\tif _, err := exec.LookPath(\"kubectl\"); err != nil {\n\t\t\treturn false, fmt.Sprintf(\"kubectl is not in the path: %+v\", err)\n\t\t}\n\t\tret = environmentHasKubernetes()\n\t\tif ret {\n\t\t\thelpers.Write(Namespace, \"k8s.io\")\n\t\t} else {\n\t\t\tmess = \"runner skips Kubernetes compatible tests in the non-Kubernetes environment\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\n// IsFlaky marks a test as randomly failing.\n// This is an ambient requirement\nvar IsFlaky = func(issueLink string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t\t// We do not even want to get to the setup phase here\n\t\t\thelpers.Write(flaky, only)\n\t\t\tret = environmentIsForFlaky()\n\t\t\tif !ret {\n\t\t\t\tmess = \"runner skips flaky compatible tests in the non-flaky environment\"\n\t\t\t}\n\t\t\treturn ret, mess\n\t\t},\n\t}\n}\n\n// Docker marks a test as suitable solely for Docker and not Nerdctl\n// Generally used as require.Not(nerdtest.Docker), which of course it the opposite\nvar Docker = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tret = !isTargetNerdish()\n\t\tif ret {\n\t\t\tmess = \"current target is docker\"\n\t\t} else {\n\t\t\tmess = \"current target is not docker\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\n// NerdctlNeedsFixing marks a test as unsuitable to be run for Nerdctl, because of a specific known issue which\n// url must be passed as an argument\nvar NerdctlNeedsFixing = func(issueLink string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t\tret = !isTargetNerdish()\n\t\t\tif ret {\n\t\t\t\tmess = \"current target is docker\"\n\t\t\t} else {\n\t\t\t\tmess = \"current target is nerdctl, but we will skip as nerdctl currently has issue: \" + issueLink\n\t\t\t}\n\t\t\treturn ret, mess\n\t\t},\n\t}\n}\n\n// BrokenTest marks a test as currently broken, with explanation provided in message, along with\n// additional requirements / restrictions describing what it can run on.\nvar BrokenTest = func(message string, req *test.Requirement) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\tret, mess := req.Check(data, helpers)\n\t\t\treturn ret, message + \"\\n\" + mess\n\t\t},\n\t\tSetup:   req.Setup,\n\t\tCleanup: req.Cleanup,\n\t}\n}\n\n// Rootless marks a test as suitable only for the rootless environment\nvar Rootless = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t// Make sure we DO not return \"IsRootless true\" for docker\n\t\tret = isTargetNerdish() && rootlessutil.IsRootless()\n\t\tif ret {\n\t\t\tmess = \"environment is root-less\"\n\t\t} else {\n\t\t\tmess = \"environment is root-ful\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\n// RootlessWithDetachNetNS marks a test as suitable only for rootless environment with detached netns support.\nvar RootlessWithDetachNetNS = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tns, err := rootlessutil.DetachedNetNS()\n\t\tif err != nil {\n\t\t\treturn false, fmt.Sprintf(\"failed to check for detached netns: %+v\", err)\n\t\t}\n\t\tif ns == \"\" {\n\t\t\treturn false, \"detached netns is not supported\"\n\t\t}\n\t\treturn true, \"detached netns is supported\"\n\t},\n}\n\n// RootlessWithoutDetachNetNS marks a test as suitable only for rootless environment without detached netns support.\n// i.e., RootlessKit v1.\nvar RootlessWithoutDetachNetNS = require.All(Rootless, require.Not(RootlessWithDetachNetNS))\n\n// Rootful marks a test as suitable only for rootful env\nvar Rootful = require.Not(Rootless)\n\n// Info requires that `nerdctl info` satisfies the condition function passed as argument.\nfunc Info(f func(dockercompat.Info) error) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t\tstdout := helpers.Capture(\"info\", \"--format\", \"{{ json . }}\")\n\t\t\tvar dinf dockercompat.Info\n\t\t\terr := json.Unmarshal([]byte(stdout), &dinf)\n\t\t\tif err != nil {\n\t\t\t\treturn false, fmt.Sprintf(\"failed to parse docker info: %v\", err)\n\t\t\t}\n\t\t\tif err := f(dinf); err != nil {\n\t\t\t\treturn false, err.Error()\n\t\t\t}\n\t\t\treturn true, \"\"\n\t\t},\n\t}\n}\n\n// CGroup requires that cgroup is enabled\nvar CGroup = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tret = true\n\t\tmess = \"cgroup is enabled\"\n\t\tstdout := helpers.Capture(\"info\", \"--format\", \"{{ json . }}\")\n\t\tvar dinf dockercompat.Info\n\t\terr := json.Unmarshal([]byte(stdout), &dinf)\n\t\tassert.NilError(helpers.T(), err, \"failed to parse docker info\")\n\t\tswitch dinf.CgroupDriver {\n\t\tcase \"none\", \"\":\n\t\t\tret = false\n\t\t\tmess = \"cgroup is none\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\nvar CgroupsAccessible = require.All(\n\tCGroup,\n\t&test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t\tisRootLess := isTargetNerdish() && rootlessutil.IsRootless()\n\t\t\tif isRootLess {\n\t\t\t\tstdout := helpers.Capture(\"info\", \"--format\", \"{{ json . }}\")\n\t\t\t\tvar dinf dockercompat.Info\n\t\t\t\terr := json.Unmarshal([]byte(stdout), &dinf)\n\t\t\t\tassert.NilError(helpers.T(), err, \"failed to parse docker info\")\n\t\t\t\treturn dinf.CgroupVersion == \"2\", \"we are rootless, and cgroup version is not 2\"\n\t\t\t}\n\t\t\treturn true, \"\"\n\t\t},\n\t},\n)\n\n// CGroupV2 requires that cgroup is enabled and cgroup version is 2\nvar CGroupV2 = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tret = true\n\t\tmess = \"cgroup is enabled\"\n\t\tstdout := helpers.Capture(\"info\", \"--format\", \"{{ json . }}\")\n\t\tvar dinf dockercompat.Info\n\t\terr := json.Unmarshal([]byte(stdout), &dinf)\n\t\tassert.NilError(helpers.T(), err, \"failed to parse docker info\")\n\t\tswitch dinf.CgroupDriver {\n\t\tcase \"none\", \"\":\n\t\t\tret = false\n\t\t\tmess = \"cgroup is none\"\n\t\t}\n\t\tif dinf.CgroupVersion != \"2\" {\n\t\t\tret = false\n\t\t\tmess = \"cgroup version is not 2\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\n// Soci requires that the soci snapshotter is enabled\nvar Soci = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tret = false\n\t\tmess = \"soci is not enabled\"\n\t\tstdout := helpers.Capture(\"info\", \"--format\", \"{{ json . }}\")\n\t\tvar dinf dockercompat.Info\n\t\terr := json.Unmarshal([]byte(stdout), &dinf)\n\t\tassert.NilError(helpers.T(), err, \"failed to parse docker info\")\n\t\tfor _, p := range dinf.Plugins.Storage {\n\t\t\tif p == \"soci\" {\n\t\t\t\tret = true\n\t\t\t\tmess = \"soci is enabled\"\n\t\t\t}\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\nvar Stargz = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tret = false\n\t\tmess = \"stargz is not enabled\"\n\t\tstdout := helpers.Capture(\"info\", \"--format\", \"{{ json . }}\")\n\t\tvar dinf dockercompat.Info\n\t\terr := json.Unmarshal([]byte(stdout), &dinf)\n\t\tassert.NilError(helpers.T(), err, \"failed to parse docker info\")\n\t\tfor _, p := range dinf.Plugins.Storage {\n\t\t\tif p == \"stargz\" {\n\t\t\t\tret = true\n\t\t\t\tmess = \"stargz is enabled\"\n\t\t\t}\n\t\t}\n\t\t// Need this to happen now for Cleanups to work\n\t\t// FIXME: we should be able to access the env (at least through helpers.Command().) instead of this gym\n\t\thelpers.Write(stargz, enabled)\n\t\treturn ret, mess\n\t},\n}\n\n// Registry marks a test as requiring a registry to be deployed\nvar Registry = require.All(\n\t// Registry requires Linux currently\n\trequire.Linux,\n\t(func() *test.Requirement {\n\t\t// Provisional: see note in cleanup\n\t\t// var reg *registry.Server\n\n\t\treturn &test.Requirement{\n\t\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\t\treturn true, \"\"\n\t\t\t},\n\t\t\tSetup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// Ensure we have registry images now, so that we can run --pull=never\n\t\t\t\t// This is useful for two reasons:\n\t\t\t\t// - if ghcr.io is out, we want to fail early\n\t\t\t\t// - when we start a large number of registries in subtests, no need to round-trip to ghcr everytime\n\t\t\t\t// This of course assumes that the subtests are NOT going to prune / rmi images\n\t\t\t\tregistryImage := platform.RegistryImageStable\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", registryImage)\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", platform.DockerAuthImage)\n\t\t\t\thelpers.Ensure(\"pull\", \"--quiet\", platform.KuboImage)\n\t\t\t},\n\t\t\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t\t\t// FIXME: figure out what to do with reg setup/cleanup routines\n\t\t\t\t// Provisionally, reg is available here in the closure\n\t\t\t},\n\t\t}\n\t})(),\n)\n\n// Build marks a test as suitable only if buildkitd is enabled (only tested for nerdctl obviously)\nvar Build = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t// FIXME: shouldn't we run buildkitd in a container? At least for testing, that would be so much easier than\n\t\t// against the host install\n\t\tret := true\n\t\tmess := \"buildkitd is enabled\"\n\n\t\tif isTargetNerdish() {\n\t\t\tnamespace := defaultNamespace\n\t\t\tif ns := helpers.Read(Namespace); ns != \"\" {\n\t\t\t\tnamespace = string(ns)\n\t\t\t}\n\n\t\t\tbkHostAddr, err := buildkitutil.GetBuildkitHost(namespace)\n\t\t\tif err != nil {\n\t\t\t\tret = false\n\t\t\t\tmess = fmt.Sprintf(\"buildkitd is not enabled: %+v\", err)\n\t\t\t\treturn ret, mess\n\t\t\t}\n\t\t\t// We also require the buildctl binary in the path\n\t\t\t_, err = exec.LookPath(\"buildctl\")\n\t\t\tif err != nil {\n\t\t\t\tret = false\n\t\t\t\tmess = fmt.Sprintf(\"buildctl is not in the path: %+v\", err)\n\t\t\t\treturn ret, mess\n\t\t\t}\n\t\t\thelpers.Write(BuildkitHost, test.ConfigValue(bkHostAddr))\n\t\t}\n\t\treturn ret, mess\n\t},\n\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\t// Previously, every build test was sequential, and was purging the build cache.\n\t\t// Running this in parallel of any other test depending on build will trash it.\n\t\t// The only way to parallelize any test involving build is indeed to disable this.\n\t\t// The price to pay is that we might get cache from another test.\n\t\t// This can be avoided individually by passing --no-cache if and when necessary\n\t\t// helpers.Anyhow(\"builder\", \"prune\", \"--all\", \"--force\")\n\t},\n}\n\nvar IPFS = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t// FIXME: we should be able to access the env (at least through helpers.Command().) instead of this gym\n\t\thelpers.Write(ipfs, enabled)\n\t\t// FIXME: this is incomplete. We obviously need a daemon running, properly configured\n\t\treturn require.Binary(\"ipfs\").Check(data, helpers)\n\t},\n}\n\n// Private makes a test run inside a dedicated namespace, with a private config.toml, hosts directory, and DOCKER_CONFIG path\n// If the target is docker, parallelism is forcefully disabled\nvar Private = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t// We need this to happen NOW and not in setup, as otherwise cleanup with operate on the default namespace\n\t\tnamespace := data.Identifier(\"private\")\n\t\thelpers.Write(Namespace, test.ConfigValue(namespace))\n\t\tdata.Labels().Set(\"_deletenamespace\", namespace)\n\t\t// FIXME: is this necessary? Should NoParallel be subsumed into config?\n\t\thelpers.Write(modePrivate, enabled)\n\t\treturn true, \"private mode creates a dedicated namespace for nerdctl, and disable parallelism for docker\"\n\t},\n\n\tCleanup: func(data test.Data, helpers test.Helpers) {\n\t\tif isTargetNerdish() {\n\t\t\t// FIXME: there are conditions where we still have some stuff in there and this fails...\n\t\t\tcontainerList := strings.TrimSpace(helpers.Capture(\"ps\", \"-aq\"))\n\t\t\tif containerList != \"\" {\n\t\t\t\thelpers.Ensure(append([]string{\"rm\", \"-f\"}, strings.Split(containerList, \"\\n\")...)...)\n\t\t\t}\n\t\t\thelpers.Ensure(\"system\", \"prune\", \"-f\", \"--all\", \"--volumes\")\n\t\t\thelpers.Anyhow(\"namespace\", \"remove\", data.Labels().Get(\"_deletenamespace\"))\n\t\t}\n\t},\n}\n\n// Gomodjail returns whether the binary is packed with gomodjail.\n// https://github.com/AkihiroSuda/gomodjail\nvar Gomodjail = &test.Requirement{\n\tCheck: func(_ test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t// FIXME: do not rely on the filename\n\t\tret = strings.HasSuffix(getTarget(), \".gomodjail\")\n\t\tif ret {\n\t\t\tmess = \"current target is packed with gomodjail\"\n\t\t} else {\n\t\t\tmess = \"current target is not packed with gomodjail\"\n\t\t}\n\t\treturn ret, mess\n\t},\n}\n\nvar AllowModifyUserns = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\tif testutil.GetAllowModifyUsers() {\n\t\t\treturn true, \"allow modify userns is enabled\"\n\t\t}\n\t\treturn false, \"allow modify userns is disabled\"\n\t},\n}\n\nvar RemapIDs = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\t// Create a cobra command for ProcessRootCmdFlags to get globalOptions\n\t\tctx := context.Background()\n\t\tsnapshotterName := defaults.DefaultSnapshotter\n\t\tnamespace := defaultNamespace\n\t\taddress := defaults.DefaultAddress\n\n\t\tclient, ctx, cancel, err := clientutil.NewClient(ctx, namespace, address)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Sprintf(\"failed to create client: %v\", err)\n\t\t}\n\t\tdefer cancel()\n\n\t\tcaps, err := client.GetSnapshotterCapabilities(ctx, snapshotterName)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Sprintf(\"failed to get snapshotter capabilities: %v\", err)\n\t\t}\n\n\t\tfor _, cap := range caps {\n\t\t\tif cap == \"remap-ids\" {\n\t\t\t\treturn true, \"snapshotter supports ID remapping\"\n\t\t\t}\n\t\t}\n\t\treturn false, \"snapshotter does not support ID remapping\"\n\t},\n}\n\n// SociVersion returns a requirement that checks if the installed SOCI version\n// meets the minimum required version\nfunc SociVersion(minVersion string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\t// Use the common CheckSociVersion function from snapshotterutil\n\t\t\terr := snapshotterutil.CheckSociVersion(minVersion)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err.Error()\n\t\t\t}\n\t\t\treturn true, fmt.Sprintf(\"soci version meets minimum requirement %s\", minVersion)\n\t\t},\n\t}\n}\n\nfunc ContainerdVersion(v string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\tctx := context.Background()\n\t\t\tnamespace := defaultNamespace\n\t\t\taddress := defaults.DefaultAddress\n\t\t\tclient, ctx, cancel, err := clientutil.NewClient(ctx, namespace, address)\n\t\t\tif err != nil {\n\t\t\t\treturn false, fmt.Sprintf(\"failed to create client: %v\", err)\n\t\t\t}\n\t\t\tdefer cancel()\n\t\t\tif sv, err := containerdutil.ServerSemVer(ctx, client); err != nil {\n\t\t\t\treturn false, err.Error()\n\t\t\t} else if sv.LessThan(semver.MustParse(v)) {\n\t\t\t\treturn false, fmt.Sprintf(\"`nerdctl commit --compression expects containerd %s or later, got containerd %v\", v, sv)\n\t\t\t}\n\t\t\treturn true, \"\"\n\t\t},\n\t}\n}\n\n// CNIFirewallVersion checks if the CNI firewall plugin version is greater than or equal to the specified version\nfunc CNIFirewallVersion(requiredVersion string) *test.Requirement {\n\treturn &test.Requirement{\n\t\tCheck: func(data test.Data, helpers test.Helpers) (bool, string) {\n\t\t\tcniPath := ncdefaults.CNIPath()\n\t\t\tfirewallPath := filepath.Join(cniPath, \"firewall\")\n\t\t\tok, err := netutil.FirewallPluginGEQVersion(firewallPath, requiredVersion)\n\t\t\tif err != nil {\n\t\t\t\treturn false, fmt.Sprintf(\"Failed to check CNI firewall version: %v\", err)\n\t\t\t}\n\n\t\t\tif !ok {\n\t\t\t\treturn false, fmt.Sprintf(\"CNI firewall plugin version is less than required version %s\", requiredVersion)\n\t\t\t}\n\n\t\t\treturn true, fmt.Sprintf(\"CNI firewall plugin version is greater than or equal to required version %s\", requiredVersion)\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/requirements_other.go",
    "content": "//go:build !windows\n\n/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n)\n\nvar HyperV = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\treturn false, \"HyperV is a windows-only feature\"\n\t},\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/requirements_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nvar HyperV = &test.Requirement{\n\tCheck: func(data test.Data, helpers test.Helpers) (ret bool, mess string) {\n\t\treturn testutil.HyperVSupported(), \"HyperV is not enabled, skipping test\"\n\t},\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/test.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n)\n\nvar DockerConfig test.ConfigKey = \"DockerConfig\"\nvar Namespace test.ConfigKey = \"Namespace\"\nvar NerdctlToml test.ConfigKey = \"NerdctlToml\"\nvar HostsDir test.ConfigKey = \"HostsDir\"\nvar DataRoot test.ConfigKey = \"DataRoot\"\nvar Debug test.ConfigKey = \"Debug\"\n\nfunc Setup() *test.Case {\n\ttest.Customize(&nerdctlSetup{})\n\treturn &test.Case{\n\t\tEnv: map[string]string{},\n\t}\n}\n\ntype nerdctlSetup struct {\n}\n\nfunc (ns *nerdctlSetup) CustomCommand(testCase *test.Case, t tig.T) test.CustomizableCommand {\n\treturn newNerdCommand(testCase.Config, t)\n}\n\nfunc (ns *nerdctlSetup) AmbientRequirements(testCase *test.Case, t tig.T) {\n\t// Ambient requirements, bail out now if these do not match\n\tif environmentHasIPv6() && testCase.Config.Read(ipv6) != only {\n\t\tt.Skip(\"runner skips non-IPv6 compatible tests in the IPv6 environment\")\n\t}\n\n\tif environmentHasKubernetes() && testCase.Config.Read(kubernetes) != only {\n\t\tt.Skip(\"runner skips non-Kubernetes compatible tests in the Kubernetes environment\")\n\t}\n\n\tif environmentIsForFlaky() && testCase.Config.Read(flaky) != only {\n\t\tt.Skip(\"runner skips non-flaky tests in the flaky environment\")\n\t}\n\n\tif !isTargetNerdish() && testCase.Config.Read(modePrivate) == enabled {\n\t\t// For docker, we do disable parallel since there is no namespace where we can isolate\n\t\ttestCase.NoParallel = true\n\t}\n\n\t// We do not want private to get inherited by subtests, as we want them to be in the same namespace set here\n\ttestCase.Config.Write(modePrivate, \"\")\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/third-party.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"os/exec\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/utils/testca\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry\"\n)\n\nfunc BuildCtlCommand(helpers test.Helpers, args ...string) test.TestableCommand {\n\tassert.Assert(helpers.T(), string(helpers.Read(BuildkitHost)) != \"\", \"You first need to Require Build to use buildctl\")\n\tbuildctl, _ := exec.LookPath(\"buildctl\")\n\tcmd := helpers.Custom(buildctl)\n\tcmd.WithArgs(\"--addr=\" + string(helpers.Read(BuildkitHost)))\n\tcmd.WithArgs(args...)\n\treturn cmd\n}\n\nfunc KubeCtlCommand(helpers test.Helpers, args ...string) test.TestableCommand {\n\tkubectl, _ := exec.LookPath(\"kubectl\")\n\tcmd := helpers.Custom(kubectl)\n\tcmd.WithArgs(\"--namespace=nerdctl-test-k8s\")\n\tcmd.WithArgs(args...)\n\treturn cmd\n}\n\nfunc RegistryWithTokenAuth(data test.Data, helpers test.Helpers, user, pass string, port int, tls bool) (*registry.Server, *registry.TokenAuthServer) {\n\trca := testca.NewX509(data, helpers)\n\tas := registry.NewCesantaAuthServer(data, helpers, rca, 0, user, pass, tls)\n\tre := registry.NewDockerRegistry(data, helpers, rca, port, as.Auth)\n\treturn re, as\n}\n\nfunc RegistryWithNoAuth(data test.Data, helpers test.Helpers, port int, tls bool) *registry.Server {\n\tvar rca *testca.Cert\n\tif tls {\n\t\trca = testca.NewX509(data, helpers)\n\t}\n\treturn registry.NewDockerRegistry(data, helpers, rca, port, &registry.NoAuth{})\n}\n\nfunc RegistryWithBasicAuth(data test.Data, helpers test.Helpers, user, pass string, port int, tls bool) *registry.Server {\n\tauth := &registry.BasicAuth{\n\t\tUsername: user,\n\t\tPassword: pass,\n\t}\n\tvar rca *testca.Cert\n\tif tls {\n\t\trca = testca.NewX509(data, helpers)\n\t}\n\treturn registry.NewDockerRegistry(data, helpers, rca, port, auth)\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/utilities.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/expect\"\n\t\"github.com/containerd/nerdctl/mod/tigron/require\"\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\t\"github.com/containerd/nerdctl/mod/tigron/tig\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n)\n\nconst (\n\t// It seems that at this moment, the busybox on windows image we are using has an outdated version of sleep\n\t// that does not support inf/infinity.\n\t// This constant is provided as a mean for tests to express the intention of sleep infinity without having to\n\t// worry about that and get windows compatibility.\n\tInfinity = \"3600\"\n)\n\nfunc IsDocker() bool {\n\treturn strings.HasPrefix(filepath.Base(testutil.GetTarget()), \"docker\")\n}\n\n// InspectContainer is a helper that can be used inside custom commands or Setup\nfunc InspectContainer(helpers test.Helpers, name string) dockercompat.Container {\n\thelpers.T().Helper()\n\tvar res dockercompat.Container\n\tcmd := helpers.Command(\"container\", \"inspect\", name)\n\tcmd.Run(&test.Expected{\n\t\tOutput: expect.JSON([]dockercompat.Container{}, func(dc []dockercompat.Container, t tig.T) {\n\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\")\n\t\t\tres = dc[0]\n\t\t}),\n\t})\n\treturn res\n}\n\nfunc InspectVolume(helpers test.Helpers, name string) native.Volume {\n\thelpers.T().Helper()\n\tvar res native.Volume\n\tcmd := helpers.Command(\"volume\", \"inspect\", name)\n\tcmd.Run(&test.Expected{\n\t\tOutput: expect.JSON([]native.Volume{}, func(dc []native.Volume, t tig.T) {\n\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\")\n\t\t\tres = dc[0]\n\t\t}),\n\t})\n\treturn res\n}\n\nfunc InspectNetwork(helpers test.Helpers, name string) dockercompat.Network {\n\thelpers.T().Helper()\n\tvar res dockercompat.Network\n\tcmd := helpers.Command(\"network\", \"inspect\", name)\n\tcmd.Run(&test.Expected{\n\t\tOutput: expect.JSON([]dockercompat.Network{}, func(dc []dockercompat.Network, t tig.T) {\n\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\")\n\t\t\tres = dc[0]\n\t\t}),\n\t})\n\treturn res\n}\n\nfunc InspectNetworkNative(helpers test.Helpers, name string) native.Network {\n\thelpers.T().Helper()\n\tvar res native.Network\n\tcmd := helpers.Command(\"network\", \"inspect\", \"--mode\", \"native\", name)\n\tcmd.Run(&test.Expected{\n\t\tOutput: expect.JSON([]native.Network{}, func(dc []native.Network, t tig.T) {\n\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\")\n\t\t\tres = dc[0]\n\t\t}),\n\t})\n\treturn res\n}\n\nfunc InspectImage(helpers test.Helpers, name string) dockercompat.Image {\n\thelpers.T().Helper()\n\tvar res dockercompat.Image\n\tcmd := helpers.Command(\"image\", \"inspect\", name)\n\tcmd.Run(&test.Expected{\n\t\tOutput: expect.JSON([]dockercompat.Image{}, func(dc []dockercompat.Image, t tig.T) {\n\t\t\tassert.Equal(t, 1, len(dc), \"Unexpectedly got multiple results\")\n\t\t\tres = dc[0]\n\t\t}),\n\t})\n\treturn res\n}\n\nconst (\n\tmaxRetry = 20\n\tsleep    = time.Second\n)\n\nfunc EnsureContainerStarted(helpers test.Helpers, con string) {\n\thelpers.T().Helper()\n\tstarted := false\n\tfor i := 0; i < maxRetry && !started; i++ {\n\t\thelpers.Command(\"container\", \"inspect\", con).\n\t\t\tRun(&test.Expected{\n\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tvar dc []dockercompat.Container\n\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\tif err != nil || len(dc) == 0 {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tassert.Equal(t, len(dc), 1, \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\tstarted = dc[0].State.Running\n\t\t\t\t},\n\t\t\t})\n\t\ttime.Sleep(sleep)\n\t}\n\n\tif !started {\n\t\tins := helpers.Capture(\"container\", \"inspect\", con)\n\t\tlgs := helpers.Capture(\"logs\", con)\n\t\tps := helpers.Capture(\"ps\", \"-a\")\n\t\thelpers.T().Log(ins)\n\t\thelpers.T().Log(lgs)\n\t\thelpers.T().Log(ps)\n\t\thelpers.T().Log(fmt.Sprintf(\"container %s still not running after %d retries\", con, maxRetry))\n\t\thelpers.T().FailNow()\n\t}\n}\n\nfunc EnsureContainerExited(helpers test.Helpers, con string, exitCode int) {\n\thelpers.T().Helper()\n\texited := false\n\tfor i := 0; i < maxRetry && !exited; i++ {\n\t\thelpers.Command(\"container\", \"inspect\", con).\n\t\t\tRun(&test.Expected{\n\t\t\t\tExitCode: expect.ExitCodeNoCheck,\n\t\t\t\tOutput: func(stdout string, t tig.T) {\n\t\t\t\t\tvar dc []dockercompat.Container\n\t\t\t\t\terr := json.Unmarshal([]byte(stdout), &dc)\n\t\t\t\t\tif err != nil || len(dc) == 0 || (len(dc) > 0 && dc[0].State == nil) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tassert.Equal(t, len(dc), 1, \"Unexpectedly got multiple results\\n\")\n\t\t\t\t\tstate := dc[0].State\n\t\t\t\t\tif state.Running {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif state.Status != \"exited\" && state.Status != \"dead\" {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// Use a negative exitCode to ignore the exit code and only verify exited/dead state.\n\t\t\t\t\tif exitCode >= 0 && state.ExitCode != exitCode {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\texited = true\n\t\t\t\t},\n\t\t\t})\n\t\ttime.Sleep(sleep)\n\t}\n\n\tif !exited {\n\t\tins := helpers.Capture(\"container\", \"inspect\", con)\n\t\tlgs := helpers.Capture(\"logs\", con)\n\t\tps := helpers.Capture(\"ps\", \"-a\")\n\t\thelpers.T().Log(ins)\n\t\thelpers.T().Log(lgs)\n\t\thelpers.T().Log(ps)\n\t\thelpers.T().Log(fmt.Sprintf(\"container %s still not exited after %d retries\", con, maxRetry))\n\t\thelpers.T().FailNow()\n\t}\n}\n\nfunc GenerateJWEKeyPair(data test.Data, helpers test.Helpers) (string, string) {\n\thelpers.T().Helper()\n\n\tpath := \"jwe-key-pair\"\n\tdata.Temp().Dir(path)\n\n\tpass, message := require.Binary(\"openssl\").Check(data, helpers)\n\tif !pass {\n\t\thelpers.T().Skip(message)\n\t}\n\n\tpri := data.Temp().Path(path, \"mykey.pem\")\n\tpub := data.Temp().Path(path, \"mypubkey.pem\")\n\n\t// Exec openssl commands to ensure that nerdctl is compatible with the output of openssl commands.\n\t// Do NOT refactor this function to use \"crypto/rsa\" stdlib.\n\thelpers.Custom(\"openssl\", \"genrsa\", \"-out\", pri).Run(&test.Expected{})\n\thelpers.Custom(\"openssl\", \"rsa\", \"-in\", pri, \"-pubout\", \"-out\", pub).Run(&test.Expected{})\n\n\treturn pri, pub\n}\n\nfunc GenerateCosignKeyPair(data test.Data, helpers test.Helpers, password string) (pri string, pub string) {\n\thelpers.T().Helper()\n\n\tpath := \"cosign-key-pair\"\n\tdata.Temp().Dir(path)\n\n\tpass, message := require.Binary(\"cosign\").Check(data, helpers)\n\tif !pass {\n\t\thelpers.T().Skip(message)\n\t}\n\n\tcmd := helpers.Custom(\"cosign\", \"generate-key-pair\")\n\tcmd.WithCwd(data.Temp().Path(path))\n\tcmd.Setenv(\"COSIGN_PASSWORD\", password)\n\tcmd.Run(&test.Expected{})\n\n\treturn data.Temp().Path(path, \"cosign.key\"), data.Temp().Path(path, \"cosign.pub\")\n}\n\nfunc FindIPv6(output string) net.IP {\n\tvar ipv6 string\n\tlines := strings.Split(output, \"\\n\")\n\tfor _, line := range lines {\n\t\tif strings.Contains(line, \"inet6\") {\n\t\t\tfields := strings.Fields(line)\n\t\t\tif len(fields) > 1 {\n\t\t\t\tipv6 = strings.Split(fields[1], \"/\")[0]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn net.ParseIP(ipv6)\n}\n"
  },
  {
    "path": "pkg/testutil/nerdtest/utilities_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nerdtest\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/mod/tigron/test\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n)\n\nconst SignalCaught = \"received\"\n\nvar SigQuit os.Signal = syscall.SIGQUIT\nvar SigUsr1 os.Signal = syscall.SIGUSR1\n\nfunc RunSigProxyContainer(signal os.Signal, exitOnSignal bool, args []string, data test.Data, helpers test.Helpers) test.TestableCommand {\n\tsig := strconv.Itoa(int(signal.(syscall.Signal)))\n\tready := \"trap ready\"\n\tscript := `#!/bin/sh\n\tset -eu\n\n\tsig_msg () {\n\t\tprintf \"` + SignalCaught + `\\n\"\n\t\t[ \"` + strconv.FormatBool(exitOnSignal) + `\" != true ] || exit 0\n\t}\n\n\ttrap sig_msg ` + sig + `\n\tprintf \"` + ready + `\\n\"\n\twhile true; do\n\t\tprintf \"waiting...\\n\"\n\t\tsleep 0.5\n\tdone\n`\n\n\targs = append(args, \"--name\", data.Identifier(), testutil.CommonImage, \"sh\", \"-c\", script)\n\targs = append([]string{\"run\"}, args...)\n\n\tcmd := helpers.Command(args...)\n\t// NOTE: because of a test like TestStopWithStopSignal, we need to wait enough for nerdctl to terminate the container\n\t// It looks like EL8 could be particularly slow (https://github.com/containerd/nerdctl/issues/4068)\n\t// Note that in normal circumstances, 10 seconds is plenty enough.\n\tcmd.WithTimeout(40 * time.Second)\n\tcmd.Background()\n\tEnsureContainerStarted(helpers, data.Identifier())\n\n\tfor {\n\t\tout := helpers.Capture(\"logs\", data.Identifier())\n\t\tif strings.Contains(out, ready) {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\n\treturn cmd\n}\n\n// StartHTTPServer starts an HTTP server bound to 0.0.0.0 and returns a URL reachable\n// from processes that cannot access 127.0.0.1 due to namespace isolation.\n// It also returns a cleanup function that stops the server.\nfunc StartHTTPServer(handler http.Handler) (url string, stop func(), err error) {\n\tl, err := net.Listen(\"tcp\", \"0.0.0.0:0\")\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tsrv := &httptest.Server{Config: &http.Server{Handler: handler}}\n\tsrv.Listener = l\n\tsrv.Start()\n\thostIP, herr := nettestutil.NonLoopbackIPv4()\n\tif herr != nil {\n\t\tsrv.Close()\n\t\treturn \"\", nil, herr\n\t}\n\t_, port, perr := net.SplitHostPort(l.Addr().String())\n\tif perr != nil {\n\t\tsrv.Close()\n\t\treturn \"\", nil, perr\n\t}\n\treturn \"http://\" + hostIP.String() + \":\" + port, func() { srv.Close() }, nil\n}\n"
  },
  {
    "path": "pkg/testutil/nettestutil/nettestutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage nettestutil\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/containerd/errdefs\"\n)\n\nfunc HTTPGet(urlStr string, attempts int, insecure bool) (*http.Response, error) {\n\tvar (\n\t\tresp *http.Response\n\t\terr  error\n\t)\n\tif attempts < 1 {\n\t\treturn nil, errdefs.ErrInvalidArgument\n\t}\n\tclient := &http.Client{\n\t\tTimeout: 3 * time.Second,\n\t\tTransport: &http.Transport{\n\t\t\tTLSClientConfig: &tls.Config{\n\t\t\t\tInsecureSkipVerify: insecure,\n\t\t\t},\n\t\t},\n\t}\n\tfor i := 0; i < attempts; i++ {\n\t\tresp, err = client.Get(urlStr)\n\t\tif err == nil {\n\t\t\treturn resp, nil\n\t\t}\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\treturn nil, fmt.Errorf(\"error after %d attempts: %w\", attempts, err)\n}\n\nfunc NonLoopbackIPv4() (net.IP, error) {\n\t// no need to use [rootlessutil.WithDetachedNetNSIfAny] here\n\taddrs, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, addr := range addrs {\n\t\tip, _, err := net.ParseCIDR(addr.String())\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tipv4 := ip.To4()\n\t\tif ipv4 == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif ipv4.IsLoopback() {\n\t\t\tcontinue\n\t\t}\n\t\treturn ipv4, nil\n\t}\n\treturn nil, fmt.Errorf(\"non-loopback IPv4 address not found, attempted=%+v: %w\", addrs, errdefs.ErrNotFound)\n}\n\nfunc GenerateMACAddress() (string, error) {\n\tbuf := make([]byte, 6)\n\tif _, err := rand.Read(buf); err != nil {\n\t\treturn \"\", err\n\t}\n\t// make sure byte 0 (broadcast) of the first byte is not set\n\t// and byte 1 (local) is set\n\tbuf[0] = buf[0]&254 | 2\n\treturn fmt.Sprintf(\"%02x:%02x:%02x:%02x:%02x:%02x\", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]), nil\n}\n"
  },
  {
    "path": "pkg/testutil/portlock/portlock.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\n// portlock provides a mechanism for containers to acquire and release ports they plan to expose, and a wait mechanism\n// This allows tests dependent on running containers to always parallelize without having to worry about port collision\n// with any other test\n// Note that this does NOT protect against trying to use a port that is already used by an unrelated third-party service or container\n// Also note that *generally* finding a free port is not easy:\n// - to just \"listen\" and see if it works won't work for containerized services that are DNAT-ed (plus, that would be racy)\n// - inspecting iptables instead (or in addition to) may work for containers, but this depends on how networking has been set (and yes, it is also racy)\n// Our approach here is optimistic: tests are responsible for calling Acquire and Release\npackage portlock\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar mut = &sync.Mutex{} //nolint:gochecknoglobals\nvar portList = map[int]bool{}\n\nfunc Acquire(port int) (int, error) {\n\tflexible := false\n\tif port == 0 {\n\t\tport = 5000\n\t\tflexible = true\n\t}\n\tfor {\n\t\tmut.Lock()\n\t\tif _, ok := portList[port]; !ok {\n\t\t\tportList[port] = true\n\t\t\tmut.Unlock()\n\t\t\treturn port, nil\n\t\t}\n\t\tmut.Unlock()\n\t\tif flexible {\n\t\t\tport++\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Println(\"Waiting for port to become available...\", port)\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc Release(port int) error {\n\tmut.Lock()\n\tdelete(portList, port)\n\tmut.Unlock()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/testutil/testca/testca.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testca\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gotest.tools/v3/assert\"\n)\n\ntype CA struct {\n\tKeyPath  string\n\tCertPath string\n\n\tt      testing.TB\n\tkey    *rsa.PrivateKey\n\tcert   *x509.Certificate\n\tcloseF func() error\n}\n\nfunc (ca *CA) Close() error {\n\treturn ca.closeF()\n}\n\nconst keyLength = 4096\n\nfunc New(t testing.TB) *CA {\n\tkey, err := rsa.GenerateKey(rand.Reader, keyLength)\n\tassert.NilError(t, err)\n\n\tcert := &x509.Certificate{\n\t\tSerialNumber: serialNumber(t),\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"nerdctl test organization\"},\n\t\t\tCommonName:   fmt.Sprintf(\"nerdctl CA (%s)\", t.Name()),\n\t\t},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(24 * time.Hour),\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tdir, err := os.MkdirTemp(t.TempDir(), \"ca\")\n\tassert.NilError(t, err)\n\tkeyPath := filepath.Join(dir, \"ca.key\")\n\tcertPath := filepath.Join(dir, \"ca.cert\")\n\twritePair(t, keyPath, certPath, cert, cert, key, key)\n\n\treturn &CA{\n\t\tKeyPath:  keyPath,\n\t\tCertPath: certPath,\n\t\tt:        t,\n\t\tkey:      key,\n\t\tcert:     cert,\n\t\tcloseF: func() error {\n\t\t\treturn os.RemoveAll(dir)\n\t\t},\n\t}\n}\n\ntype Cert struct {\n\tKeyPath  string\n\tCertPath string\n\tcloseF   func() error\n}\n\nfunc (c *Cert) Close() error {\n\treturn c.closeF()\n}\n\nfunc (ca *CA) NewCert(host string, additional ...string) *Cert {\n\tt := ca.t\n\n\tkey, err := rsa.GenerateKey(rand.Reader, keyLength)\n\tassert.NilError(t, err)\n\n\tadditional = append([]string{host}, additional...)\n\n\tcert := &x509.Certificate{\n\t\tSerialNumber: serialNumber(t),\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"nerdctl test organization\"},\n\t\t\tCommonName:   fmt.Sprintf(\"nerdctl %s (%s)\", host, t.Name()),\n\t\t},\n\t\tNotBefore:   time.Now(),\n\t\tNotAfter:    time.Now().Add(24 * time.Hour),\n\t\tKeyUsage:    x509.KeyUsageCRLSign,\n\t\tExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tDNSNames:    additional,\n\t}\n\tfor _, h := range additional {\n\t\tif ip := net.ParseIP(h); ip != nil {\n\t\t\tcert.IPAddresses = append(cert.IPAddresses, ip)\n\t\t}\n\t}\n\n\tdir, err := os.MkdirTemp(t.TempDir(), \"cert\")\n\tassert.NilError(t, err)\n\tcertPath := filepath.Join(dir, \"a.cert\")\n\tkeyPath := filepath.Join(dir, \"a.key\")\n\twritePair(t, keyPath, certPath, cert, ca.cert, key, ca.key)\n\n\treturn &Cert{\n\t\tCertPath: certPath,\n\t\tKeyPath:  keyPath,\n\t\tcloseF: func() error {\n\t\t\treturn os.RemoveAll(dir)\n\t\t},\n\t}\n}\n\nfunc writePair(t testing.TB, keyPath, certPath string, cert, caCert *x509.Certificate, key, caKey *rsa.PrivateKey) {\n\tkeyF, err := os.Create(keyPath)\n\tassert.NilError(t, err)\n\tdefer keyF.Close()\n\tassert.NilError(t, pem.Encode(keyF, &pem.Block{Type: \"RSA PRIVATE KEY\", Bytes: x509.MarshalPKCS1PrivateKey(key)}))\n\tassert.NilError(t, keyF.Close())\n\n\tcertB, err := x509.CreateCertificate(rand.Reader, cert, caCert, &key.PublicKey, caKey)\n\tassert.NilError(t, err)\n\tcertF, err := os.Create(certPath)\n\tassert.NilError(t, err)\n\tdefer certF.Close()\n\tassert.NilError(t, pem.Encode(certF, &pem.Block{Type: \"CERTIFICATE\", Bytes: certB}))\n\tassert.NilError(t, certF.Close())\n}\n\nfunc serialNumber(t testing.TB) *big.Int {\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 60)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\tassert.NilError(t, err)\n\treturn serialNumber\n}\n"
  },
  {
    "path": "pkg/testutil/testregistry/certsd_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testregistry\n\nimport (\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/hoststoml\"\n)\n\nfunc generateCertsd(dir string, certPath string, hostIP string, port int) error {\n\treturn (&hoststoml.HostsToml{\n\t\tCA: certPath,\n\t}).Save(dir, hostIP, port)\n}\n"
  },
  {
    "path": "pkg/testutil/testregistry/testregistry_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testregistry\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n\t\"gotest.tools/v3/assert\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/portlock\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testca\"\n)\n\ntype RegistryServer struct {\n\tIP       net.IP\n\tPort     int\n\tScheme   string\n\tListenIP net.IP\n\tCleanup  func(err error)\n\tLogs     func()\n\tHostsDir string // contains \"<HostIP>:<ListenPort>/hosts.toml\"\n}\n\ntype TokenAuthServer struct {\n\tIP       net.IP\n\tPort     int\n\tScheme   string\n\tListenIP net.IP\n\tCleanup  func(err error)\n\tLogs     func()\n\tAuth     Auth\n\tCertPath string\n}\n\nfunc EnsureImages(base *testutil.Base) {\n\tregistryImage := platform.RegistryImageStable\n\tbase.Cmd(\"pull\", \"--quiet\", registryImage).AssertOK()\n\tbase.Cmd(\"pull\", \"--quiet\", platform.DockerAuthImage).AssertOK()\n\tbase.Cmd(\"pull\", \"--quiet\", platform.KuboImage).AssertOK()\n}\n\nfunc NewAuthServer(base *testutil.Base, ca *testca.CA, port int, user, pass string, tls bool) *TokenAuthServer {\n\tEnsureImages(base)\n\n\tname := testutil.Identifier(base.T)\n\t// listen on 0.0.0.0 to enable 127.0.0.1\n\tlistenIP := net.ParseIP(\"0.0.0.0\")\n\thostIP, err := nettestutil.NonLoopbackIPv4()\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed finding ipv4 non loopback interface: %w\", err))\n\t// Prepare configuration file for authentication server\n\t// Details: https://github.com/cesanta/docker_auth/blob/1.7.1/examples/simple.yml\n\tconfigFile, err := os.CreateTemp(\"\", \"authconfig\")\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed creating temporary directory for config file: %w\", err))\n\tbpass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed bcrypt encrypting password: %w\", err))\n\tconfigFileName := configFile.Name()\n\tscheme := \"http\"\n\tconfigContent := fmt.Sprintf(`\nserver:\n  addr: \":5100\"\ntoken:\n  issuer: \"Acme auth server\"\n  expiration: 900\n  certificate: \"/auth/domain.crt\"\n  key: \"/auth/domain.key\"\nusers:\n  \"%s\":\n    password: \"%s\"\nacl:\n  - match: {account: \"%s\"}\n    actions: [\"*\"]\n`, user, string(bpass), user)\n\tif tls {\n\t\tscheme = \"https\"\n\t\tconfigContent = fmt.Sprintf(`\nserver:\n  addr: \":5100\"\n  certificate: \"/auth/domain.crt\"\n  key: \"/auth/domain.key\"\ntoken:\n  issuer: \"Acme auth server\"\n  expiration: 900\nusers:\n  \"%s\":\n    password: \"%s\"\nacl:\n  - match: {account: \"%s\"}\n    actions: [\"*\"]\n`, user, string(bpass), user)\n\t}\n\t_, err = configFile.Write([]byte(configContent))\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed writing configuration: %w\", err))\n\n\tcert := ca.NewCert(hostIP.String())\n\n\tport, err = portlock.Acquire(port)\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed acquiring port: %w\", err))\n\tcontainerName := fmt.Sprintf(\"auth-%s-%d\", name, port)\n\t// Cleanup possible leftovers first\n\tbase.Cmd(\"rm\", \"-f\", containerName).Run()\n\n\tcleanup := func(err error) {\n\t\tresult := base.Cmd(\"rm\", \"-f\", containerName).Run()\n\t\terrPortRelease := portlock.Release(port)\n\t\terrCertClose := cert.Close()\n\t\terrConfigClose := configFile.Close()\n\t\terrConfigRemove := os.Remove(configFileName)\n\t\tif err == nil {\n\t\t\tassert.NilError(base.T, result.Error, fmt.Errorf(\"failed stopping container: %w\", err))\n\t\t\tassert.NilError(base.T, errPortRelease, fmt.Errorf(\"failed releasing port: %w\", err))\n\t\t\tassert.NilError(base.T, errCertClose, fmt.Errorf(\"failed cleaning certs: %w\", err))\n\t\t\tassert.NilError(base.T, errConfigClose, fmt.Errorf(\"failed closing config file: %w\", err))\n\t\t\tassert.NilError(base.T, errConfigRemove, fmt.Errorf(\"failed removing config file: %w\", err))\n\t\t}\n\t}\n\n\terr = func() error {\n\t\t// Run authentication server\n\t\tcmd := base.Cmd(\n\t\t\t\"run\",\n\t\t\t\"--pull=never\",\n\t\t\t\"-d\",\n\t\t\t\"-p\", fmt.Sprintf(\"%s:%d:5100\", listenIP, port),\n\t\t\t\"--name\", containerName,\n\t\t\t\"-v\", cert.CertPath+\":/auth/domain.crt\",\n\t\t\t\"-v\", cert.KeyPath+\":/auth/domain.key\",\n\t\t\t\"-v\", configFileName+\":/config/auth_config.yml\",\n\t\t\ttestutil.DockerAuthImage,\n\t\t\t\"/config/auth_config.yml\").Run()\n\t\tif cmd.Error != nil {\n\t\t\tbase.T.Logf(\"%s:\\n%s\\n%s\\n-------\\n%s\", containerName, cmd.Cmd, cmd.Stdout(), cmd.Stderr())\n\t\t\treturn cmd.Error\n\t\t}\n\t\tjoined := net.JoinHostPort(hostIP.String(), strconv.Itoa(port))\n\t\t_, err = nettestutil.HTTPGet(fmt.Sprintf(\"%s://%s/auth\", scheme, joined), 5, true)\n\t\treturn err\n\t}()\n\n\tif err != nil {\n\t\tcl := base.Cmd(\"logs\", containerName).Run()\n\t\tbase.T.Logf(\"%s:\\n%s\\n%s\\n=========================\\n%s\", containerName, cl.Cmd, cl.Stdout(), cl.Stderr())\n\t\tcleanup(err)\n\t}\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed starting auth container in a timely manner: %w\", err))\n\n\treturn &TokenAuthServer{\n\t\tIP:       hostIP,\n\t\tPort:     port,\n\t\tScheme:   scheme,\n\t\tListenIP: listenIP,\n\t\tCertPath: cert.CertPath,\n\t\tAuth: &TokenAuth{\n\t\t\tAddress:  scheme + \"://\" + net.JoinHostPort(hostIP.String(), strconv.Itoa(port)),\n\t\t\tCertPath: cert.CertPath,\n\t\t},\n\t\tCleanup: cleanup,\n\t\tLogs: func() {\n\t\t\tbase.T.Logf(\"%s: %q\", containerName, base.Cmd(\"logs\", containerName).Run().String())\n\t\t},\n\t}\n\n}\n\n// Auth is an interface to pass to the test registry for configuring authentication\ntype Auth interface {\n\tParams(*testutil.Base) []string\n}\n\ntype NoAuth struct {\n}\n\nfunc (na *NoAuth) Params(base *testutil.Base) []string {\n\treturn []string{}\n}\n\ntype TokenAuth struct {\n\tAddress  string\n\tCertPath string\n}\n\nfunc (ta *TokenAuth) Params(base *testutil.Base) []string {\n\treturn []string{\n\t\t\"--env\", \"REGISTRY_AUTH=token\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_REALM=\" + ta.Address + \"/auth\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_SERVICE=Docker registry\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_ISSUER=Acme auth server\",\n\t\t\"--env\", \"REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/auth/domain.crt\",\n\t\t\"-v\", ta.CertPath + \":/auth/domain.crt\",\n\t}\n}\n\ntype BasicAuth struct {\n\tRealm    string\n\tHtFile   string\n\tUsername string\n\tPassword string\n}\n\nfunc (ba *BasicAuth) Params(base *testutil.Base) []string {\n\tif ba.Realm == \"\" {\n\t\tba.Realm = \"Basic Realm\"\n\t}\n\tif ba.HtFile == \"\" && ba.Username != \"\" && ba.Password != \"\" {\n\t\tpass := ba.Password\n\t\tencryptedPass, _ := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)\n\t\ttmpDir, _ := os.MkdirTemp(base.T.TempDir(), \"htpasswd\")\n\t\tba.HtFile = filepath.Join(tmpDir, \"htpasswd\")\n\t\t_ = filesystem.WriteFile(ba.HtFile, []byte(fmt.Sprintf(`%s:%s`, ba.Username, string(encryptedPass[:]))), 0600)\n\t}\n\tret := []string{\n\t\t\"--env\", \"REGISTRY_AUTH=htpasswd\",\n\t\t\"--env\", \"REGISTRY_AUTH_HTPASSWD_REALM=\" + ba.Realm,\n\t\t\"--env\", \"REGISTRY_AUTH_HTPASSWD_PATH=/htpasswd\",\n\t}\n\tif ba.HtFile != \"\" {\n\t\tret = append(ret, \"-v\", ba.HtFile+\":/htpasswd\")\n\t}\n\treturn ret\n}\n\nfunc NewRegistry(base *testutil.Base, ca *testca.CA, port int, auth Auth, boundCleanup func(error)) *RegistryServer {\n\tEnsureImages(base)\n\n\tname := testutil.Identifier(base.T)\n\t// listen on 0.0.0.0 to enable 127.0.0.1\n\tlistenIP := net.ParseIP(\"0.0.0.0\")\n\thostIP, err := nettestutil.NonLoopbackIPv4()\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed finding ipv4 non loopback interface: %w\", err))\n\tport, err = portlock.Acquire(port)\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed acquiring port: %w\", err))\n\n\tcontainerName := fmt.Sprintf(\"registry-%s-%d\", name, port)\n\t// Cleanup possible leftovers first\n\tbase.Cmd(\"rm\", \"-f\", containerName).Run()\n\n\targs := []string{\n\t\t\"run\",\n\t\t\"--pull=never\",\n\t\t\"-d\",\n\t\t\"-p\", fmt.Sprintf(\"%s:%d:5000\", listenIP, port),\n\t\t\"--name\", containerName,\n\t}\n\tscheme := \"http\"\n\tvar cert *testca.Cert\n\tif ca != nil {\n\t\tscheme = \"https\"\n\t\tcert = ca.NewCert(hostIP.String(), \"127.0.0.1\", \"localhost\", \"::1\")\n\t\targs = append(args,\n\t\t\t\"--env\", \"REGISTRY_HTTP_TLS_CERTIFICATE=/registry/domain.crt\",\n\t\t\t\"--env\", \"REGISTRY_HTTP_TLS_KEY=/registry/domain.key\",\n\t\t\t\"-v\", cert.CertPath+\":/registry/domain.crt\",\n\t\t\t\"-v\", cert.KeyPath+\":/registry/domain.key\",\n\t\t)\n\t}\n\n\targs = append(args, auth.Params(base)...)\n\tregistryImage := testutil.RegistryImageStable\n\targs = append(args, registryImage)\n\n\tcleanup := func(err error) {\n\t\tresult := base.Cmd(\"rm\", \"-f\", containerName).Run()\n\t\terrPortRelease := portlock.Release(port)\n\t\tvar errCertClose error\n\t\tif cert != nil {\n\t\t\terrCertClose = cert.Close()\n\t\t}\n\t\tif boundCleanup != nil {\n\t\t\tboundCleanup(err)\n\t\t}\n\t\tif cert != nil && err == nil {\n\t\t\tassert.NilError(base.T, errCertClose, fmt.Errorf(\"failed cleaning certificates: %w\", err))\n\t\t}\n\t\tif err == nil {\n\t\t\tassert.NilError(base.T, result.Error, fmt.Errorf(\"failed removing container: %w\", err))\n\t\t\tassert.NilError(base.T, errPortRelease, fmt.Errorf(\"failed releasing port: %w\", err))\n\t\t}\n\t}\n\n\thostsDir, err := func() (string, error) {\n\t\thDir, err := os.MkdirTemp(base.T.TempDir(), \"certs.d\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif ca != nil {\n\t\t\terr = generateCertsd(hDir, ca.CertPath, hostIP.String(), port)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\terr = generateCertsd(hDir, ca.CertPath, \"127.0.0.1\", port)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\terr = generateCertsd(hDir, ca.CertPath, \"localhost\", port)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tif port == 443 {\n\t\t\t\terr = generateCertsd(hDir, ca.CertPath, hostIP.String(), 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\terr = generateCertsd(hDir, ca.CertPath, \"127.0.0.1\", 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\terr = generateCertsd(hDir, ca.CertPath, \"localhost\", 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcmd := base.Cmd(args...).Run()\n\t\tif cmd.Error != nil {\n\t\t\tbase.T.Logf(\"%s:\\n%s\\n%s\\n-------\\n%s\", containerName, cmd.Cmd, cmd.Stdout(), cmd.Stderr())\n\t\t\treturn \"\", cmd.Error\n\t\t}\n\n\t\tif _, err = nettestutil.HTTPGet(fmt.Sprintf(\"%s://%s:%s/v2\", scheme, hostIP.String(), strconv.Itoa(port)), 5, true); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn hDir, nil\n\t}()\n\n\tif err != nil {\n\t\tcl := base.Cmd(\"logs\", containerName).Run()\n\t\tbase.T.Logf(\"%s:\\n%s\\n%s\\n=========================\\n%s\", containerName, cl.Cmd, cl.Stdout(), cl.Stderr())\n\t\tcleanup(err)\n\t}\n\tassert.NilError(base.T, err, fmt.Errorf(\"failed starting registry container in a timely manner: %w\", err))\n\n\treturn &RegistryServer{\n\t\tIP:       hostIP,\n\t\tPort:     port,\n\t\tScheme:   scheme,\n\t\tListenIP: listenIP,\n\t\tCleanup:  cleanup,\n\t\tLogs: func() {\n\t\t\tbase.T.Logf(\"%s: %q\", containerName, base.Cmd(\"logs\", containerName).Run().String())\n\t\t},\n\t\tHostsDir: hostsDir,\n\t}\n}\n\nfunc NewWithTokenAuth(base *testutil.Base, user, pass string, port int, tls bool) *RegistryServer {\n\tca := testca.New(base.T)\n\tas := NewAuthServer(base, ca, 0, user, pass, tls)\n\tauth := &TokenAuth{\n\t\tAddress:  as.Scheme + \"://\" + net.JoinHostPort(as.IP.String(), strconv.Itoa(as.Port)),\n\t\tCertPath: as.CertPath,\n\t}\n\treturn NewRegistry(base, ca, port, auth, as.Cleanup)\n}\n\nfunc NewWithNoAuth(base *testutil.Base, port int, tls bool) *RegistryServer {\n\tvar ca *testca.CA\n\tif tls {\n\t\tca = testca.New(base.T)\n\t}\n\treturn NewRegistry(base, ca, port, &NoAuth{}, nil)\n}\n"
  },
  {
    "path": "pkg/testutil/testsyslog/testsyslog.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testsyslog\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/testutil/testca\"\n)\n\nfunc StartServer(n, la string, done chan<- string, certs ...*testca.Cert) (addr string, sock io.Closer) {\n\tif n == \"udp\" || n == \"tcp\" || n == \"tcp+tls\" {\n\t\tla = \"127.0.0.1:0\"\n\t} else {\n\t\t// unix and unixgram: choose an address if none given\n\t\tif la == \"\" {\n\t\t\t// use os.CreateTemp to get a name that is unique\n\t\t\tf, err := os.CreateTemp(\"\", \"syslogtest\")\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"TempFile: \", err)\n\t\t\t}\n\t\t\tf.Close()\n\t\t\tla = f.Name()\n\t\t}\n\t\tos.Remove(la)\n\t}\n\n\tif n == \"udp\" || n == \"unixgram\" {\n\t\tl, e := net.ListenPacket(n, la)\n\t\tif e != nil {\n\t\t\tlog.Fatalf(\"startServer failed: %v\", e)\n\t\t}\n\t\taddr = l.LocalAddr().String()\n\t\tsock = l\n\t\tgo runPacketSyslog(l, done)\n\t} else if n == \"tcp+tls\" {\n\t\tif len(certs) == 0 {\n\t\t\tlog.Fatalf(\"certificates required.\")\n\t\t}\n\t\tcer := certs[0]\n\t\tif cer == nil {\n\t\t\tlog.Fatalf(\"certificates is nil\")\n\t\t}\n\t\tcert, err := tls.LoadX509KeyPair(cer.CertPath, cer.KeyPath)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"failed to load TLS keypair: %v\", err)\n\t\t}\n\t\tconfig := tls.Config{Certificates: []tls.Certificate{cert}}\n\t\tl, e := tls.Listen(\"tcp\", la, &config)\n\t\tif e != nil {\n\t\t\tlog.Fatalf(\"startServer failed: %v\", e)\n\t\t}\n\t\taddr = l.Addr().String()\n\t\tsock = l\n\t\tgo runStreamSyslog(l, done)\n\t} else {\n\t\tl, e := net.Listen(n, la)\n\t\tif e != nil {\n\t\t\tlog.Fatalf(\"startServer failed: %v\", e)\n\t\t}\n\t\taddr = l.Addr().String()\n\t\tsock = l\n\t\tgo runStreamSyslog(l, done)\n\t}\n\treturn addr, sock\n}\n\nfunc TestableNetwork(network string) bool {\n\tswitch network {\n\tcase \"unix\", \"unixgram\":\n\t\tswitch runtime.GOOS {\n\t\tcase \"darwin\":\n\t\t\tswitch runtime.GOARCH {\n\t\t\tcase \"arm\", \"arm64\":\n\t\t\t\treturn false\n\t\t\t}\n\t\tcase \"windows\":\n\t\t\treturn false\n\t\t}\n\tcase \"udp\", \"tcp\", \"tcp+tls\":\n\t\treturn !rootlessutil.IsRootless()\n\t}\n\treturn true\n}\n\nfunc runPacketSyslog(c net.PacketConn, done chan<- string) {\n\tvar buf [4096]byte\n\tvar rcvd string\n\tct := 0\n\tfor {\n\t\tvar n int\n\t\tvar err error\n\n\t\t_ = c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))\n\t\tn, _, err = c.ReadFrom(buf[:])\n\t\trcvd += string(buf[:n])\n\t\tif err != nil {\n\t\t\tif oe, ok := err.(*net.OpError); ok {\n\t\t\t\tif ct < 3 && oe.Temporary() {\n\t\t\t\t\tct++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tc.Close()\n\tdone <- rcvd\n}\n\nfunc runStreamSyslog(l net.Listener, done chan<- string) {\n\tfor {\n\t\tvar c net.Conn\n\t\tvar err error\n\t\tif c, err = l.Accept(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tgo func(c net.Conn) {\n\t\t\t_ = c.SetReadDeadline(time.Now().Add(5 * time.Second))\n\t\t\tb := bufio.NewReader(c)\n\t\t\tfor ct := 1; ct&7 != 0; ct++ {\n\t\t\t\ts, err := b.ReadString('\\n')\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tdone <- s\n\t\t\t}\n\t\t\tc.Close()\n\t\t}(c)\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/testutil.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"github.com/opencontainers/go-digest\"\n\t\"gotest.tools/v3/assert\"\n\t\"gotest.tools/v3/icmd\"\n\n\t\"github.com/containerd/containerd/v2/defaults\"\n\t\"github.com/containerd/log\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/buildkitutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/infoutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/native\"\n\t\"github.com/containerd/nerdctl/v2/pkg/internal/filesystem\"\n\t\"github.com/containerd/nerdctl/v2/pkg/platformutil\"\n\t\"github.com/containerd/nerdctl/v2/pkg/rootlessutil\"\n)\n\ntype Base struct {\n\tT                    testing.TB\n\tTarget               string\n\tDaemonIsKillable     bool\n\tEnableIPv6           bool\n\tIPv6Compatible       bool\n\tEnableKubernetes     bool\n\tKubernetesCompatible bool\n\tBinary               string\n\tArgs                 []string\n\tEnv                  []string\n\tDir                  string\n}\n\n// WithStdin sets the standard input of Cmd to the specified reader\nfunc WithStdin(r io.Reader) func(*Cmd) {\n\treturn func(i *Cmd) {\n\t\ti.Cmd.Stdin = r\n\t}\n}\n\nfunc (b *Base) Cmd(args ...string) *Cmd {\n\ticmdCmd := icmd.Command(b.Binary, append(b.Args, args...)...)\n\ticmdCmd.Env = b.Env\n\ticmdCmd.Dir = b.Dir\n\tcmd := &Cmd{\n\t\tCmd:  icmdCmd,\n\t\tBase: b,\n\t}\n\treturn cmd\n}\n\n// ComposeCmd executes `nerdctl -n nerdctl-test compose` or `docker-compose`\nfunc (b *Base) ComposeCmd(args ...string) *Cmd {\n\tbinary := b.Binary\n\tbinaryArgs := append(b.Args, append([]string{\"compose\"}, args...)...)\n\ticmdCmd := icmd.Command(binary, binaryArgs...)\n\ticmdCmd.Env = b.Env\n\ticmdCmd.Dir = b.Dir\n\tcmd := &Cmd{\n\t\tCmd:  icmdCmd,\n\t\tBase: b,\n\t}\n\treturn cmd\n}\n\nfunc (b *Base) ComposeCmdWithHelper(helper []string, args ...string) *Cmd {\n\thelperBin, err := exec.LookPath(helper[0])\n\tif err != nil {\n\t\tb.T.Skipf(\"helper binary %q not found\", helper[0])\n\t}\n\tbinary := b.Binary\n\tbinaryArgs := append(b.Args, append([]string{\"compose\"}, args...)...)\n\n\thelperArgs := helper[1:]\n\thelperArgs = append(helperArgs, binary)\n\thelperArgs = append(helperArgs, binaryArgs...)\n\ticmdCmd := icmd.Command(helperBin, helperArgs...)\n\ticmdCmd.Env = b.Env\n\tcmd := &Cmd{\n\t\tCmd:  icmdCmd,\n\t\tBase: b,\n\t}\n\treturn cmd\n}\n\nfunc (b *Base) CmdWithHelper(helper []string, args ...string) *Cmd {\n\thelperBin, err := exec.LookPath(helper[0])\n\tif err != nil {\n\t\tb.T.Skipf(\"helper binary %q not found\", helper[0])\n\t}\n\thelperArgs := helper[1:]\n\thelperArgs = append(helperArgs, b.Binary)\n\thelperArgs = append(helperArgs, b.Args...)\n\thelperArgs = append(helperArgs, args...)\n\n\ticmdCmd := icmd.Command(helperBin, helperArgs...)\n\tcmd := &Cmd{\n\t\tCmd:  icmdCmd,\n\t\tBase: b,\n\t}\n\treturn cmd\n}\n\nfunc (b *Base) systemctlTarget() string {\n\tif IsDocker() {\n\t\treturn \"docker.service\"\n\t}\n\n\treturn \"containerd.service\"\n}\n\nfunc (b *Base) systemctlArgs() []string {\n\tvar systemctlArgs []string\n\tif os.Geteuid() != 0 {\n\t\tsystemctlArgs = append(systemctlArgs, \"--user\")\n\t}\n\treturn systemctlArgs\n}\n\nfunc (b *Base) KillDaemon() {\n\tb.T.Helper()\n\tif !b.DaemonIsKillable {\n\t\tb.T.Skip(\"daemon is not killable (hint: set \\\"-test.allow-kill-daemon\\\")\")\n\t}\n\ttarget := b.systemctlTarget()\n\tb.T.Logf(\"killing %q\", target)\n\tcmdKill := exec.Command(\"systemctl\",\n\t\tappend(b.systemctlArgs(),\n\t\t\t[]string{\"kill\", target}...)...)\n\tif out, err := cmdKill.CombinedOutput(); err != nil {\n\t\terr = fmt.Errorf(\"cannot kill %q: %q: %w\", target, string(out), err)\n\t\tb.T.Fatal(err)\n\t}\n\t// the daemon should restart automatically\n}\n\nfunc (b *Base) EnsureDaemonActive() {\n\tb.T.Helper()\n\ttarget := b.systemctlTarget()\n\tb.T.Logf(\"checking activity of %q\", target)\n\tsystemctlArgs := b.systemctlArgs()\n\tconst (\n\t\tmaxRetry = 30\n\t\tsleep    = 3 * time.Second\n\t)\n\tfor i := 0; i < maxRetry; i++ {\n\t\tcmd := exec.Command(\"systemctl\", append(systemctlArgs, \"is-active\", target)...)\n\t\tout, err := cmd.CombinedOutput()\n\t\tb.T.Logf(\"(retry=%d) %s\", i, string(out))\n\t\tif err == nil {\n\t\t\t// The daemon is now running, but the daemon may still refuse connections to containerd.sock\n\t\t\tb.T.Logf(\"daemon %q is now running, checking whether the daemon can handle requests\", target)\n\t\t\tinfoRes := b.Cmd(\"info\").Run()\n\t\t\tif infoRes.ExitCode == 0 {\n\t\t\t\tb.T.Logf(\"daemon %q can now handle requests\", target)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tb.T.Logf(\"(retry=%d) %s\", i, infoRes.Combined())\n\t\t}\n\t\ttime.Sleep(sleep)\n\t}\n\tb.T.Fatalf(\"daemon %q not running?\", target)\n}\n\nfunc (b *Base) DumpDaemonLogs(minutes int) {\n\tb.T.Helper()\n\ttarget := b.systemctlTarget()\n\tcmd := exec.Command(\"journalctl\",\n\t\tappend(b.systemctlArgs(), \"-u\", target, \"--no-pager\", \"-S\", fmt.Sprintf(\"%d min ago\", minutes))...)\n\tb.T.Logf(\"===== %v =====\", cmd.Args)\n\tout, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\tb.T.Log(string(out))\n\tb.T.Log(\"==========\")\n}\n\nfunc (b *Base) InspectContainer(name string) dockercompat.Container {\n\tcmdResult := b.Cmd(\"container\", \"inspect\", name).Run()\n\tassert.Equal(b.T, cmdResult.ExitCode, 0)\n\tvar dc []dockercompat.Container\n\tif err := json.Unmarshal([]byte(cmdResult.Stdout()), &dc); err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\tassert.Equal(b.T, 1, len(dc))\n\treturn dc[0]\n}\n\nfunc (b *Base) InspectImage(name string) dockercompat.Image {\n\tcmdResult := b.Cmd(\"image\", \"inspect\", name).Run()\n\tassert.Equal(b.T, cmdResult.ExitCode, 0)\n\tvar dc []dockercompat.Image\n\tif err := json.Unmarshal([]byte(cmdResult.Stdout()), &dc); err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\tassert.Equal(b.T, 1, len(dc))\n\treturn dc[0]\n}\n\nfunc (b *Base) InspectNetwork(name string) dockercompat.Network {\n\tcmdResult := b.Cmd(\"network\", \"inspect\", name).Run()\n\tassert.Equal(b.T, cmdResult.ExitCode, 0)\n\tvar dc []dockercompat.Network\n\tif err := json.Unmarshal([]byte(cmdResult.Stdout()), &dc); err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\tassert.Equal(b.T, 1, len(dc))\n\treturn dc[0]\n}\n\nfunc (b *Base) InspectVolume(name string, args ...string) native.Volume {\n\tcmd := append([]string{\"volume\", \"inspect\"}, args...)\n\tcmd = append(cmd, name)\n\tcmdResult := b.Cmd(cmd...).Run()\n\tassert.Equal(b.T, cmdResult.ExitCode, 0)\n\tvar dc []native.Volume\n\tif err := json.Unmarshal([]byte(cmdResult.Stdout()), &dc); err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\tassert.Equal(b.T, 1, len(dc))\n\treturn dc[0]\n}\n\nfunc (b *Base) Info() dockercompat.Info {\n\tcmdResult := b.Cmd(\"info\", \"--format\", \"{{ json . }}\").Run()\n\tassert.Equal(b.T, cmdResult.ExitCode, 0)\n\tvar info dockercompat.Info\n\tif err := json.Unmarshal([]byte(cmdResult.Stdout()), &info); err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\treturn info\n}\n\nfunc (b *Base) InfoNative() native.Info {\n\tb.T.Helper()\n\tif IsDocker() {\n\t\tb.T.Skip(\"InfoNative() should not be called for non-nerdctl target\")\n\t}\n\tcmdResult := b.Cmd(\"info\", \"--mode\", \"native\", \"--format\", \"{{ json . }}\").Run()\n\tassert.Equal(b.T, cmdResult.ExitCode, 0)\n\tvar info native.Info\n\tif err := json.Unmarshal([]byte(cmdResult.Stdout()), &info); err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\treturn info\n}\n\nfunc (b *Base) ContainerdAddress() string {\n\tb.T.Helper()\n\tif IsDocker() {\n\t\tb.T.Skip(\"ContainerdAddress() should not be called for non-nerdctl target\")\n\t}\n\tif os.Geteuid() == 0 {\n\t\treturn defaults.DefaultAddress\n\t}\n\txdr, err := rootlessutil.XDGRuntimeDir()\n\tif err != nil {\n\t\tb.T.Log(err)\n\t\txdr = fmt.Sprintf(\"/run/user/%d\", os.Geteuid())\n\t}\n\tpidFile := filepath.Join(xdr, \"containerd-rootless\", \"child_pid\")\n\tpidB, err := filesystem.ReadFile(pidFile)\n\tif err != nil {\n\t\tb.T.Fatal(err)\n\t}\n\tpidS := strings.TrimSpace(string(pidB))\n\treturn filepath.Join(\"/proc\", pidS, \"root\", defaults.DefaultAddress)\n}\n\nfunc (b *Base) EnsureContainerStarted(con string) {\n\tb.T.Helper()\n\n\tconst (\n\t\tmaxRetry = 5\n\t\tsleep    = time.Second\n\t)\n\tfor i := 0; i < maxRetry; i++ {\n\t\tif b.InspectContainer(con).State.Running {\n\t\t\tb.T.Logf(\"container %s is now running\", con)\n\t\t\treturn\n\t\t}\n\t\tb.T.Logf(\"(retry=%d)\", i+1)\n\t\ttime.Sleep(sleep)\n\t}\n\tb.T.Fatalf(\"conainer %s not running\", con)\n}\n\nfunc (b *Base) EnsureContainerExited(con string, expectedExitCode int) {\n\tb.T.Helper()\n\n\tconst (\n\t\tmaxRetry = 5\n\t\tsleep    = time.Second\n\t)\n\tvar c dockercompat.Container\n\tfor i := 0; i < maxRetry; i++ {\n\t\tc = b.InspectContainer(con)\n\t\tif c.State.Status == \"exited\" {\n\t\t\tb.T.Logf(\"container %s have exited with status %d\", con, c.State.ExitCode)\n\t\t\tif c.State.ExitCode == expectedExitCode {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tb.T.Logf(\"(retry=%d)\", i+1)\n\t\ttime.Sleep(sleep)\n\t}\n\tb.T.Fatalf(\"expected conainer %s to have exited with code %d, got status %+v\",\n\t\tcon, expectedExitCode, c.State)\n}\n\ntype Cmd struct {\n\ticmd.Cmd\n\t*Base\n\trunResult *icmd.Result\n\tmu        sync.Mutex\n}\n\nfunc (c *Cmd) Run() *icmd.Result {\n\tc.Base.T.Helper()\n\tc.mu.Lock()\n\tc.runResult = icmd.RunCmd(c.Cmd)\n\tc.mu.Unlock()\n\treturn c.runResult\n}\n\nfunc (c *Cmd) runIfNecessary() *icmd.Result {\n\tc.Base.T.Helper()\n\tc.mu.Lock()\n\tif c.runResult == nil {\n\t\tc.runResult = icmd.RunCmd(c.Cmd)\n\t}\n\tc.mu.Unlock()\n\treturn c.runResult\n}\n\nfunc (c *Cmd) Start() *icmd.Result {\n\tc.Base.T.Helper()\n\treturn icmd.StartCmd(c.Cmd)\n}\n\nfunc (c *Cmd) CmdOption(cmdOptions ...func(*Cmd)) *Cmd {\n\tfor _, opt := range cmdOptions {\n\t\topt(c)\n\t}\n\treturn c\n}\n\nfunc (c *Cmd) Assert(expected icmd.Expected) {\n\tc.Base.T.Helper()\n\tc.runIfNecessary().Assert(c.Base.T, expected)\n}\n\nfunc (c *Cmd) AssertOK() {\n\tc.Base.T.Helper()\n\tc.AssertExitCode(0)\n}\n\nfunc (c *Cmd) AssertFail() {\n\tc.Base.T.Helper()\n\tres := c.runIfNecessary()\n\tassert.Assert(c.Base.T, res.ExitCode != 0, res)\n}\n\nfunc (c *Cmd) AssertExitCode(exitCode int) {\n\tc.Base.T.Helper()\n\tres := c.runIfNecessary()\n\tassert.Assert(c.Base.T, res.ExitCode == exitCode, res)\n}\n\nfunc (c *Cmd) AssertOutContains(s string) {\n\tc.Base.T.Helper()\n\texpected := icmd.Expected{\n\t\tOut: s,\n\t}\n\tc.Assert(expected)\n}\n\nfunc (c *Cmd) AssertCombinedOutContains(s string) {\n\tc.Base.T.Helper()\n\tres := c.runIfNecessary()\n\tassert.Assert(c.Base.T, strings.Contains(res.Combined(), s), fmt.Sprintf(\"expected output to contain %q: %q\", s, res.Combined()))\n}\n\n// AssertOutContainsAll checks if command output contains All strings in `strs`.\nfunc (c *Cmd) AssertOutContainsAll(strs ...string) {\n\tc.Base.T.Helper()\n\tfn := func(stdout string) error {\n\t\tfor _, s := range strs {\n\t\t\tif !strings.Contains(stdout, s) {\n\t\t\t\treturn fmt.Errorf(\"expected stdout to contain %q\", s)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tc.AssertOutWithFunc(fn)\n}\n\n// AssertOutContainsAny checks if command output contains Any string in `strs`.\nfunc (c *Cmd) AssertOutContainsAny(strs ...string) {\n\tc.Base.T.Helper()\n\tfn := func(stdout string) error {\n\t\tfor _, s := range strs {\n\t\t\tif strings.Contains(stdout, s) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"expected stdout to contain any of %q\", strings.Join(strs, \"|\"))\n\t}\n\tc.AssertOutWithFunc(fn)\n}\n\nfunc (c *Cmd) AssertOutNotContains(s string) {\n\tc.Base.T.Helper()\n\tc.AssertOutWithFunc(func(stdout string) error {\n\t\tif strings.Contains(stdout, s) {\n\t\t\treturn fmt.Errorf(\"expected stdout to not contain %q\", s)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *Cmd) AssertOutExactly(s string) {\n\tc.Base.T.Helper()\n\tfn := func(stdout string) error {\n\t\tif stdout != s {\n\t\t\treturn fmt.Errorf(\"expected %q, got %q\", s, stdout)\n\t\t}\n\t\treturn nil\n\t}\n\tc.AssertOutWithFunc(fn)\n}\n\nfunc (c *Cmd) AssertOutWithFunc(fn func(stdout string) error) {\n\tc.Base.T.Helper()\n\tres := c.runIfNecessary()\n\tassert.Equal(c.Base.T, 0, res.ExitCode, res)\n\tassert.NilError(c.Base.T, fn(res.Stdout()), res.Combined())\n}\n\nfunc (c *Cmd) Out() string {\n\tc.Base.T.Helper()\n\tres := c.runIfNecessary()\n\tassert.Equal(c.Base.T, 0, res.ExitCode, res)\n\treturn res.Stdout()\n}\n\nvar (\n\tflagTestTarget      string\n\tflagTestKillDaemon  bool\n\tflagTestIPv6        bool\n\tflagTestKube        bool\n\tflagTestFlaky       bool\n\tflagTestModifyUsers bool\n)\n\nvar (\n\ttestLockFile = filepath.Join(os.TempDir(), \"nerdctl-test-prevent-concurrency\", \".lock\")\n)\n\nfunc M(m *testing.M) {\n\tflag.StringVar(&flagTestTarget, \"test.target\", \"nerdctl\", \"target to test\")\n\tflag.BoolVar(&flagTestKillDaemon, \"test.allow-kill-daemon\", false, \"enable tests that kill the daemon\")\n\tflag.BoolVar(&flagTestModifyUsers, \"test.allow-modify-users\", false, \"enable tests that creates/deletes user accounts on the host\")\n\tflag.BoolVar(&flagTestIPv6, \"test.only-ipv6\", false, \"enable tests on IPv6\")\n\tflag.BoolVar(&flagTestKube, \"test.only-kubernetes\", false, \"enable tests on Kubernetes\")\n\tflag.BoolVar(&flagTestFlaky, \"test.only-flaky\", false, \"enable testing of flaky tests only (if false, flaky tests are ignored)\")\n\tflag.Parse()\n\n\tif flagTestTarget == \"\" {\n\t\tflagTestTarget = \"nerdctl\"\n\t}\n\n\tos.Exit(func() int {\n\t\t// If there is a lockfile (no err), or if we error-ed stating it (permission), another test run is currently going.\n\t\t// Note that this could be racy. The .lock file COULD get acquired after this and before we hit the lock section.\n\t\t// This is not a big deal then: we will just wait for the lock to free.\n\t\tif _, err := os.Stat(testLockFile); err == nil || !errors.Is(err, os.ErrNotExist) {\n\t\t\tlog.L.Errorf(\"Another test binary is already running. If you think this is an error, manually remove %s\", testLockFile)\n\t\t\treturn 1\n\t\t}\n\n\t\terr := os.MkdirAll(filepath.Dir(testLockFile), 0o777)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"failed creating testing lock directory %q\", filepath.Dir(testLockFile))\n\t\t\treturn 1\n\t\t}\n\n\t\t// Ensure that permissions are set to 777 (regardless of umask value), so that we do not lock people out when\n\t\t// switching between rootful and rootless locking\n\t\tos.Chmod(filepath.Dir(testLockFile), 0o777)\n\n\t\t// Acquire lock\n\t\tlock, err := filesystem.Lock(filepath.Dir(testLockFile))\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"failed acquiring testing lock %q\", filepath.Dir(testLockFile))\n\t\t\treturn 1\n\t\t}\n\n\t\t// Release...\n\t\tdefer filesystem.Unlock(lock)\n\n\t\t// Create marker file\n\t\terr = filesystem.WriteFile(testLockFile, []byte(\"prevent testing from running in parallel for subpackages integration tests\"), 0o666)\n\t\tif err != nil {\n\t\t\tlog.L.WithError(err).Errorf(\"failed writing lock file %q\", testLockFile)\n\t\t\treturn 1\n\t\t}\n\n\t\t// Ensure cleanup\n\t\tdefer func() {\n\t\t\tos.Remove(testLockFile)\n\t\t}()\n\n\t\t// Now, run the tests\n\t\tfmt.Fprintf(os.Stderr, \"test target: %q\\n\", flagTestTarget)\n\n\t\treturn m.Run()\n\t}())\n}\n\nfunc GetTarget() string {\n\tif flagTestTarget == \"\" {\n\t\tpanic(\"GetTarget() was called without calling M()\")\n\t}\n\treturn flagTestTarget\n}\n\nfunc GetEnableIPv6() bool {\n\treturn flagTestIPv6\n}\n\nfunc GetEnableKubernetes() bool {\n\treturn flagTestKube\n}\n\nfunc GetFlakyEnvironment() bool {\n\treturn flagTestFlaky\n}\n\nfunc GetDaemonIsKillable() bool {\n\treturn flagTestKillDaemon\n}\n\nfunc GetAllowModifyUsers() bool {\n\treturn flagTestModifyUsers\n}\n\nfunc IsDocker() bool {\n\treturn strings.HasPrefix(filepath.Base(GetTarget()), \"docker\")\n}\n\nfunc DockerIncompatible(t testing.TB) {\n\tif IsDocker() {\n\t\tt.Skip(\"test is incompatible with Docker\")\n\t}\n}\n\nfunc RequiresBuild(t testing.TB) {\n\tif !IsDocker() {\n\t\tbuildkitHost, err := buildkitutil.GetBuildkitHost(Namespace)\n\t\tif err != nil {\n\t\t\tt.Skipf(\"test requires buildkitd: %+v\", err)\n\t\t}\n\t\tt.Logf(\"buildkitHost=%q\", buildkitHost)\n\t}\n}\n\nfunc RequireExecPlatform(t testing.TB, ss ...string) {\n\tok, err := platformutil.CanExecProbably(ss...)\n\tif !ok {\n\t\tmsg := fmt.Sprintf(\"test requires platform %v\", ss)\n\t\tif err != nil {\n\t\t\tmsg += fmt.Sprintf(\": %v\", err)\n\t\t}\n\t\tt.Skip(msg)\n\t}\n}\n\nfunc RequireKernelVersion(t testing.TB, constraint string) {\n\tt.Helper()\n\tc, err := semver.NewConstraint(constraint)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// EL kernel versions are not semver, so, cleanup first\n\tun := strings.Split(infoutil.UnameR(), \"-\")[0]\n\tunameR, err := semver.NewVersion(un)\n\tif err != nil {\n\t\tt.Skip(err)\n\t}\n\tif !c.Check(unameR) {\n\t\tt.Skipf(\"version %v does not satisfy constraints %v\", unameR, c)\n\t}\n}\n\nfunc RequireContainerdPlugin(base *Base, requiredType, requiredID string, requiredCaps []string) {\n\tbase.T.Helper()\n\tinfo := base.InfoNative()\n\tfor _, p := range info.Daemon.Plugins.Plugins {\n\t\tif p.Type != requiredType {\n\t\t\tcontinue\n\t\t}\n\t\tif p.ID != requiredID {\n\t\t\tcontinue\n\t\t}\n\t\tpCapMap := make(map[string]struct{}, len(p.Capabilities))\n\t\tfor _, f := range p.Capabilities {\n\t\t\tpCapMap[f] = struct{}{}\n\t\t}\n\t\tfor _, f := range requiredCaps {\n\t\t\tif _, ok := pCapMap[f]; !ok {\n\t\t\t\tbase.T.Skipf(\"test requires containerd plugin \\\"%s.%s\\\" with capabilities %v (missing %q)\", requiredType, requiredID, requiredCaps, f)\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\tif len(requiredCaps) == 0 {\n\t\tbase.T.Skipf(\"test requires containerd plugin \\\"%s.%s\\\"\", requiredType, requiredID)\n\t} else {\n\t\tbase.T.Skipf(\"test requires containerd plugin \\\"%s.%s\\\" with capabilities %v\", requiredType, requiredID, requiredCaps)\n\t}\n}\n\nfunc RequireSystemService(t testing.TB, sv string) {\n\tt.Helper()\n\tif runtime.GOOS != \"linux\" {\n\t\tt.Skipf(\"Service %q is not supported on %q\", sv, runtime.GOOS)\n\t}\n\tvar systemctlArgs []string\n\tif rootlessutil.IsRootless() {\n\t\tsystemctlArgs = append(systemctlArgs, \"--user\")\n\t}\n\tsystemctlArgs = append(systemctlArgs, []string{\"-q\", \"is-active\", sv}...)\n\tcmd := exec.Command(\"systemctl\", systemctlArgs...)\n\tif err := cmd.Run(); err != nil {\n\t\tt.Skipf(\"Service %q does not seem active: %v: %v\", sv, cmd.Args, err)\n\t}\n}\n\n// RequireExecutable skips tests when executable `name` is not present in PATH.\nfunc RequireExecutable(t testing.TB, name string) {\n\tif _, err := exec.LookPath(name); err != nil {\n\t\tt.Skipf(\"required executable doesn't exist in PATH: %s\", name)\n\t}\n}\n\nconst Namespace = \"nerdctl-test\"\n\nfunc NewBaseWithNamespace(t *testing.T, ns string) *Base {\n\tif ns == \"\" || ns == \"default\" || ns == Namespace {\n\t\tt.Fatalf(`the other base namespace cannot be \"%s\"`, ns)\n\t}\n\treturn newBase(t, ns, false, false)\n}\n\nfunc NewBaseWithIPv6Compatible(t *testing.T) *Base {\n\treturn newBase(t, Namespace, true, false)\n}\n\nfunc NewBase(t *testing.T) *Base {\n\treturn newBase(t, Namespace, false, false)\n}\n\nfunc newBase(t *testing.T, ns string, ipv6Compatible bool, kubernetesCompatible bool) *Base {\n\tbase := &Base{\n\t\tT:                    t,\n\t\tTarget:               GetTarget(),\n\t\tDaemonIsKillable:     GetDaemonIsKillable(),\n\t\tEnableIPv6:           GetEnableIPv6(),\n\t\tIPv6Compatible:       ipv6Compatible,\n\t\tEnableKubernetes:     GetEnableKubernetes(),\n\t\tKubernetesCompatible: kubernetesCompatible,\n\t\tEnv:                  os.Environ(),\n\t}\n\tif base.EnableIPv6 && !base.IPv6Compatible {\n\t\tt.Skip(\"runner skips non-IPv6 compatible tests in the IPv6 environment\")\n\t} else if !base.EnableIPv6 && base.IPv6Compatible {\n\t\tt.Skip(\"runner skips IPv6 compatible tests in the non-IPv6 environment\")\n\t}\n\tif base.EnableKubernetes && !base.KubernetesCompatible {\n\t\tt.Skip(\"runner skips non-Kubernetes compatible tests in the Kubernetes environment\")\n\t} else if !base.EnableKubernetes && base.KubernetesCompatible {\n\t\tt.Skip(\"runner skips Kubernetes compatible tests in the non-Kubernetes environment\")\n\t}\n\tif !GetFlakyEnvironment() && !GetEnableKubernetes() && !GetEnableIPv6() {\n\t\tt.Skip(\"legacy tests are considered flaky by default and are skipped unless in the flaky environment\")\n\t}\n\tvar err error\n\tbase.Binary, err = exec.LookPath(base.Target)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif IsDocker() {\n\t\tif err = exec.Command(base.Binary, \"compose\", \"version\").Run(); err != nil {\n\t\t\tt.Fatalf(\"docker does not support compose: %v\", err)\n\t\t}\n\t} else {\n\t\tbase.Args = []string{\"--namespace=\" + ns}\n\t}\n\n\treturn base\n}\n\n// Identifier can be used as a name of container, image, volume, network, etc.\nfunc Identifier(t testing.TB) string {\n\ts := t.Name()\n\ts = strings.ReplaceAll(s, \" \", \"_\")\n\ts = strings.ReplaceAll(s, \"/\", \"-\")\n\ts = strings.ToLower(s)\n\ts = \"nerdctl-\" + s\n\tif len(s) > 76 {\n\t\ts = \"nerdctl-\" + digest.SHA256.FromString(t.Name()).Encoded()\n\t}\n\treturn s\n}\n\n// RegisterBuildCacheCleanup adds a 'builder prune --all --force' cleanup function\n// to run on test teardown.\nfunc RegisterBuildCacheCleanup(t *testing.T) {\n\tt.Cleanup(func() {\n\t\tNewBase(t).Cmd(\"builder\", \"prune\", \"--all\", \"--force\").Run()\n\t})\n}\n"
  },
  {
    "path": "pkg/testutil/testutil_darwin.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nconst (\n\tCommonImage = \"\"\n\n\t// This error string is expected when attempting to connect to a TCP socket\n\t// for a service which actively refuses the connection.\n\t// (e.g. attempting to connect using http to an https endpoint).\n\t// It should be \"connection refused\" as per the TCP RFC.\n\t// https://www.rfc-editor.org/rfc/rfc793\n\tExpectedConnectionRefusedError = \"connection refused\"\n)\n\nvar (\n\tBusyboxImage     = \"there-is-no-test-on-darwin\"\n\tAlpineImage      = \"there-is-no-test-on-darwin\"\n\tNginxAlpineImage = \"there-is-no-test-on-darwin\"\n\tGolangImage      = \"there-is-no-test-on-darwin\"\n)\n"
  },
  {
    "path": "pkg/testutil/testutil_freebsd.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nconst (\n\tCommonImage = \"docker.io/knast/freebsd:13-STABLE\"\n\n\t// This error string is expected when attempting to connect to a TCP socket\n\t// for a service which actively refuses the connection.\n\t// (e.g. attempting to connect using http to an https endpoint).\n\t// It should be \"connection refused\" as per the TCP RFC.\n\t// https://www.rfc-editor.org/rfc/rfc793\n\tExpectedConnectionRefusedError = \"connection refused\"\n)\n\nvar (\n\tBusyboxImage     = \"there-is-no-such-test-on-freebsd\"\n\tAlpineImage      = \"there-is-no-such-test-on-freebsd\"\n\tNginxAlpineImage = \"there-is-no-such-test-on-freebsd\"\n\tGolangImage      = \"there-is-no-such-test-on-freebsd\"\n)\n"
  },
  {
    "path": "pkg/testutil/testutil_linux.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nvar (\n\tAlpineImage         = GetTestImage(\"alpine\")\n\tBusyboxImage        = GetTestImage(\"busybox\")\n\tDockerAuthImage     = GetTestImage(\"docker_auth\")\n\tFluentdImage        = GetTestImage(\"fluentd\")\n\tGolangImage         = GetTestImage(\"golang\")\n\tKuboImage           = GetTestImage(\"kubo\")\n\tMariaDBImage        = GetTestImage(\"mariadb\")\n\tNginxAlpineImage    = GetTestImage(\"nginx\")\n\tRegistryImageStable = GetTestImage(\"registry\")\n\tSystemdImage        = GetTestImage(\"stargz\")\n\tWordpressImage      = GetTestImage(\"wordpress\")\n\n\tCommonImage = AlpineImage\n\n\tFedoraESGZImage = GetTestImage(\"fedora_esgz\") // eStargz\n\tFfmpegSociImage = GetTestImage(\"ffmpeg_soci\") // SOCI\n\tUbuntuImage     = GetTestImage(\"ubuntu\")      // Large enough for testing soci index creation\n\tCoreDNSImage    = GetTestImage(\"coredns\")\n)\n\nconst (\n\t// This error string is expected when attempting to connect to a TCP socket\n\t// for a service which actively refuses the connection.\n\t// (e.g. attempting to connect using http to an https endpoint).\n\t// It should be \"connection refused\" as per the TCP RFC.\n\t// https://www.rfc-editor.org/rfc/rfc793\n\tExpectedConnectionRefusedError = \"connection refused\"\n\n\tNginxAlpineIndexHTMLSnippet = \"<title>Welcome to nginx!</title>\"\n\tWordpressIndexHTMLSnippet   = \"<title>WordPress &rsaquo; Installation</title>\"\n\n\t// Source: https://gist.github.com/cpuguy83/fcf3041e5d8fb1bb5c340915aabeebe0\n\tNonDistBlobImage = \"ghcr.io/cpuguy83/non-dist-blob:latest@sha256:8859ffb0bb604463fe19f1e606ceda9f4f8f42e095bf78c42458cf6da7b5c7e7\"\n\t// Foreign layer digest\n\tNonDistBlobDigest = \"sha256:be691b1535726014cdf3b715ff39361b19e121ca34498a9ceea61ad776b9c215\"\n)\n"
  },
  {
    "path": "pkg/testutil/testutil_windows.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage testutil\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/Microsoft/hcsshim\"\n\t\"golang.org/x/sys/windows/svc/mgr\"\n\n\t\"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat\"\n)\n\nconst (\n\t// CommonImage.\n\t//\n\t// More work needs to be done to support windows containers in test framework\n\t// for the tests that are run now this image (used in k8s upstream testing) meets the needs\n\t// use gcr.io/k8s-staging-e2e-test-images/busybox:1.36-1-windows-amd64-ltsc2022 locally on windows 11\n\t// https://github.com/microsoft/Windows-Containers/issues/179\n\tBusyboxImage = \"gcr.io/k8s-staging-e2e-test-images/busybox:1.36.1-1\"\n\tWindowsNano  = BusyboxImage\n\tCommonImage  = WindowsNano\n\n\t// NOTE(aznashwan): the upstream e2e Nginx test image is actually based on BusyBox.\n\tNginxAlpineImage            = \"registry.k8s.io/e2e-test-images/nginx:1.14-2\"\n\tNginxAlpineIndexHTMLSnippet = \"<title>Welcome to nginx!</title>\"\n\n\t// This error string is expected when attempting to connect to a TCP socket\n\t// for a service which actively refuses the connection.\n\t// (e.g. attempting to connect using http to an https endpoint).\n\t// It should be \"connection refused\" as per the TCP RFC, but it is the\n\t// below string constant on Windows.\n\t// https://www.rfc-editor.org/rfc/rfc793\n\tExpectedConnectionRefusedError = \"No connection could be made because the target machine actively refused it.\"\n)\n\nvar (\n\tGolangImage = \"fixme-test-using-this-image-is-disabled-on-windows\"\n\tAlpineImage = \"fixme-test-using-this-image-is-disabled-on-windows\"\n\n\thypervContainer     bool\n\thypervSupported     bool\n\thypervSupportedOnce sync.Once\n)\n\n// HyperVSupported is a test helper to check if hyperv is enabled on\n// the host. This can be used to skip tests that require virtualization.\nfunc HyperVSupported() bool {\n\tif s := os.Getenv(\"NO_HYPERV\"); s != \"\" {\n\t\tif b, err := strconv.ParseBool(s); err == nil && b {\n\t\t\treturn false\n\t\t}\n\t}\n\thypervSupportedOnce.Do(func() {\n\t\t// Hyper-V Virtual Machine Management service name\n\t\tconst hypervServiceName = \"vmms\"\n\n\t\tm, err := mgr.Connect()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer m.Disconnect()\n\n\t\ts, err := m.OpenService(hypervServiceName)\n\t\t// hyperv service was present\n\t\tif err == nil {\n\t\t\thypervSupported = true\n\t\t\ts.Close()\n\t\t}\n\t})\n\treturn hypervSupported\n}\n\n// HyperVContainer is a test helper to check if the container is a\n// hyperv type container, lists only running containers\nfunc HyperVContainer(inspect dockercompat.Container) (bool, error) {\n\tquery := hcsshim.ComputeSystemQuery{}\n\tcontainersList, err := hcsshim.GetContainers(query)\n\tif err != nil {\n\t\thypervContainer = false\n\t\treturn hypervContainer, err\n\t}\n\n\tfor _, container := range containersList {\n\t\t// have to use IDs, not all containers have name set\n\t\tif strings.Contains(container.ID, inspect.ID) {\n\t\t\tif container.SystemType == \"VirtualMachine\" {\n\t\t\t\thypervContainer = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn hypervContainer, nil\n}\n"
  },
  {
    "path": "pkg/transferutil/progress.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage transferutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\tocispec \"github.com/opencontainers/image-spec/specs-go/v1\"\n\n\t\"github.com/containerd/containerd/v2/core/transfer\"\n\t\"github.com/containerd/containerd/v2/pkg/progress\"\n)\n\n// From https://github.com/containerd/containerd/blob/v2.2.0-rc.0/cmd/ctr/commands/image/pull.go#L240-L473\ntype progressNode struct {\n\ttransfer.Progress\n\tchildren []*progressNode\n\troot     bool\n}\n\nfunc (n *progressNode) mainDesc() *ocispec.Descriptor {\n\tif n.Desc != nil {\n\t\treturn n.Desc\n\t}\n\tfor _, c := range n.children {\n\t\tif desc := c.mainDesc(); desc != nil {\n\t\t\treturn desc\n\t\t}\n\t}\n\treturn nil\n}\n\n// ProgressHandler returns a progress callback and a cleanup function to render transfer progress.\n// This implementation is based on containerd's ctr command progress handler.\nfunc ProgressHandler(ctx context.Context, out io.Writer) (transfer.ProgressFunc, func()) {\n\tctx, cancel := context.WithCancel(ctx)\n\tvar (\n\t\tfw       = progress.NewWriter(out)\n\t\tstart    = time.Now()\n\t\tstatuses = map[string]*progressNode{}\n\t\troots    = []*progressNode{}\n\t\tpc       = make(chan transfer.Progress, 5)\n\t\tstatus   string\n\t\tcloseC   = make(chan struct{})\n\t)\n\n\tprogressFn := func(p transfer.Progress) {\n\t\tselect {\n\t\tcase pc <- p:\n\t\tcase <-ctx.Done():\n\t\t}\n\t}\n\n\tdone := func() {\n\t\tcancel()\n\t\t<-closeC\n\t}\n\n\tgo func() {\n\t\tdefer close(closeC)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase p := <-pc:\n\t\t\t\tif p.Name == \"\" {\n\t\t\t\t\tstatus = p.Event\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif node, ok := statuses[p.Name]; !ok {\n\t\t\t\t\tnode = &progressNode{\n\t\t\t\t\t\tProgress: p,\n\t\t\t\t\t\troot:     true,\n\t\t\t\t\t}\n\t\t\t\t\tif len(p.Parents) == 0 {\n\t\t\t\t\t\troots = append(roots, node)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar parents []string\n\t\t\t\t\t\tfor _, parent := range p.Parents {\n\t\t\t\t\t\t\tpStatus, ok := statuses[parent]\n\t\t\t\t\t\t\tif ok {\n\t\t\t\t\t\t\t\tparents = append(parents, parent)\n\t\t\t\t\t\t\t\tpStatus.children = append(pStatus.children, node)\n\t\t\t\t\t\t\t\tnode.root = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tnode.Progress.Parents = parents\n\t\t\t\t\t\tif node.root {\n\t\t\t\t\t\t\troots = append(roots, node)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tstatuses[p.Name] = node\n\t\t\t\t} else {\n\t\t\t\t\tif len(node.Progress.Parents) != len(p.Parents) {\n\t\t\t\t\t\tvar parents []string\n\t\t\t\t\t\tvar removeRoot bool\n\t\t\t\t\t\tfor _, parent := range p.Parents {\n\t\t\t\t\t\t\tpStatus, ok := statuses[parent]\n\t\t\t\t\t\t\tif ok {\n\t\t\t\t\t\t\t\tparents = append(parents, parent)\n\t\t\t\t\t\t\t\tvar found bool\n\t\t\t\t\t\t\t\tfor _, child := range pStatus.children {\n\t\t\t\t\t\t\t\t\tif child.Progress.Name == p.Name {\n\t\t\t\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !found {\n\t\t\t\t\t\t\t\t\tpStatus.children = append(pStatus.children, node)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif node.root {\n\t\t\t\t\t\t\t\t\tremoveRoot = true\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tnode.root = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp.Parents = parents\n\t\t\t\t\t\t// Check if needs to remove from root\n\t\t\t\t\t\tif removeRoot {\n\t\t\t\t\t\t\tfor i := range roots {\n\t\t\t\t\t\t\t\tif roots[i] == node {\n\t\t\t\t\t\t\t\t\troots = append(roots[:i], roots[i+1:]...)\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tnode.Progress = p\n\t\t\t\t}\n\n\t\t\t\tdisplayHierarchy(fw, status, roots, start)\n\t\t\t\tfw.Flush()\n\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn progressFn, done\n}\n\nfunc displayHierarchy(w io.Writer, status string, roots []*progressNode, start time.Time) {\n\ttotal := displayNode(w, \"\", roots)\n\tfor _, r := range roots {\n\t\tif desc := r.mainDesc(); desc != nil {\n\t\t\tfmt.Fprintf(w, \"%s %s\\n\", desc.MediaType, desc.Digest)\n\t\t}\n\t}\n\t// Print the Status line\n\tfmt.Fprintf(w, \"%s\\telapsed: %-4.1fs\\ttotal: %7.6v\\t(%v)\\t\\n\",\n\t\tstatus,\n\t\ttime.Since(start).Seconds(),\n\t\tprogress.Bytes(total),\n\t\tprogress.NewBytesPerSecond(total, time.Since(start)))\n}\n\nfunc displayNode(w io.Writer, prefix string, nodes []*progressNode) int64 {\n\tvar total int64\n\tfor i, node := range nodes {\n\t\tstatus := node.Progress\n\t\ttotal += status.Progress\n\t\tpf, cpf := prefixes(i, len(nodes))\n\t\tif node.root {\n\t\t\tpf, cpf = \"\", \"\"\n\t\t}\n\n\t\tname := prefix + pf + shortenName(status.Name)\n\n\t\tswitch status.Event {\n\t\tcase \"downloading\", \"uploading\", \"extracting\":\n\t\t\tvar bar progress.Bar\n\t\t\tif status.Total > 0.0 {\n\t\t\t\tbar = progress.Bar(float64(status.Progress) / float64(status.Total))\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"%-40.40s\\t%-11s\\t%40r\\t%8.8s/%s\\t\\n\",\n\t\t\t\tname,\n\t\t\t\tstatus.Event,\n\t\t\t\tbar,\n\t\t\t\tprogress.Bytes(status.Progress), progress.Bytes(status.Total))\n\t\tcase \"resolving\", \"waiting\":\n\t\t\tbar := progress.Bar(0.0)\n\t\t\tfmt.Fprintf(w, \"%-40.40s\\t%-11s\\t%40r\\t\\n\",\n\t\t\t\tname,\n\t\t\t\tstatus.Event,\n\t\t\t\tbar)\n\t\tcase \"complete\", \"extracted\":\n\t\t\tbar := progress.Bar(1.0)\n\t\t\tfmt.Fprintf(w, \"%-40.40s\\t%-11s\\t%40r\\t\\n\",\n\t\t\t\tname,\n\t\t\t\tstatus.Event,\n\t\t\t\tbar)\n\t\tdefault:\n\t\t\tfmt.Fprintf(w, \"%-40.40s\\t%s\\t\\n\",\n\t\t\t\tname,\n\t\t\t\tstatus.Event)\n\t\t}\n\t\ttotal += displayNode(w, prefix+cpf, node.children)\n\t}\n\treturn total\n}\n\nfunc prefixes(index, length int) (string, string) {\n\tif index+1 == length {\n\t\treturn \"└──\", \"   \"\n\t}\n\treturn \"├──\", \"│  \"\n}\n\nfunc shortenName(name string) string {\n\tif strings.HasPrefix(name, \"sha256:\") && len(name) == 71 {\n\t\treturn \"(\" + name[7:19] + \")\"\n\t}\n\treturn name\n}\n"
  },
  {
    "path": "pkg/version/version.go",
    "content": "/*\n   Copyright The containerd Authors.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"runtime/debug\"\n\t\"strconv\"\n)\n\nvar (\n\t// Version is filled via Makefile\n\tVersion = \"\"\n\t// Revision is filled via Makefile\n\tRevision = \"\"\n)\n\nconst unknown = \"<unknown>\"\n\nfunc GetVersion() string {\n\tif Version != \"\" {\n\t\treturn Version\n\t}\n\t/*\n\t * go install example.com/cmd/foo@vX.Y.Z: bi.Main.Version=\"vX.Y.Z\",                               vcs.revision is unset\n\t * go install example.com/cmd/foo@latest: bi.Main.Version=\"vX.Y.Z\",                               vcs.revision is unset\n\t * go install example.com/cmd/foo@master: bi.Main.Version=\"vX.Y.Z-N.yyyyMMddhhmmss-gggggggggggg\", vcs.revision is unset\n\t * go install ./cmd/foo:                  bi.Main.Version=\"(devel)\", vcs.revision=\"gggggggggggggggggggggggggggggggggggggggg\"\n\t *                                        vcs.time=\"yyyy-MM-ddThh:mm:ssZ\", vcs.modified=(\"false\"|\"true\")\n\t */\n\tif bi, ok := debug.ReadBuildInfo(); ok {\n\t\tif bi.Main.Version != \"\" && bi.Main.Version != \"(devel)\" {\n\t\t\treturn bi.Main.Version\n\t\t}\n\t}\n\treturn unknown\n}\n\nfunc GetRevision() string {\n\tif Revision != \"\" {\n\t\treturn Revision\n\t}\n\tif bi, ok := debug.ReadBuildInfo(); ok {\n\t\tvar (\n\t\t\tvcsRevision string\n\t\t\tvcsModified bool\n\t\t)\n\t\tfor _, f := range bi.Settings {\n\t\t\tswitch f.Key {\n\t\t\tcase \"vcs.revision\":\n\t\t\t\tvcsRevision = f.Value\n\t\t\tcase \"vcs.modified\":\n\t\t\t\tvcsModified, _ = strconv.ParseBool(f.Value)\n\t\t\t}\n\t\t}\n\t\tif vcsRevision == \"\" {\n\t\t\treturn unknown\n\t\t}\n\t\trev := vcsRevision\n\t\tif vcsModified {\n\t\t\trev += \".m\"\n\t\t}\n\t\treturn rev\n\t}\n\treturn unknown\n}\n"
  }
]