[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug Report\nabout: Report a bug encountered using kind\nlabels: kind/bug\n\n---\n\n<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!-->\n\n\n**What happened**:\n\n<!-- If creating a cluster failed, please run the create cluster command again with the `--retain` flag to prevent cleanup on failure, then run `kind export logs` to dump the cluster logs before cleaning up manually with `kind delete cluster`. Then attach the logs from the path printed by `kind export logs` to this issue as a zip or tarball archive. This will aid us greatly in diagnosing the failure. When `kubeadm init` / `kubeadm join` fail, there are many possible cases and the kubeadm logs typically don't contain enough details vs the full cluster logs. Thanks!-->\n\n**What you expected to happen**:\n\n**How to reproduce it (as _minimally_ and precisely as possible)**:\n\n**Anything else we need to know?**:\n\n**Environment:**\n\n- kind version: (use `kind version`):\n- Runtime info: (use `docker info`, `podman info` or `nerdctl info`):\n- OS (e.g. from `/etc/os-release`):\n- Kubernetes version: (use `kubectl version`):\n- Any proxies or other special environment settings?:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/cleanup.md",
    "content": "---\nname: Cleanup \nabout: Pay down technical debt, reduce friction, etc.\nlabels: kind/cleanup\n\n---\n\n<!-- Please use this template while filing an issue to highlight technical debt to be paid down, or friction to be reduced -->\n\n**What should be cleaned up or changed**:\n\n**Why is this needed**:\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.md",
    "content": "---\nname: Documentation Request\nabout: Suggest what should be documented in kind\nlabels: kind/documentation\n\n---\n<!-- Please only use this template for submitting documentation requests -->\n\n**What would you like to be documented**:\n\n**Why is this needed**:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.md",
    "content": "---\nname: Enhancement Request\nabout: Suggest an enhancement to kind\nlabels: kind/feature\n\n---\n<!-- Please only use this template for submitting enhancement requests -->\n\n**What would you like to be added**:\n\n**Why is this needed**:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: Ask a question about using kind\nlabels: kind/support\n\n---\n\n<!-- Consider also checking https://kind.sigs.k8s.io/#community-discussion-contribution-and-support for support, our slack community is especially helpful! -->\n"
  },
  {
    "path": ".github/actions/setup-env/action.yaml",
    "content": "name: \"Setup environment\"\ndescription: \"Performs common setup operations.\"\nruns:\n  using: \"composite\"\n  steps:\n    - name: Get go version\n      id: golangversion\n      run: |\n        echo \"go_version=$(cat .go-version)\" >> \"$GITHUB_OUTPUT\"\n      shell: bash\n\n    - name: Set up Go\n      id: go\n      uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1\n      with:\n        go-version: ${{ steps.golangversion.outputs.go_version }}\n        check-latest: true\n\n    - name: Install kind\n      run: sudo make install INSTALL_DIR=/usr/local/bin\n      shell: bash\n\n    - name: Install kubectl\n      run: |\n        curl -LO https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl\n        chmod +x ./kubectl\n        sudo mv ./kubectl /usr/local/bin/kubectl\n      shell: bash\n\n    - name: Enable ipv4 and ipv6 forwarding\n      run: |\n        sudo sysctl -w net.ipv6.conf.all.forwarding=1\n        sudo sysctl -w net.ipv4.ip_forward=1\n      shell: bash\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "---\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    labels:\n      - \"area/dependency\"\n      - \"release-note-none\"\n      - \"ok-to-test\"\n    open-pull-requests-limit: 10\n    groups:\n      actions:\n        update-types:\n          - \"minor\"\n          - \"patch\"\n"
  },
  {
    "path": ".github/workflows/docker.yaml",
    "content": "name: Docker\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - 'site/**'\n\npermissions:\n  contents: read\n\njobs:\n  docker:\n    name: Docker\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        ipFamily: [ipv4, ipv6]\n        deployment: [singleNode, multiNode]\n    env:\n      JOB_NAME: \"docker-${{ matrix.deployment }}-${{ matrix.ipFamily }}\"\n      IP_FAMILY: ${{ matrix.ipFamily }}\n    steps:\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - uses: ./.github/actions/setup-env\n\n      - name: Create single node cluster\n        if: ${{ matrix.deployment == 'singleNode' }}\n        run: |\n          cat <<EOF | /usr/local/bin/kind create cluster -v7 --wait 1m --retain --config=-\n          kind: Cluster\n          apiVersion: kind.x-k8s.io/v1alpha4\n          networking:\n            ipFamily: ${IP_FAMILY}\n          EOF\n\n      - name: Create multi node cluster\n        if: ${{ matrix.deployment == 'multiNode' }}\n        run: |\n          cat <<EOF | /usr/local/bin/kind create cluster -v7 --wait 1m --retain --config=-\n          kind: Cluster\n          apiVersion: kind.x-k8s.io/v1alpha4\n          networking:\n            ipFamily: ${IP_FAMILY}\n          nodes:\n          - role: control-plane\n          - role: worker\n          - role: worker\n          EOF\n\n      - name: Get Cluster status\n        run: |\n          # wait network is ready\n          kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns\n          kubectl get nodes -o wide\n          kubectl get pods -A\n\n      - name: Load docker image\n        run: |\n          docker pull busybox\n          /usr/local/bin/kind load docker-image busybox\n\n      - name: Export logs\n        if: always()\n        run: |\n          mkdir -p /tmp/kind/logs\n          /usr/local/bin/kind export logs /tmp/kind/logs\n          sudo chown -R $USER:$USER /tmp/kind/logs\n\n      - name: Upload logs\n        if: always()\n        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}\n          path: /tmp/kind/logs\n\n      - name: Delete cluster\n        run: /usr/local/bin/kind delete cluster\n"
  },
  {
    "path": ".github/workflows/nerdctl.yaml",
    "content": "name: Nerdctl\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - 'site/**'\n\npermissions:\n  contents: read\n\njobs:\n  nerdctl:\n    name: Nerdctl\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        ipFamily: [ipv4, ipv6]\n        deployment: [singleNode, multiNode]\n        exclude:\n          - ipFamily: ipv6\n    env:\n      JOB_NAME: \"nerdctl-${{ matrix.deployment }}-${{ matrix.ipFamily }}\"\n      IP_FAMILY: ${{ matrix.ipFamily }}\n      NERDCTL_VERSION: \"2.0.2\"\n      KIND_EXPERIMENTAL_PROVIDER: \"nerdctl\"\n    steps:\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - uses: ./.github/actions/setup-env\n\n      - name: Install nerdctl\n        run: |\n          # Remove Docker and Podman\n          sudo systemctl is-active --quiet docker.service || systemctl stop docker.service\n          sudo apt-get remove -y docker-ce docker-ce-cli podman containerd.io\n          sudo rm -rf /etc/systemd/system/containerd.service # clean up the cotnainerd systemd file\n          # Install nerdctl full package\n          sudo curl -sSL https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-full-${NERDCTL_VERSION}-linux-amd64.tar.gz | sudo tar -xvz -C /usr/local\n          # Start Containerd\n          sudo systemctl daemon-reload\n          sudo systemctl enable --now containerd\n          # Show Versions\n          sudo ctr version\n          sudo nerdctl version\n\n      - name: Create single node cluster\n        if: ${{ matrix.deployment == 'singleNode' }}\n        run: |\n          cat <<EOF | sudo /usr/local/bin/kind create cluster -v7 --wait 1m --retain --config=-\n          kind: Cluster\n          apiVersion: kind.x-k8s.io/v1alpha4\n          networking:\n            ipFamily: ${IP_FAMILY}\n          EOF\n\n      - name: Create multi node cluster\n        if: ${{ matrix.deployment == 'multiNode' }}\n        run: |\n          cat <<EOF | sudo /usr/local/bin/kind create cluster -v7 --wait 1m --retain --config=-\n          kind: Cluster\n          apiVersion: kind.x-k8s.io/v1alpha4\n          networking:\n            ipFamily: ${IP_FAMILY}\n          nodes:\n          - role: control-plane\n          - role: worker\n          - role: worker\n          EOF\n\n      - name: Get Cluster status\n        run: |\n          # wait network is ready\n          sudo kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns\n          sudo kubectl get nodes -o wide\n          sudo kubectl get pods -A\n\n      # TODO: similar to podman, this fails because the imageID() code in KinD is hardcoded to run a docker command\n      # need to solve this code before this test will work properly\n      - name: Load nerdctl image\n        run: |\n          sudo nerdctl pull busybox\n          sudo /usr/local/bin/kind load docker-image busybox\n        continue-on-error: true\n\n      - name: Export logs\n        if: always()\n        run: |\n          sudo find /etc/cni/net.d/ -type f -exec sh -c 'echo \"{}\" && cat \"{}\"' \\;\n          sudo mkdir -p /tmp/kind/logs\n          sudo /usr/local/bin/kind export logs /tmp/kind/logs\n          sudo chown -R $USER:$USER /tmp/kind/logs\n\n      - name: Upload logs\n        if: always()\n        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}\n          path: /tmp/kind/logs\n\n      - name: Delete cluster\n        run: sudo /usr/local/bin/kind delete cluster\n"
  },
  {
    "path": ".github/workflows/podman.yml",
    "content": "name: Podman\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - 'site/**'\n\npermissions:\n  contents: read\n\njobs:\n  podman:\n    name: Podman\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        ipFamily: [ipv4, ipv6]\n        deployment: [singleNode, multiNode]\n        exclude:\n          - ipFamily: ipv6\n    env:\n      JOB_NAME: \"podman-${{ matrix.deployment }}-${{ matrix.ipFamily }}\"\n      KIND_EXPERIMENTAL_PROVIDER: \"podman\"\n      IP_FAMILY: ${{ matrix.ipFamily }}\n      PODMAN_VERSION: \"stable\"\n    steps:\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - uses: ./.github/actions/setup-env\n\n      - name: Setup podman\n        run: |\n          podman version\n          # podman requires dnsmasq for custom networks\n          # https://github.com/actions/virtual-environments/issues/2708\n          sudo apt-get update\n          sudo apt-get -y install dnsmasq\n          # crun >= 1.9.1 is required on Ubuntu 20.04.6\n          # https://github.com/kubernetes-sigs/kind/issues/3526\n          curl -Lo ./crun https://github.com/containers/crun/releases/download/1.14.3/crun-1.14.3-linux-amd64\n          chmod +x ./crun\n          sudo mv ./crun /usr/bin/crun\n\n      - name: Create single node cluster\n        if: ${{ matrix.deployment == 'singleNode' }}\n        run: |\n          cat <<EOF | sudo KIND_EXPERIMENTAL_PROVIDER=podman kind create cluster -v7 --wait 1m --retain --config=-\n          kind: Cluster\n          apiVersion: kind.x-k8s.io/v1alpha4\n          networking:\n            ipFamily: ${IP_FAMILY}\n          nodes:\n          - role: control-plane\n            extraPortMappings:\n            - containerPort: 80\n              hostPort: 80\n              listenAddress: 0.0.0.0\n            - containerPort: 443\n              hostPort: 443\n              listenAddress: 0.0.0.0\n          EOF\n\n      - name: Create multi node cluster\n        if: ${{ matrix.deployment == 'multiNode' }}\n        run: |\n          cat <<EOF | sudo KIND_EXPERIMENTAL_PROVIDER=podman kind create cluster -v7 --wait 1m --retain --config=-\n          kind: Cluster\n          apiVersion: kind.x-k8s.io/v1alpha4\n          networking:\n            ipFamily: ${IP_FAMILY}\n          nodes:\n          - role: control-plane\n          - role: worker\n          - role: worker\n          EOF\n\n      - name: Get Cluster status\n        run: |\n          # wait network is ready\n          sudo kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns\n          sudo kubectl get nodes -o wide\n          sudo kubectl get pods -A\n\n      # TODO: https://github.com/kubernetes-sigs/kind/issues/2038\n      - name: Load docker image\n        run: |\n          sudo podman pull busybox\n          sudo KIND_EXPERIMENTAL_PROVIDER=podman kind load docker-image busybox\n        continue-on-error: true\n\n      - name: Export logs\n        if: always()\n        run: |\n          mkdir -p /tmp/kind/logs\n          sudo KIND_EXPERIMENTAL_PROVIDER=podman kind export logs /tmp/kind/logs\n          sudo chown -R $USER:$USER /tmp/kind/logs\n\n      - name: Upload logs\n        if: always()\n        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}\n          path: /tmp/kind/logs\n\n      - name: Delete cluster\n        run: sudo KIND_EXPERIMENTAL_PROVIDER=podman kind delete cluster\n"
  },
  {
    "path": ".github/workflows/vm.yaml",
    "content": "name: VM\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches:\n      - main\n    paths-ignore:\n      - 'site/**'\n\npermissions:\n  contents: read\n\njobs:\n  vm:\n    name: \"VM\"\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # Fedora is different from Ubuntu in LSM (SELinux), filesystem (btrfs), kernel version, etc.\n          - template: fedora\n            provider: docker\n            rootless: rootful\n          - template: fedora\n            provider: docker\n            rootless: rootless\n          - template: fedora\n            provider: podman\n            rootless: rootful\n          - template: fedora\n            provider: podman\n            rootless: rootless\n    env:\n      KIND_EXPERIMENTAL_PROVIDER: \"${{ matrix.provider }}\"\n      ROOTLESS: \"${{ matrix.rootless }}\"\n      HELPER: \"./hack/ci/lima-helper.sh\"\n      JOB_NAME: \"vm-${{ matrix.template }}-${{ matrix.provider }}-${{ matrix.rootless }}\"\n    steps:\n      - name: Check out code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Get go version\n        id: golangversion\n        run: |\n          echo \"go_version=$(cat .go-version)\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Set up Go\n        id: go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: ${{ steps.golangversion.outputs.go_version }}\n          check-latest: true\n\n      - name: \"Install Lima\"\n        uses: lima-vm/lima-actions/setup@55627e31b78637bf254a8b2a14da8ea7d12564e5 # v1\n        id: lima-actions-setup\n\n      - name: \"Cache ~/.cache/lima\"\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: ~/.cache/lima\n          key: lima-${{ steps.lima-actions-setup.outputs.version }}-${{ matrix.template }}\n\n      - name: \"Start VM\"\n        # --plain is set to disable file sharing, port forwarding, built-in containerd, etc.\n        run: limactl start --name=default --plain template://${{ matrix.template }}\n\n      - name: \"Initialize VM\"\n        run: |\n          set -eux -o pipefail\n          # Sync the current directory to /tmp/kind in the guest\n          limactl cp -r . default:/tmp/kind\n          # Install packages\n          lima sudo /tmp/kind/hack/ci/init-vm.sh\n          # Enable systemd lingering for rootless\n          lima sudo loginctl enable-linger \"$USER\"\n          # Install kind\n          lima sudo git config --global --add safe.directory /tmp/kind\n          lima sudo make -C /tmp/kind install INSTALL_DIR=/usr/bin\n\n      - name: Set up Rootless Docker\n        if: ${{ matrix.provider == 'docker' && matrix.rootless == 'rootless' }}\n        run: |\n          # Disable the rootful daemon\n          \"$HELPER\" sudo systemctl disable --now docker\n          # Install the systemd unit\n          \"$HELPER\" dockerd-rootless-setuptool.sh install\n          # Modify the client config to use the rootless daemon by default\n          \"$HELPER\" docker context use rootless\n\n      - name: Show provider info\n        run: |\n          \"$HELPER\" \"$KIND_EXPERIMENTAL_PROVIDER\" info\n          \"$HELPER\" \"$KIND_EXPERIMENTAL_PROVIDER\" version\n\n      - name: Create a cluster\n        run: |\n          \"$HELPER\" kind create cluster -v7 --wait 10m --retain\n\n      - name: Get Cluster status\n        run: |\n          \"$HELPER\" kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns\n          \"$HELPER\" kubectl get nodes -o wide\n          \"$HELPER\" kubectl get pods -A\n\n      - name: Export logs\n        if: always()\n        run: |\n          \"$HELPER\" kind export logs /tmp/kind/logs\n          mkdir -p /tmp/kind/logs/lima\n          cp -a ~/.lima/default/*.log /tmp/kind/logs/lima || true\n          \"$HELPER\" tar cC /tmp/kind/logs . | tar xC /tmp/kind/logs\n\n      - name: Upload logs\n        if: always()\n        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}\n          path: /tmp/kind/logs\n"
  },
  {
    "path": ".gitignore",
    "content": "# build and test outputs\n/bin/\n/_output/\n/_artifacts/\n\n# used for the code generators only\n/vendor/\n\n# macOS\n.DS_Store\n\n# files generated by editors\n.idea/\n*.iml\n.vscode/\n*.swp\n*.sublime-project\n*.sublime-workspace\n*~\n"
  },
  {
    "path": ".go-version",
    "content": "1.25.7\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nWelcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:\n\n_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._\n\n## Getting Started\n\nWe have full documentation on how to get started contributing here: https://kind.sigs.k8s.io/docs/contributing/getting-started/, _please_ read this!\nA lot of work went into this guide 🙃\n\n## Mentorship\n\n- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - Kubernetes has a diverse set of mentorship programs available that are always looking for volunteers!\n\n<!---\nCustom Information - if you're copying this template for the first time you can add custom content here, for example:\n\n## Contact Information\n\n- [Slack channel](https://kubernetes.slack.com/messages/kubernetes-users) - Replace `kubernetes-users` with your slack channel string, this will send users directly to your channel. \n- [Mailing list](URL)\n\n-->\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 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": "Makefile",
    "content": "# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Old-skool build tools.\n# Simple makefile to build kind quickly and reproducibly\n#\n# Common uses:\n# - installing kind: `make install INSTALL_DIR=$HOME/go/bin`\n# - building: `make build`\n# - cleaning up and starting over: `make clean`\n#\n################################################################################\n# ========================== Capture Environment ===============================\n# get the repo root and output path\nREPO_ROOT:=${CURDIR}\nOUT_DIR=$(REPO_ROOT)/bin\n# record the source commit in the binary, overridable\nCOMMIT?=$(shell git rev-parse HEAD 2>/dev/null)\n# count the commits since the last release\nCOMMIT_COUNT?=$(shell git describe --tags | rev | cut -d- -f2 | rev)\n################################################################################\n# ========================= Setup Go With Gimme ================================\n# go version to use for build etc.\n# setup correct go version with gimme\nGOTOOLCHAIN:=$(shell . hack/build/gotoolchain.sh && echo \"$${GOTOOLCHAIN}\")\nPATH:=$(shell . hack/build/setup-go.sh && echo \"$${PATH}\")\n# go1.9+ can autodetect GOROOT, but if some other tool sets it ...\nGOROOT:=\n# enable modules\nGO111MODULE=on\n# disable CGO by default for static binaries\nCGO_ENABLED=0\nexport PATH GOROOT GO111MODULE CGO_ENABLED GOTOOLCHAIN\n# work around broken PATH export\nSPACE:=$(subst ,, )\nSHELL:=env PATH=$(subst $(SPACE),\\$(SPACE),$(PATH)) $(SHELL)\n################################################################################\n# ============================== OPTIONS =======================================\n# install tool\nINSTALL?=install\n# install will place binaries here, by default attempts to mimic go install\nINSTALL_DIR?=$(shell hack/build/goinstalldir.sh)\n# the output binary name, overridden when cross compiling\nKIND_BINARY_NAME?=kind\n# build flags for the kind binary\n# - reproducible builds: -trimpath and -ldflags=-buildid=\n# - smaller binaries: -w (trim debugger data, but not panics)\n# - metadata: -X=... to bake in git commit\nKIND_VERSION_PKG:=sigs.k8s.io/kind/pkg/cmd/kind/version\nKIND_BUILD_LD_FLAGS:=-X=$(KIND_VERSION_PKG).gitCommit=$(COMMIT) -X=$(KIND_VERSION_PKG).gitCommitCount=$(COMMIT_COUNT)\nKIND_BUILD_FLAGS?=-trimpath -ldflags=\"-buildid= -w $(KIND_BUILD_LD_FLAGS)\"\n################################################################################\n# ================================= Building ===================================\n# standard \"make\" target -> builds\nall: build\n# builds kind in a container, outputs to $(OUT_DIR)\nkind:\n\tgo build -v -o \"$(OUT_DIR)/$(KIND_BINARY_NAME)\" $(KIND_BUILD_FLAGS)\n# alias for building kind\nbuild: kind\n# use: make install INSTALL_DIR=/usr/local/bin\ninstall: build\n\t$(INSTALL) -d $(INSTALL_DIR)\n\t$(INSTALL) \"$(OUT_DIR)/$(KIND_BINARY_NAME)\" \"$(INSTALL_DIR)/$(KIND_BINARY_NAME)\"\n################################################################################\n# ================================= Testing ====================================\n# unit tests (hermetic)\nunit:\n\tMODE=unit hack/make-rules/test.sh\n# integration tests\nintegration:\n\tMODE=integration hack/make-rules/test.sh\n# all tests\ntest:\n\thack/make-rules/test.sh\n################################################################################\n# ================================= Cleanup ====================================\n# standard cleanup target\nclean:\n\trm -rf \"$(OUT_DIR)/\"\n################################################################################\n# ============================== Auto-Update ===================================\n# update generated code, gofmt, etc.\nupdate:\n\thack/make-rules/update/all.sh\n# update generated code\ngenerate:\n\thack/make-rules/update/generated.sh\n# gofmt\ngofmt:\n\thack/make-rules/update/gofmt.sh\n################################################################################\n# ================================== Linting ===================================\n# run linters, ensure generated code, etc.\nverify:\n\thack/make-rules/verify/all.sh\n# code linters\nlint:\n\thack/make-rules/verify/lint.sh\n# shell linter\nshellcheck:\n\thack/make-rules/verify/shellcheck.sh\n#################################################################################\n.PHONY: all kind build install unit clean update generate gofmt verify lint shellcheck\n"
  },
  {
    "path": "OWNERS",
    "content": "# See the OWNERS docs at https://go.k8s.io/owners\n\nreviewers:\n  - aojea\n  - BenTheElder\n  - stmcginnis\napprovers:\n  - aojea\n  - BenTheElder\n  - stmcginnis\nemeritus_approvers:\n  - amwat\n  - munnerz\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img alt=\"kind\" src=\"./logo/logo.png\" width=\"300px\" /></p>\n\n# Please see [Our Documentation](https://kind.sigs.k8s.io/docs/user/quick-start/) for more in-depth installation etc.\n\nkind is a tool for running local Kubernetes clusters using Docker container \"nodes\".\nkind was primarily designed for testing Kubernetes itself, but may be used for local development or CI.\n\nIf you have [go] 1.16+ and [docker], [podman] or [nerdctl] installed `go install sigs.k8s.io/kind@v0.31.0 && kind create cluster` is all you need!\n\n![](site/static/images/kind-create-cluster.png)\n\nkind consists of:\n- Go [packages][packages] implementing [cluster creation][cluster package], [image build][build package], etc.\n- A command line interface ([`kind`][kind cli]) built on these packages.\n- Docker [image(s)][images] written to run systemd, Kubernetes, etc.\n- [`kubetest`][kubetest] integration also built on these packages (WIP)\n\nkind bootstraps each \"node\" with [kubeadm][kubeadm]. For more details see [the design documentation][design doc].\n\n**NOTE**: kind is still a work in progress, see the [1.0 roadmap].\n\n## Installation and usage\n\nFor a complete [install guide] see [the documentation here][install guide].\n\nYou can install kind with `go install sigs.k8s.io/kind@v0.31.0`.\n\n**NOTE**: please use the latest go to do this. KIND is developed with the latest stable go, see [`.go-version`](./.go-version) for the exact version we're using.\n\nThis will put `kind` in `$(go env GOPATH)/bin`. If you encounter the error\n`kind: command not found` after installation then you may need to either add that directory to your `$PATH` as\nshown [here](https://golang.org/doc/code.html#GOPATH) or do a manual installation by cloning the repo and run\n`make build` from the repository.\n\nWithout installing go, kind can be built reproducibly with docker using `make build`.\n\nStable binaries are also available on the [releases] page. Stable releases are\ngenerally recommended for CI usage in particular.\nTo install, download the binary for your platform from \"Assets\" and place this\ninto your `$PATH`:\n\nOn Linux:\n\n```console\n# For AMD64 / x86_64\n[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-$(uname)-amd64\n# For ARM64\n[ $(uname -m) = aarch64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-$(uname)-arm64\nchmod +x ./kind\nsudo mv ./kind /usr/local/bin/kind\n```\n\nOn macOS via Homebrew:\n\n```console\nbrew install kind\n```\n\nOn macOS via MacPorts:\n\n```console\nsudo port selfupdate && sudo port install kind\n```\n\nOn macOS via Bash:\n\n```console\n# For Intel Macs\n[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-darwin-amd64\n# For M1 / ARM Macs\n[ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-darwin-arm64\nchmod +x ./kind\nmv ./kind /some-dir-in-your-PATH/kind\n```\n\nOn Windows:\n\n```powershell\ncurl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/v0.31.0/kind-windows-amd64\nMove-Item .\\kind-windows-amd64.exe c:\\some-dir-in-your-PATH\\kind.exe\n\n# OR via Chocolatey (https://chocolatey.org/packages/kind)\nchoco install kind\n```\n\nTo use kind, you will need to [install docker].\nOnce you have docker running you can create a cluster with:\n\n```console\nkind create cluster\n```\n\nTo delete your cluster use:\n\n```console\nkind delete cluster\n```\n\n<!--TODO(bentheelder): improve this part of the guide-->\nTo create a cluster from Kubernetes source:\n- ensure that Kubernetes is cloned in `$(go env GOPATH)/src/k8s.io/kubernetes`\n- build a node image and create a cluster with:\n```console\nkind build node-image\nkind create cluster --image kindest/node:latest\n```\n\nMulti-node clusters and other advanced features may be configured with a config\nfile, for more usage see [the docs][user guide] or run `kind [command] --help`\n\n## Community\n\nPlease reach out for bugs, feature requests, and other issues!\nThe maintainers of this project are reachable via:\n\n- [Kubernetes Slack] in the [#kind] channel\n- [filing an issue] against this repo\n- The Kubernetes [SIG-Testing Mailing List]\n\nCurrent maintainers are [@aojea], [@BenTheElder], and [@stmcginnis] - feel free to\nreach out if you have any questions!\n\nPull Requests are very welcome!\nIf you're planning a new feature, please file an issue to discuss first.\n\nCheck the [issue tracker] for `help wanted` issues if you're unsure where to\nstart, or feel free to reach out to discuss. 🙂\n\nSee also: our own [contributor guide] and the Kubernetes [community page].\n\n## Why kind?\n\n- kind supports multi-node (including HA) clusters\n- kind supports building Kubernetes release builds from source\n  - support for make / bash or docker, in addition to pre-published builds\n- kind supports Linux, macOS and Windows\n- kind is a [CNCF certified conformant Kubernetes installer](https://landscape.cncf.io/?selected=kind)\n\n### Code of conduct\n\nParticipation in the Kubernetes community is governed by the [Kubernetes Code of Conduct].\n\n<!--links-->\n[go]: https://golang.org/\n[go-supported]: https://golang.org/doc/devel/release.html#policy\n[docker]: https://www.docker.com/\n[podman]: https://podman.io/\n[nerdctl]: https://github.com/containerd/nerdctl\n[community page]: https://kubernetes.io/community/\n[Kubernetes Code of Conduct]: code-of-conduct.md\n[Go Report Card Badge]: https://goreportcard.com/badge/sigs.k8s.io/kind\n[Go Report Card]: https://goreportcard.com/report/sigs.k8s.io/kind\n[conformance tests]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md\n[packages]: ./pkg\n[cluster package]: ./pkg/cluster\n[build package]: ./pkg/build\n[kind cli]: ./main.go\n[images]: ./images\n[kubetest]: https://github.com/kubernetes/test-infra/tree/master/kubetest\n[kubeadm]: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/\n[design doc]: https://kind.sigs.k8s.io/docs/design/initial\n[user guide]: https://kind.sigs.k8s.io/docs/user/quick-start\n[SIG-Testing Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing\n[issue tracker]: https://github.com/kubernetes-sigs/kind/issues\n[filing an issue]: https://github.com/kubernetes-sigs/kind/issues/new\n[Kubernetes Slack]: http://slack.k8s.io/\n[#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/\n[1.0 roadmap]: https://kind.sigs.k8s.io/docs/contributing/1.0-roadmap\n[install docker]: https://docs.docker.com/install/\n[@BenTheElder]: https://github.com/BenTheElder\n[@munnerz]: https://github.com/munnerz\n[@aojea]: https://github.com/aojea\n[@amwat]: https://github.com/amwat\n[@stmcginnis]: https://github.com/stmcginnis\n[contributor guide]: https://kind.sigs.k8s.io/docs/contributing/getting-started\n[releases]: https://github.com/kubernetes-sigs/kind/releases\n[install guide]: https://kind.sigs.k8s.io/docs/user/quick-start/#installation\n[modules]: https://github.com/golang/go/wiki/Modules\n"
  },
  {
    "path": "SECURITY_CONTACTS",
    "content": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Team to reach out\n# to for triaging and handling of incoming issues.\n#\n# The below names agree to abide by the\n# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)\n# and will be removed and replaced if they violate that agreement.\n#\n# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE\n# INSTRUCTIONS AT https://kubernetes.io/security/\n\nBenTheElder\nmunnerz\n"
  },
  {
    "path": "cmd/kind/app/main.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package app is the implementation of the kind application.\npackage app\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// Main is the kind main(), it will invoke Run(), if an error is returned\n// it will then call os.Exit\nfunc Main() {\n\tif err := Run(cmd.NewLogger(), cmd.StandardIOStreams(), os.Args[1:]); err != nil {\n\t\tos.Exit(1)\n\t}\n}\n\n// Run invokes the kind root command, returning the error.\n// See: sigs.k8s.io/kind/pkg/cmd/kind\nfunc Run(logger log.Logger, streams cmd.IOStreams, args []string) error {\n\t// NOTE: we handle the quiet flag here so we can fully silence cobra\n\tif checkQuiet(args) {\n\t\t// if we are in quiet mode, we want to suppress all status output\n\t\t// only streams.Out should be written to (program output)\n\t\tlogger = log.NoopLogger{}\n\t\tstreams.ErrOut = io.Discard\n\t}\n\t// actually run the command\n\tc := kind.NewCommand(logger, streams)\n\tc.SetArgs(args)\n\tif err := c.Execute(); err != nil {\n\t\tlogError(logger, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// checkQuiet returns true if -q / --quiet was set in args\nfunc checkQuiet(args []string) bool {\n\tflags := pflag.NewFlagSet(\"persistent-quiet\", pflag.ContinueOnError)\n\tflags.ParseErrorsWhitelist.UnknownFlags = true\n\tquiet := false\n\tflags.BoolVarP(\n\t\t&quiet,\n\t\t\"quiet\",\n\t\t\"q\",\n\t\tfalse,\n\t\t\"silence all stderr output\",\n\t)\n\t// NOTE: pflag will error if -h / --help is specified\n\t// We don't care here. That will be handled downstream\n\t// It will also call flags.Usage so we're making that no-op\n\tflags.Usage = func() {}\n\t_ = flags.Parse(args)\n\treturn quiet\n}\n\n// logError logs the error and the root stacktrace if there is one\nfunc logError(logger log.Logger, err error) {\n\tcolorEnabled := cmd.ColorEnabled(logger)\n\tif colorEnabled {\n\t\tlogger.Errorf(\"\\x1b[31mERROR\\x1b[0m: %v\", err)\n\t} else {\n\t\tlogger.Errorf(\"ERROR: %v\", err)\n\t}\n\t// Display Output if the error was from running a command ...\n\tif err := exec.RunErrorForError(err); err != nil {\n\t\tif colorEnabled {\n\t\t\tlogger.Errorf(\"\\x1b[31mCommand Output\\x1b[0m: %s\", err.Output)\n\t\t} else {\n\t\t\tlogger.Errorf(\"\\nCommand Output: %s\", err.Output)\n\t\t}\n\t}\n\t// TODO: stacktrace should probably be guarded by a higher level ...?\n\tif logger.V(1).Enabled() {\n\t\t// Then display stack trace if any (there should be one...)\n\t\tif trace := errors.StackTrace(err); trace != nil {\n\t\t\tif colorEnabled {\n\t\t\t\tlogger.Errorf(\"\\x1b[31mStack Trace\\x1b[0m: %+v\", trace)\n\t\t\t} else {\n\t\t\t\tlogger.Errorf(\"\\nStack Trace: %+v\", trace)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/kind/app/main_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage app\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n)\n\nfunc TestCheckQuiet(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tName        string\n\t\tArgs        []string\n\t\tExpectQuiet bool\n\t}{\n\t\t// normal cases, we expect it to be set\n\t\t{\n\t\t\tName:        \"simply q\",\n\t\t\tArgs:        []string{\"-q\"},\n\t\t\tExpectQuiet: true,\n\t\t},\n\t\t{\n\t\t\tName:        \"simply quiet\",\n\t\t\tArgs:        []string{\"--quiet\"},\n\t\t\tExpectQuiet: true,\n\t\t},\n\t\t{\n\t\t\tName:        \"all quiet on the cli\",\n\t\t\tArgs:        []string{\"all\", \"quiet\", \"on\", \"the\", \"cli\", \"--quiet\"},\n\t\t\tExpectQuiet: true,\n\t\t},\n\t\t// pflag will throw an ErrHelp when -h / --help are in args even though\n\t\t// we don't register these as flags, checkQuiet should ignore them\n\t\t{\n\t\t\tName:        \"with ignored help\",\n\t\t\tArgs:        []string{\"--quiet\", \"--help\"},\n\t\t\tExpectQuiet: true,\n\t\t},\n\t\t{\n\t\t\tName:        \"with ignored h\",\n\t\t\tArgs:        []string{\"--quiet\", \"-h\"},\n\t\t\tExpectQuiet: true,\n\t\t},\n\t\t// not quiet for these cases ...\n\t\t{\n\t\t\tName:        \"no args\",\n\t\t\tArgs:        []string{},\n\t\t\tExpectQuiet: false,\n\t\t},\n\t\t{\n\t\t\tName:        \"loud\",\n\t\t\tArgs:        []string{\"--loud\"},\n\t\t\tExpectQuiet: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tresult := checkQuiet(tc.Args)\n\t\t\tif result != tc.ExpectQuiet {\n\t\t\t\tt.Fatalf(\"fooo\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_CommandErrReturn(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tName       string\n\t\tCommand    string\n\t\tSubcommand string\n\t}{\n\t\t{\n\t\t\tName:       \"misspelled subcommand for build\",\n\t\t\tCommand:    \"build\",\n\t\t\tSubcommand: \"nod-image\",\n\t\t},\n\t\t{\n\t\t\tName:       \"misspelled subcommand for completion\",\n\t\t\tCommand:    \"completion\",\n\t\t\tSubcommand: \"zzsh\",\n\t\t},\n\t\t{\n\t\t\tName:       \"misspelled subcommand for create\",\n\t\t\tCommand:    \"create\",\n\t\t\tSubcommand: \"clunster\",\n\t\t},\n\t\t{\n\t\t\tName:       \"misspelled subcommand for delete\",\n\t\t\tCommand:    \"delete\",\n\t\t\tSubcommand: \"clust\",\n\t\t},\n\t\t{\n\t\t\tName:       \"misspelled subcommand for export\",\n\t\t\tCommand:    \"export\",\n\t\t\tSubcommand: \"kubecfg\",\n\t\t},\n\t\t{\n\t\t\tName:       \"misspelled subcommand for get\",\n\t\t\tCommand:    \"get\",\n\t\t\tSubcommand: \"nods\",\n\t\t},\n\t\t{\n\t\t\tName:       \"misspelled subcommand for load\",\n\t\t\tCommand:    \"load\",\n\t\t\tSubcommand: \"dokker-image\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\terr := Run(cmd.NewLogger(), cmd.StandardIOStreams(), []string{tc.Command, tc.Subcommand})\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Subcommand should raise an error if not called with correct params\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/kind/main.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package main is the main entrypoint for the kind cli.\npackage main\n\nimport (\n\t\"sigs.k8s.io/kind/cmd/kind/app\"\n)\n\nfunc main() {\n\tapp.Main()\n}\n"
  },
  {
    "path": "code-of-conduct.md",
    "content": "# Kubernetes Community Code of Conduct\n\nPlease refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)\n"
  },
  {
    "path": "go.mod",
    "content": "module sigs.k8s.io/kind\n\n// NOTE: This is the go language version, NOT the compiler version.\n//\n// This controls the *minimum* required go version and therefore available Go\n// language features.\n//\n// See ./.go-version for the go compiler version used when building binaries\n//\n// https://go.dev/doc/modules/gomod-ref#go\ngo 1.17\n\nrequire (\n\tal.essio.dev/pkg/shellescape v1.5.1\n\tgithub.com/BurntSushi/toml v1.4.0\n\tgithub.com/evanphx/json-patch/v5 v5.6.0\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/pelletier/go-toml v1.9.5\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/spf13/cobra v1.8.0\n\tgithub.com/spf13/pflag v1.0.5\n\tgo.yaml.in/yaml/v3 v3.0.4\n\tsigs.k8s.io/yaml v1.4.0\n)\n\n// test-only transitive deps, these are used by sigs.k8s.io/yaml's tests\nrequire (\n\tgithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect\n\tgopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect\n)\n\nrequire (\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgolang.org/x/sys v0.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=\nal.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=\ngithub.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=\ngithub.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=\ngithub.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\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-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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=\ngithub.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=\ngopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "hack/build/README.md",
    "content": "This directory contains tooling used for building"
  },
  {
    "path": "hack/build/goinstalldir.sh",
    "content": "#!/bin/sh\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 utility prints out the golang install dir, even if go is not installed\n# IE it prints the directory where `go install ...` would theoretically place\n# binaries\n\n# if we have go, just ask go!\nif which go >/dev/null 2>&1; then\n  DIR=$(go env GOBIN)\n  if [ -n \"${DIR}\" ]; then\n    echo \"${DIR}\"\n    exit 0\n  fi\n  DIR=$(go env GOPATH)\n  if [ -n \"${DIR}\" ]; then\n    echo \"${DIR}/bin\"\n    exit 0\n  fi\nfi\n\n# mimic go behavior\n\n# check if GOBIN is set anyhow\nif [ -n \"${GOBIN}\" ]; then\n  echo \"${GOBIN}\"\n  exit 0\nfi\n\n# check if GOPATH is set anyhow\nif [ -n \"${GOPATH}\" ]; then\n  echo \"${GOPATH}/bin\"\n  exit 0\nfi\n\n# finally use default for no $GOPATH or $GOBIN\necho \"${HOME}/go/bin\"\n"
  },
  {
    "path": "hack/build/gotoolchain.sh",
    "content": "#!/bin/bash\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# script to set GOTOOLCHAIN as needed\n# MUST BE RUN FROM THE REPO ROOT DIRECTORY\n\n# read go-version file unless GO_VERSION is set\nGO_VERSION=\"${GO_VERSION:-\"$(cat .go-version)\"}\"\n\nGOTOOLCHAIN=\"go${GO_VERSION}\"\nexport GOTOOLCHAIN GO_VERSION\n"
  },
  {
    "path": "hack/build/init-buildx.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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 nounset -o pipefail\n\n# TODO: newer buildx releases ship their own qemu copies and don't need any of this\n# We can skip setup if the current builder already has multi-arch\n# AND if it isn't the docker driver, which doesn't work\ncurrent_builder=\"$(docker buildx inspect)\"\n# linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6\nif ! grep -q \"^Driver: docker$\" <<<\"${current_builder}\" && \\\n     grep -q \"linux/amd64\" <<<\"${current_builder}\" && \\\n     grep -q \"linux/arm64\" <<<\"${current_builder}\"; then\n  exit 0\nfi\n\n# Ensure qemu is in binfmt_misc\n# Docker desktop already has these in versions recent enough to have buildx\n# We only need to do this setup on linux hosts\nif [ \"$(uname)\" == 'Linux' ]; then\n  # NOTE: this is pinned to a digest for a reason!\n  docker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0-28@sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55 --install all\nfi\n\n# Ensure we use a builder that can leverage it (the default on linux will not)\ndocker buildx rm kind-builder || true\ndocker buildx create --use --name=kind-builder\n"
  },
  {
    "path": "hack/build/setup-go.sh",
    "content": "#!/bin/bash\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# script to setup go version with gimme as needed\n# MUST BE RUN FROM THE REPO ROOT DIRECTORY\n\n# read go-version file unless GO_VERSION is set\n# override GOTOOLCHAIN unless set as well\n. ./hack/build/gotoolchain.sh\n\n# we don't actually care where the .env files are\n# however, GIMME_SILENT_ENV doesn't trigger re-generating a .env if it\n# already exists and isn't \"silent\" (no `go version` command in it)\n# so we fix that by changing where the .env is written, ensuring ours\n# is generated from this repo and silent.\nexport GIMME_ENV_PREFIX=./bin/.gimme/\nexport GIMME_SILENT_ENV=y\n\n# only setup go if we haven't set FORCE_HOST_GO, or `go version` doesn't match\nif [ -n \"${FORCE_HOST_GO:-}\" ]; then\n  GOTOOLCHAIN=\"${GOTOOLCHAIN:-local}\"\n  export GOTOOLCHAIN\nelse\n  GOTOOLCHAIN=\"go${GO_VERSION}\"\n  export GOTOOLCHAIN\n  # go version output looks like:\n  # go version go1.14.5 darwin/amd64\n  if ! (command -v go >/dev/null && [ \"$(go version | cut -d' ' -f3)\" = \"go${GO_VERSION}\" ]); then\n    # eval because the output of this is shell to set PATH etc.\n    eval \"$(hack/third_party/gimme/gimme \"${GO_VERSION}\")\"\n  fi\nfi\n\n# force go modules\nexport GO111MODULE=on\n"
  },
  {
    "path": "hack/ci/README.md",
    "content": "This directory contains glue used to test the kind repo in CI.\n\nWe don't recommend reusing anything from these scripts, and intend to replace\nthem at some point."
  },
  {
    "path": "hack/ci/build-all.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# simple CI script to verify kind's own sources\n# TODO(bentheelder): rename / refactor. consider building kindnetd\n\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root\nREPO_ROOT=$(git rev-parse --show-toplevel)\ncd \"${REPO_ROOT}\"\n\n# build kind\nhack/release/build/cross.sh\n"
  },
  {
    "path": "hack/ci/cache-wrapper.sh",
    "content": "#!/bin/bash\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# USAGE: cache-wrapper.sh hack/go_container.sh some-go-command\n# $@ will be executed with this script wrapping cache upload / download\n\nset -o errexit -o nounset -o pipefail\n\n# options for where the cache is stored\nBUCKET=\"${BUCKET:-bentheelder-kind-ci-builds}\"\nBRANCH=\"${BRANCH:-main}\"\nCACHE_SUFFIX=\"${CACHE_SUFFIX:-\"ci-cache/${BRANCH}/gocache.tar\"}\"\nCACHE_URL=\"https://storage.googleapis.com/${BUCKET}/${CACHE_SUFFIX}\"\nCACHE_GS=\"gs://${BUCKET}/${CACHE_SUFFIX}\"\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\n# default to downloading cache\nif [ \"${DOWNLOAD_CACHE:-true}\" = \"true\" ]; then\n  # NOTE:\n  # - We clean the modcache because we won't be able to write to it if it exists\n  # https://github.com/golang/go/issues/31481\n  # - All of the relevant go system directories are under /go in KIND's build\n  # - See below for how the cache tarball is created\n  hack/go_container.sh sh -c \"go clean -modcache && curl -sSL ${CACHE_URL} | tar -C /go -zxf - --overwrite\"\nfi\n\n# run the supplied command and store the exit code for later\nset +o errexit\n\"$@\"\nres=$?\nset -o errexit\n\n# default to not uploading cache\nif [ \"${UPLOAD_CACHE:-false}\" = \"true\" ]; then\n  # We want to cache:\n  # - XDG_CACHE_HOME / the go build cache, this is /go/cache in KIND's build\n  # - The module cache, ~= $GOPATH/pkg/mod. this /go/pkg/mod in KIND's build\n  hack/go_container.sh sh -c 'tar -C /go -czf - ./cache ./pkg/mod' | gsutil cp - \"${CACHE_GS}\"\nfi\n\n# preserve the exit code from our real task\nexit $res\n"
  },
  {
    "path": "hack/ci/e2e-k8s.sh",
    "content": "#!/bin/sh\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# hack script for running a kind e2e\n# must be run with a kubernetes checkout in $PWD (IE from the checkout)\n# Usage: SKIP=\"ginkgo skip regex\" FOCUS=\"ginkgo focus regex\" kind-e2e.sh\n\nset -o errexit -o nounset -o xtrace\n\n# Settings:\n# SKIP: ginkgo skip regex\n# FOCUS: ginkgo focus regex (defaults to [Conformance] if unset; set to \"\" to run all tests)\n# LABEL_FILTER: ginkgo label query for selecting tests (see \"Spec Labels\" in https://onsi.github.io/ginkgo/#filtering-specs)\n#\n# The default is to focus on conformance tests when FOCUS is unset. To run all\n# tests (no focus filter), explicitly set FOCUS=\"\". Serial tests get skipped when\n# parallel testing is enabled. Using LABEL_FILTER instead of combining SKIP and\n# FOCUS is recommended (more expressive, easier to read than regexp).\n#\n# FEATURE_GATES:\n#          JSON or YAML encoding of a string/bool map: {\"FeatureGateA\": true, \"FeatureGateB\": false}\n#          Enables or disables feature gates in the entire cluster.\n#          Cannot be used when GA_ONLY=true.\n# RUNTIME_CONFIG:\n#          JSON or YAML encoding of a string/string (!) map: {\"apia.example.com/v1alpha1\": \"true\", \"apib.example.com/v1beta1\": \"false\"}\n#          Enables API groups in the apiserver via --runtime-config.\n#          Cannot be used when GA_ONLY=true.\n\n# cleanup logic for cleanup on exit\nCLEANED_UP=false\ncleanup() {\n  if [ \"$CLEANED_UP\" = \"true\" ]; then\n    return\n  fi\n  # KIND_CREATE_ATTEMPTED is true once we: kind create\n  if [ \"${KIND_CREATE_ATTEMPTED:-}\" = true ]; then\n    kind \"export\" logs \"${ARTIFACTS}\" || true\n    kind delete cluster || true\n  fi\n  rm -f _output/bin/e2e.test || true\n  # remove our tempdir, this needs to be last, or it will prevent kind delete\n  if [ -n \"${TMP_DIR:-}\" ]; then\n    rm -rf \"${TMP_DIR:?}\"\n  fi\n  CLEANED_UP=true\n}\n\n# setup signal handlers\n# shellcheck disable=SC2317 # this is not unreachable code\nsignal_handler() {\n  if [ -n \"${GINKGO_PID:-}\" ]; then\n    kill -TERM \"$GINKGO_PID\" || true\n  fi\n  cleanup\n}\ntrap signal_handler INT TERM\n\n# build kubernetes / node image, e2e binaries\nbuild() {\n  # build the node image w/ kubernetes\n  kind build node-image -v 1\n  # Ginkgo v1 is used by Kubernetes 1.24 and earlier, fallback if v2 is not available.\n  GINKGO_SRC_DIR=\"vendor/github.com/onsi/ginkgo/v2/ginkgo\"\n  if [ ! -d \"$GINKGO_SRC_DIR\" ]; then\n      GINKGO_SRC_DIR=\"vendor/github.com/onsi/ginkgo/ginkgo\"\n  fi\n  # make sure we have e2e requirements\n  make all WHAT=\"cmd/kubectl test/e2e/e2e.test ${GINKGO_SRC_DIR}\"\n\n  # Ensure the built kubectl is used instead of system\n  export PATH=\"${PWD}/_output/bin:$PATH\"\n}\n\n# up a cluster with kind\ncreate_cluster() {\n  # Default Log level for all components in test clusters\n  KIND_CLUSTER_LOG_LEVEL=${KIND_CLUSTER_LOG_LEVEL:-4}\n\n  # JSON or YAML map injected into featureGates config\n  feature_gates=\"${FEATURE_GATES:-{\\}}\"\n  # --runtime-config argument value passed to the API server, again as a map\n  runtime_config=\"${RUNTIME_CONFIG:-{\\}}\"\n\n  # create the config file\n  cat <<EOF > \"${ARTIFACTS}/kind-config.yaml\"\n# config for 1 control plane node and 2 workers (necessary for conformance)\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ${IP_FAMILY:-ipv4}\n  kubeProxyMode: ${KUBE_PROXY_MODE:-iptables}\n  # don't pass through host search paths\n  # TODO: possibly a reasonable default in the future for kind ...\n  dnsSearch: []\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\nfeatureGates: ${feature_gates}\nruntimeConfig: ${runtime_config}\nkubeadmConfigPatches:\n# v1beta4 for the future (v1.35.0+ ?)\n# https://github.com/kubernetes-sigs/kind/issues/3847\n# TODO: drop v1beta3 when we no longer need versions that use it\n- |\n  kind: ClusterConfiguration\n  apiVersion: kubeadm.k8s.io/v1beta4\n  apiServer:\n    extraArgs:\n      - name: \"v\"\n        value: \"${KIND_CLUSTER_LOG_LEVEL}\"\n  controllerManager:\n    extraArgs:\n      - name: \"v\"\n        value: \"${KIND_CLUSTER_LOG_LEVEL}\"\n  scheduler:\n    extraArgs:\n      - name: \"v\"\n        value: \"${KIND_CLUSTER_LOG_LEVEL}\"\n  ---\n  kind: InitConfiguration\n  apiVersion: kubeadm.k8s.io/v1beta4\n  nodeRegistration:\n    kubeletExtraArgs:\n      - name: \"v\"\n        value: \"${KIND_CLUSTER_LOG_LEVEL}\"\n      - name: \"container-log-max-files\"\n        value: \"10\"\n      - name: \"container-log-max-size\"\n        value: \"100Mi\"\n  ---\n  kind: JoinConfiguration\n  apiVersion: kubeadm.k8s.io/v1beta4\n  nodeRegistration:\n    kubeletExtraArgs:\n      - name: \"v\"\n        value: \"${KIND_CLUSTER_LOG_LEVEL}\"\n      # Warning: these flags appear to be load bearing / impact performance\n      # See: https://github.com/kubernetes-sigs/kind/pull/4046\n      # Be careful when updating these.\n      # Most CI jobs should not need them, but some CI jobs might.\n      - name: \"container-log-max-files\"\n        value: \"10\"\n      - name: \"container-log-max-size\"\n        value: \"100Mi\"\n# v1beta3 for v1.23.0 ... ?\n- |\n  kind: ClusterConfiguration\n  apiVersion: kubeadm.k8s.io/v1beta3\n  apiServer:\n    extraArgs:\n      \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"\n  controllerManager:\n    extraArgs:\n      \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"\n  scheduler:\n    extraArgs:\n      \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"\n  ---\n  kind: InitConfiguration\n  apiVersion: kubeadm.k8s.io/v1beta3\n  nodeRegistration:\n    kubeletExtraArgs:\n      \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"\n      \"container-log-max-files\": \"10\"\n      \"container-log-max-size\": \"100Mi\"\n  ---\n  kind: JoinConfiguration\n  apiVersion: kubeadm.k8s.io/v1beta3\n  nodeRegistration:\n    kubeletExtraArgs:\n      \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"\n      # Warning: these flags appear to be load bearing / impact performance\n      # See: https://github.com/kubernetes-sigs/kind/pull/4046\n      # Be careful when updating these.\n      # Most CI jobs should not need them, but some CI jobs might.\n      \"container-log-max-files\": \"10\"\n      \"container-log-max-size\": \"100Mi\"\nEOF\n  # NOTE: must match the number of workers above\n  NUM_NODES=2\n  # actually create the cluster\n  # TODO(BenTheElder): settle on verbosity for this script\n  KIND_CREATE_ATTEMPTED=true\n  kind create cluster \\\n    --image=kindest/node:latest \\\n    --retain \\\n    --wait=1m \\\n    -v=3 \\\n    \"--config=${ARTIFACTS}/kind-config.yaml\"\n\n  # debug cluster version\n  kubectl version\n\n  # Patch kube-proxy to set the verbosity level\n  kubectl patch -n kube-system daemonset/kube-proxy \\\n    --type='json' -p='[{\"op\": \"add\", \"path\": \"/spec/template/spec/containers/0/command/-\", \"value\": \"--v='\"${KIND_CLUSTER_LOG_LEVEL}\"'\" }]'\n}\n\n# run e2es with ginkgo-e2e.sh\nrun_tests() {\n  # IPv6 clusters need some CoreDNS changes in order to work in k8s CI:\n  # 1. k8s CI doesn´t offer IPv6 connectivity, so CoreDNS should be configured\n  # to work in an offline environment:\n  # https://github.com/coredns/coredns/issues/2494#issuecomment-457215452\n  # 2. k8s CI adds following domains to resolv.conf search field:\n  # c.k8s-prow-builds.internal google.internal.\n  # CoreDNS should handle those domains and answer with NXDOMAIN instead of SERVFAIL\n  # otherwise pods stops trying to resolve the domain.\n  if [ \"${IP_FAMILY:-ipv4}\" = \"ipv6\" ]; then\n    # Get the current config\n    original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns)\n    echo \"Original CoreDNS config:\"\n    echo \"${original_coredns}\"\n    # Patch it\n    fixed_coredns=$(\n      printf '%s' \"${original_coredns}\" | sed \\\n        -e 's/^.*kubernetes cluster\\.local/& internal/' \\\n        -e '/^.*upstream$/d' \\\n        -e '/^.*fallthrough.*$/d' \\\n        -e '/^.*forward . \\/etc\\/resolv.conf$/d' \\\n        -e '/^.*loop$/d' \\\n    )\n    echo \"Patched CoreDNS config:\"\n    echo \"${fixed_coredns}\"\n    printf '%s' \"${fixed_coredns}\" | kubectl apply -f -\n  fi\n\n  # ginkgo regexes and label filter\n  SKIP=\"${SKIP:-}\"\n  LABEL_FILTER=\"${LABEL_FILTER:-}\"\n  # Only default to Conformance if FOCUS was not explicitly set.\n  # This allows FOCUS=\"\" to run all tests (no focus filter).\n  if [ \"${FOCUS+set}\" != \"set\" ]; then\n    if [ -z \"${LABEL_FILTER}\" ]; then\n      FOCUS=\"\\\\[Conformance\\\\]\"\n    else\n      FOCUS=\"\"\n    fi\n  fi\n  # if we set PARALLEL=true, skip serial tests set --ginkgo-parallel\n  if [ \"${PARALLEL:-false}\" = \"true\" ]; then\n    export GINKGO_PARALLEL=y\n    if [ -z \"${SKIP}\" ]; then\n      SKIP=\"\\\\[Serial\\\\]\"\n    else\n      SKIP=\"\\\\[Serial\\\\]|${SKIP}\"\n    fi\n  fi\n\n  # setting this env prevents ginkgo e2e from trying to run provider setup\n  export KUBERNETES_CONFORMANCE_TEST='y'\n  # setting these is required to make RuntimeClass tests work ... :/\n  export KUBE_CONTAINER_RUNTIME=remote\n  export KUBE_CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock\n  export KUBE_CONTAINER_RUNTIME_NAME=containerd\n  # ginkgo can take forever to exit, so we run it in the background and save the\n  # PID, bash will not run traps while waiting on a process, but it will while\n  # running a builtin like `wait`, saving the PID also allows us to forward the\n  # interrupt\n  ./hack/ginkgo-e2e.sh \\\n    '--provider=skeleton' \"--num-nodes=${NUM_NODES}\" \\\n    \"--ginkgo.focus=${FOCUS}\" \"--ginkgo.skip=${SKIP}\" \"--ginkgo.label-filter=${LABEL_FILTER}\" \\\n    \"--report-dir=${ARTIFACTS}\" '--disable-log-dump=true' &\n  GINKGO_PID=$!\n  wait \"$GINKGO_PID\"\n}\n\nmain() {\n  # create temp dir and setup cleanup\n  TMP_DIR=$(mktemp -d)\n\n  # ensure artifacts (results) directory exists when not in CI\n  export ARTIFACTS=\"${ARTIFACTS:-${PWD}/_artifacts}\"\n  mkdir -p \"${ARTIFACTS}\"\n\n  # export the KUBECONFIG to a unique path for testing\n  KUBECONFIG=\"${HOME}/.kube/kind-test-config\"\n  export KUBECONFIG\n  echo \"exported KUBECONFIG=${KUBECONFIG}\"\n\n  # debug kind version\n  kind version\n\n  # build kubernetes\n  build\n  # in CI attempt to release some memory after building\n  if [ -n \"${KUBETEST_IN_DOCKER:-}\" ]; then\n    sync || true\n    echo 1 > /proc/sys/vm/drop_caches || true\n  fi\n\n  # create the cluster and run tests\n  res=0\n  create_cluster || res=$?\n  run_tests || res=$?\n  cleanup || res=$?\n  exit $res\n}\n\nmain\n"
  },
  {
    "path": "hack/ci/e2e.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# hack script for running a kind e2e\n# must be run with a kubernetes checkout in $PWD (IE from the checkout)\n# Usage: SKIP=\"ginkgo skip regex\" FOCUS=\"ginkgo focus regex\" kind-e2e.sh\n\nset -o errexit -o nounset -o pipefail -o xtrace\n\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd -P)\"\n\n# our exit handler (trap)\ncleanup() {\n  # remove our tempdir, this needs to be last, or it will prevent kind delete\n  [[ -n \"${TMP_DIR:-}\" ]] && rm -rf \"${TMP_DIR:?}\"\n}\n\n# install kind to a tempdir GOPATH from this script's kind checkout\ninstall_kind() {\n  mkdir -p \"${TMP_DIR}/bin\"\n  make -C \"${REPO_ROOT}\" install INSTALL_PATH=\"${TMP_DIR}/bin\"\n  export PATH=\"${TMP_DIR}/bin:${PATH}\"\n}\n\nmain() {\n  # create temp dir and setup cleanup\n  TMP_DIR=$(mktemp -d)\n  trap cleanup INT TERM EXIT\n\n  # install kind\n  install_kind\n\n  # build kubernetes / e2e test\n  \"${REPO_ROOT}/hack/ci/e2e-k8s.sh\"\n}\n\nmain\n"
  },
  {
    "path": "hack/ci/init-vm.sh",
    "content": "#!/bin/bash\nset -eux -o pipefail\n# Ensure network-related modules to be loaded\nmodprobe tap ip_tables iptable_nat ip6_tables ip6table_nat\n\n# The moby-engine package included in Fedora lacks support for rootless,\n# So we need to install docker-ce and docker-ce-rootless-extras from the upstream.\nDNF_REPO=\"\"\nINSTALL_PODMAN=\"1\"\nif grep -q centos /etc/os-release; then\n\t# Works with Rocky and Alma too\n\tDNF_REPO=\"https://download.docker.com/linux/centos/docker-ce.repo\"\n\tif grep -q el8 /etc/os-release; then\n\t\t# podman seems to conflict with docker-ce on EL8\n\t\tINSTALL_PODMAN=\"\"\n\tfi\nelif grep -q fedora /etc/os-release; then\n\tDNF_REPO=\"https://download.docker.com/linux/fedora/docker-ce.repo\"\nelse\n\techo >&2 \"Unsupported OS\"\n\texit 1\nfi\nDNF=\"dnf\"\nif command -v dnf5 &>/dev/null; then\n\t# DNF 5 (Fedora 41 or later)\n\tDNF=\"dnf5\"\n\t\"$DNF\" config-manager addrepo --from-repofile=\"${DNF_REPO}\"\nelse\n\t# DNF 4\n\t\"$DNF\" config-manager --add-repo=\"${DNF_REPO}\"\nfi\n\"$DNF\" install -y git golang make docker-ce docker-ce-rootless-extras\nsystemctl enable --now docker\nif [ -n \"${INSTALL_PODMAN}\" ]; then\n\t\"$DNF\" install -y podman\nfi\n\n# Install kubectl\nGOARCH=\"$(uname -m | sed -e 's/aarch64/arm64/' -e 's/x86_64/amd64/')\"\ncurl -L -o /usr/bin/kubectl \"https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${GOARCH}/kubectl\"\nchmod +x /usr/bin/kubectl\n\n# Configuration for rootless: https://kind.sigs.k8s.io/docs/user/rootless/\nmkdir -p \"/etc/systemd/system/user@.service.d\"\ncat <<EOF >\"/etc/systemd/system/user@.service.d/delegate.conf\"\n[Service]\nDelegate=yes\nEOF\nsystemctl daemon-reload\n"
  },
  {
    "path": "hack/ci/lima-helper.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2021 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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 nounset -o pipefail\n\n: \"${KIND_EXPERIMENTAL_PROVIDER:=docker}\"\n\nsudo=sudo\n[ \"$ROOTLESS\" = \"rootless\" ] && sudo=\nexec lima $sudo KIND_EXPERIMENTAL_PROVIDER=\"$KIND_EXPERIMENTAL_PROVIDER\" \"${@}\"\n"
  },
  {
    "path": "hack/ci/push-latest-cli/README.md",
    "content": "This tooling is used for our automated builds.\n\nThese are meant to be consumed *only* by The Kubernetes Project's CI for \ntesting Kubernetes.\n\nThese builds are currently not supported for other purposes.\n\nSee github.com/kubernetes/test-infra for the automation that invokes these."
  },
  {
    "path": "hack/ci/push-latest-cli/cloudbuild.yaml",
    "content": "# See https://cloud.google.com/cloud-build/docs/build-config\noptions:\n  substitution_option: ALLOW_LOOSE\nsteps:\n- name: gcr.io/k8s-staging-test-infra/krte:latest-master\n  env:\n  - PULL_BASE_SHA=$_PULL_BASE_SHA\n  entrypoint: hack/ci/push-latest-cli/push-latest-cli.sh\nsubstitutions:\n  _GIT_TAG: '12345'\n  _PULL_BASE_SHA: 'oops'\n"
  },
  {
    "path": "hack/ci/push-latest-cli/push-latest-cli.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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 nounset -o pipefail\nset -x;\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\n# pass through git details from prow / image builder\nif [ -n \"${PULL_BASE_SHA:-}\" ]; then\n  export COMMIT=\"${PULL_BASE_SHA:?}\"\nelse\n  COMMIT=\"$(git rev-parse HEAD 2>/dev/null)\"\n  export COMMIT\nfi\n# short commit is currently 8 characters\nSHORT_COMMIT=\"${COMMIT:0:8}\"\n\n# we upload here\nBUCKET=\"${BUCKET:-k8s-staging-kind}\"\n# under each of these\nVERSIONS=(\n  latest\n  \"${SHORT_COMMIT}\"\n)\n\n# build for all platforms\nhack/release/build/cross.sh\n\n# upload to the bucket\nfor f in bin/kind-*; do\n  # make a tarball with this\n  # TODO: eliminate e2e-k8s.sh\n  base=\"$(basename \"$f\")\"\n  platform=\"${base#kind-}\"\n  tar \\\n    -czvf \"bin/${platform}.tgz\" \\\n    --transform 's#.*kind.*#kind#' \\\n    --transform 's#.*e2e-k8s.sh#e2e-k8s.sh#' \\\n    --transform='s#^/#./#' \\\n    --mode='755' \\\n    \"${f}\" \\\n    \"hack/ci/e2e-k8s.sh\"\n  \n  # copy everything up to each version\n  for version in \"${VERSIONS[@]}\"; do\n    gsutil cp -P \"bin/${platform}.tgz\" \"gs://${BUCKET}/${version}/${platform}.tgz\"\n    gsutil cp -P \"$f\" \"gs://${BUCKET}/${version}/${base}\"\n  done\ndone\n\n# upload the e2e script so kubernetes CI can consume it\nfor version in \"${VERSIONS[@]}\"; do\n  gsutil cp -P hack/ci/e2e-k8s.sh \"gs://${BUCKET}/${version}/e2e-k8s.sh\"\ndone\n"
  },
  {
    "path": "hack/make-rules/test.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# script to run unit / integration tests, with coverage enabled and junit xml output\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\n# set to 'unit' or 'integration' to run a subset\nMODE=\"${MODE:-all}\"\n\n# build gotestsum\ncd 'hack/tools'\ngo build -o \"${REPO_ROOT}/bin/gotestsum\" gotest.tools/gotestsum\ncd \"${REPO_ROOT}\"\n\ngo_test_opts=(\n  \"-coverprofile=${REPO_ROOT}/bin/${MODE}.cov\"\n  '-covermode' 'count'\n  '-coverpkg' 'sigs.k8s.io/kind/...'\n)\nif [[ \"${MODE}\" = 'unit' ]]; then\n  go_test_opts+=('-short' '-tags=nointegration')\nelif [[ \"${MODE}\" = 'integration' ]]; then\n  go_test_opts+=('-run' '^TestIntegration')\nfi\n\n# run unit tests with coverage enabled and junit output\n(\n  set -x; \n  \"${REPO_ROOT}/bin/gotestsum\" --junitfile=\"${REPO_ROOT}/bin/${MODE}-junit.xml\" \\\n    -- \"${go_test_opts[@]}\" './...'\n)\n\n# filter out generated files\nsed '/zz_generated/d' \"${REPO_ROOT}/bin/${MODE}.cov\" > \"${REPO_ROOT}/bin/${MODE}-filtered.cov\"\n\n# generate cover html\ngo tool cover -html=\"${REPO_ROOT}/bin/${MODE}-filtered.cov\" -o \"${REPO_ROOT}/bin/${MODE}-filtered.html\"\n\n# if we are in CI, copy to the artifact upload location\nif [[ -n \"${ARTIFACTS:-}\" ]]; then\n  cp \"bin/${MODE}-junit.xml\" \"${ARTIFACTS:?}/junit.xml\"\n  cp \"${REPO_ROOT}/bin/${MODE}-filtered.cov\" \"${ARTIFACTS:?}/filtered.cov\"\n  cp \"${REPO_ROOT}/bin/${MODE}-filtered.html\" \"${ARTIFACTS:?}/filtered.html\"\nfi\n"
  },
  {
    "path": "hack/make-rules/update/README.md",
    "content": "This directory contains tools used to update dependencies, generated files, etc."
  },
  {
    "path": "hack/make-rules/update/all.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# script to run all update scripts (except deps)\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\nhack/make-rules/update/deps.sh\nhack/make-rules/update/generated.sh\nhack/make-rules/update/gofmt.sh\n"
  },
  {
    "path": "hack/make-rules/update/deps.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Runs go mod tidy, go mod vendor, and then prunes vendor\n#\n# Usage:\n#   deps.sh\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\n# tidy all modules\ngo mod tidy\n\ncd \"${REPO_ROOT}/hack/tools\"\ngo mod tidy\n\n# NOTE: kindnetd is only built for linux and uses linux APIs\ncd \"${REPO_ROOT}/images/kindnetd\"\nGOOS=linux go mod tidy\n"
  },
  {
    "path": "hack/make-rules/update/generated.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# 'go generate's kind, using tools from vendor (go-bindata)\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\n# build the generators using the tools module\ncd \"${REPO_ROOT}/hack/tools\"\ngo build -o \"${REPO_ROOT}/bin/deepcopy-gen\" k8s.io/code-generator/cmd/deepcopy-gen\n# go back to the root\ncd \"${REPO_ROOT}\"\n\n# turn off module mode before running the generators\n# https://github.com/kubernetes/code-generator/issues/69\n# we also need to populate vendor\n\n# run the generators\nbin/deepcopy-gen --output-file zz_generated.deepcopy.go --go-header-file hack/tools/boilerplate.go.txt ./pkg/internal/apis/config/\nbin/deepcopy-gen --output-file zz_generated.deepcopy.go --go-header-file hack/tools/boilerplate.go.txt ./pkg/apis/config/v1alpha4\n\n\n# set module mode back, return to repo root and gofmt to ensure we format generated code\nmake gofmt\n"
  },
  {
    "path": "hack/make-rules/update/gofmt.sh",
    "content": "#!/bin/bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# script to run gofmt over our code (not vendor)\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\nfind . -name '*.go' -type f -print0 | xargs -0 gofmt -s -w\n"
  },
  {
    "path": "hack/make-rules/verify/README.md",
    "content": "This directory contains tools to verify the state and quality of the repo."
  },
  {
    "path": "hack/make-rules/verify/all.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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 nounset -o pipefail\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\n# exit code, if a script fails we'll set this to 1\nres=0\n\n# run all verify scripts, optionally skipping any of them\n\nif [[ \"${VERIFY_LINT:-true}\" == \"true\" ]]; then\n  echo \"verifying lints ...\"\n  hack/make-rules/verify/lint.sh || res=1\n  cd \"${REPO_ROOT}\"\nfi\n\nif [[ \"${VERIFY_GENERATED:-true}\" == \"true\" ]]; then\n  echo \"verifying generated ...\"\n  hack/make-rules/verify/generated.sh || res=1\n  cd \"${REPO_ROOT}\"\nfi\n\nif [[ \"${VERIFY_SHELLCHECK:-true}\" == \"true\" ]]; then\n  echo \"verifying shellcheck ...\"\n  hack/make-rules/verify/shellcheck.sh || res=1\n  cd \"${REPO_ROOT}\"\nfi\n\n# exit based on verify scripts\nif [[ \"${res}\" = 0 ]]; then\n  echo \"\"\n  echo \"All verify checks passed, congrats!\"\nelse\n  echo \"\"\n  echo \"One or more verify checks failed! See output above...\"\nfi\nexit \"${res}\"\n\n"
  },
  {
    "path": "hack/make-rules/verify/generated.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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 nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\n# place to stick temp binaries\nBINDIR=\"${REPO_ROOT}/bin\"\nmkdir -p \"${BINDIR}\"\n\n# TMP_REPO is used in make_temp_repo_copy\nTMP_REPO=\"$(TMPDIR=\"${BINDIR}\" mktemp -d \"${BINDIR}/verify-deps.XXXXX\")\"\n\n# exit trap cleanup for TMP_REPO\ncleanup() {\n  if [[ -n \"${TMP_REPO}\" ]]; then\n    rm -rf \"${TMP_REPO}\"\n  fi\n}\n\n# copies repo into a temp root saved to TMP_REPO\nmake_temp_repo_copy() {\n  # we need to copy everything but bin (and the old _output) (which is .gitignore anyhow)\n  find . \\\n    -mindepth 1 -maxdepth 1 \\\n    \\( \\\n      -type d -path \"./.git\" -o \\\n      -type d -path \"./bin\" -o \\\n      -type d -path \"./_output\" \\\n    \\) -prune -o \\\n    -exec bash -c 'cp -r \"${0}\" \"${1}/${0}\" >/dev/null 2>&1' {} \"${TMP_REPO}\" \\;\n}\n\nmain() {\n  trap cleanup EXIT\n\n  # copy repo root into tempdir under ./_output\n  make_temp_repo_copy\n\n  # run generated code update script\n  cd \"${TMP_REPO}\"\n  REPO_ROOT=\"${TMP_REPO}\" make generate\n\n  # make sure the temp repo has no changes relative to the real repo\n  diff=$(diff -Nupr \\\n          -x \".git\" \\\n          -x \"bin\" \\\n          -x \"_output\" \\\n          -x \"vendor\" \\\n         \"${REPO_ROOT}\" \"${TMP_REPO}\" 2>/dev/null || true)\n  if [[ -n \"${diff}\" ]]; then\n    echo \"unexpectedly dirty working directory after hack/update/generated.sh\" >&2\n    echo \"\" >&2\n    echo \"${diff}\" >&2\n    echo \"\" >&2\n    echo \"please run make generate\" >&2\n    exit 1\n  fi\n}\n\nmain\n"
  },
  {
    "path": "hack/make-rules/verify/lint.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# script to run linters\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\n# build golangci-lint\ncd \"${REPO_ROOT}/hack/tools\"\ngo build -o \"${REPO_ROOT}\"/bin/golangci-lint github.com/golangci/golangci-lint/v2/cmd/golangci-lint\ncd \"${REPO_ROOT}\"\n\n# first for the repo in general\n\"${REPO_ROOT}\"/bin/golangci-lint --config \"${REPO_ROOT}/hack/tools/.golangci.yml\" run ./...\n\n# then for kindnetd module\ncd \"${REPO_ROOT}/images/kindnetd\"\nGOOS=linux \"${REPO_ROOT}\"/bin/golangci-lint --config \"${REPO_ROOT}/hack/tools/.golangci.yml\" run ./...\n"
  },
  {
    "path": "hack/make-rules/verify/shellcheck.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# CI script to run shellcheck\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\n# required version for this script, if not installed on the host we will\n# use the official docker image instead. keep this in sync with SHELLCHECK_IMAGE\nSHELLCHECK_VERSION=\"0.9.0\"\n# upstream shellcheck latest stable image as of October 23rd, 2019\nSHELLCHECK_IMAGE='docker.io/koalaman/shellcheck-alpine:v0.9.0@sha256:e19ed93c22423970d56568e171b4512c9244fc75dd9114045016b4a0073ac4b7'\n\nDOCKER=\"${DOCKER:-docker}\"\n\n# detect if the host machine has the required shellcheck version installed\n# if so, we will use that instead.\nHAVE_SHELLCHECK=false\nif which shellcheck &>/dev/null; then\n  detected_version=\"$(shellcheck --version | grep 'version: .*')\"\n  if [[ \"${detected_version}\" = \"version: ${SHELLCHECK_VERSION}\" ]]; then\n    HAVE_SHELLCHECK=true\n  fi\nfi\n\n# Find all shell scripts excluding:\n# - Anything git-ignored - No need to lint untracked files.\n# - ./.git/* - Ignore anything in the git object store.\n# - ./vendor/* - Ignore vendored contents.\n# - ./bin/* - No need to lint output directories.\nall_shell_scripts=()\nwhile IFS=$'\\n' read -r script;\n  do git check-ignore -q \"$script\" || all_shell_scripts+=(\"$script\");\ndone < <(grep -irl '#!.*sh' . | grep -Ev '(^\\./\\.git/)|(^\\./vendor/)|(^\\./hack/third_party/)|(^\\./images/.*/scripts/third_party/)|(^\\./bin/)|(\\.go$)')\n\n# common arguments we'll pass to shellcheck\nSHELLCHECK_OPTIONS=(\n  # allow following sourced files that are not specified in the command,\n  # we need this because we specify one file at a time in order to trivially\n  # detect which files are failing\n  '--external-sources'\n  # disabled lint codes\n  # 2330 - disabled due to https://github.com/koalaman/shellcheck/issues/1162\n  '--exclude=2230'\n  # 2126 - disabled because grep -c exits error when there are zero matches,\n  # unlike grep | wc -l\n  '--exclude=2126'\n  # set colorized output\n  '--color=auto'\n)\n\n# actually shellcheck\n# tell the user which we've selected and lint all scripts\n# The shellcheck errors are printed to stdout by default, hence they need to be redirected\n# to stderr in order to be well parsed for Junit representation by juLog function\nres=0\nif ${HAVE_SHELLCHECK}; then\n  >&2 echo \"Using host shellcheck ${SHELLCHECK_VERSION} binary.\"\n  shellcheck \"${SHELLCHECK_OPTIONS[@]}\" \"${all_shell_scripts[@]}\" >&2 || res=$?\nelse\n  >&2 echo \"Using shellcheck ${SHELLCHECK_VERSION} docker image.\"\n  \"${DOCKER}\" run \\\n    --rm -v \"${REPO_ROOT}:${REPO_ROOT}\" -w \"${REPO_ROOT}\" \\\n    \"${SHELLCHECK_IMAGE}\" \\\n  shellcheck \"${SHELLCHECK_OPTIONS[@]}\" \"${all_shell_scripts[@]}\" >&2 || res=$?\nfi\nexit $res\n"
  },
  {
    "path": "hack/release/build/README.md",
    "content": "This directory contains temporary tooling used to make releasing kind easier.\n"
  },
  {
    "path": "hack/release/build/cross.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# simple script to build binaries for release\nset -o errexit -o nounset -o pipefail\n\n# cd to the repo root and setup go\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\nsource hack/build/setup-go.sh\n\n# controls the number of concurrent builds\nPARALLELISM=${PARALLELISM:-6}\n\necho \"Building in parallel for:\"\n# What we do here:\n# - use xargs to build in parallel (-P) while collecting a combined exit code\n# - use cat to supply the individual args to xargs (one line each)\n# - use env -S to split the line into environment variables and execute\n# - ... the build\n# NOTE: the binary name needs to be in single quotes so we delay evaluating\n# GOOS / GOARCH\n# NOTE: disable SC2016 because we _intend_ for these to evaluate later\n# shellcheck disable=SC2016\nif xargs -0 -n1 -P \"${PARALLELISM}\" bash -c 'eval $0; make build KIND_BINARY_NAME=kind-${GOOS}-${GOARCH}'; then\n    echo \"Cross build passed!\" 1>&2\nelse\n    echo \"Cross build failed!\" 1>&2\n    exit 1\nfi < <(cat <<EOF | tr '\\n' '\\0'\nexport GOOS=windows GOARCH=amd64\nexport GOOS=darwin GOARCH=amd64\nexport GOOS=darwin GOARCH=arm64\nexport GOOS=linux GOARCH=amd64\nexport GOOS=linux GOARCH=arm64\nEOF\n)\n\n# add sha256 for binaries\ncd \"${REPO_ROOT}\"/bin\nfor f in kind-*; do\n    shasum -a 256 \"$f\" > \"$f\".sha256sum;\ndone\n"
  },
  {
    "path": "hack/release/build/push-node.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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 nounset -o pipefail\n\nREGISTRY=\"${REGISTRY:-gcr.io/k8s-staging-kind}\"\nIMAGE_NAME=\"${IMAGE_NAME:-node}\"\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\n# ensure we have up to date kind\nmake build\n\n# path to kubernetes sources\nKUBEROOT=\"${KUBEROOT:-\"$(go env GOPATH)\"/src/k8s.io/kubernetes}\"\n\n# ensure we have qemu setup so we can run cross-arch images\n# TODO: dedupe specifying this image?\ndocker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0-28@sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55 --install all\n\n# NOTE: adding platforms is costly in terms of build time\n# we will consider expanding this in the future, for now the aim is to prove\n# multi-arch and enable developers working on commonly available hardware\n# Other users are free to build their own images on additional platforms using\n# their own time and resources. Please see our docs.\nARCHES=\"${ARCHES:-amd64 arm64}\"\nIFS=\" \" read -r -a __arches__ <<< \"$ARCHES\"\n\nset -x\n# ensure clean build\n(cd \"${KUBEROOT}\" && make clean)\n# get kubernetes version\nversion_line=\"$(cd \"${KUBEROOT}\"; ./hack/print-workspace-status.sh | grep 'STABLE_DOCKER_TAG')\"\nkube_version=\"${version_line#\"STABLE_DOCKER_TAG \"}\"\n\n# kubernetes build option(s)\nGOFLAGS=\"${GOFLAGS:-}\"\nif [ -z \"${GOFLAGS}\" ]; then\n    # TODO: dockerless only applies to < 1.24, the version selection here is brittle\n    case \"${kube_version}\" in\n    v1.1[0-8].*)\n        GOFLAGS=\"-tags=providerless\"\n        ;;\n    *)\n        GOFLAGS=\"-tags=providerless,dockerless\"\n        ;;\n    esac\nfi\nexport GOFLAGS\n\n# build for each arch\nIMAGE=\"${REGISTRY}/${IMAGE_NAME}:${kube_version}\"\nimages=()\nfor arch in \"${__arches__[@]}\"; do\n    image=\"${REGISTRY}/${IMAGE_NAME}-${arch}:${kube_version}\"\n    \"${REPO_ROOT}/bin/kind\" build node-image --image=\"${image}\" --arch=\"${arch}\" \"${KUBEROOT}\"\n    images+=(\"${image}\")\ndone\n\n# combine to manifest list tagged with kubernetes version\n# images must be pushed to be referenced by docker manifest\n# we push only after all builds have succeeded\nfor image in \"${images[@]}\"; do\n    docker push \"${image}\"\ndone\ndocker manifest rm \"${IMAGE}\" || true\ndocker manifest create \"${IMAGE}\" \"${images[@]}\"\ndocker manifest push \"${IMAGE}\"\n"
  },
  {
    "path": "hack/release/create.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# creates a release and following pre-release commit for `kind`\n# builds binaries between the commits\n# Use like: create.sh <release-version> <next-prerelease-version>\n# EG: create.sh 0.3.0 0.4.0\nset -o errexit -o nounset -o pipefail\n\nUPSTREAM='https://github.com/kubernetes-sigs/kind.git'\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\n# check for arguments\nif [ \"$#\" -ne 2 ]; then\n    echo \"Usage: create.sh release-version next-prerelease-version\"\n    exit 1\nfi\n\n# darwin is great\nSED=\"sed\"\nif which gsed &>/dev/null; then\n  SED=\"gsed\"\nfi\nif ! (${SED} --version 2>&1 | grep -q GNU); then\n  echo \"!!! GNU sed is required.  If on OS X, use 'brew install gnu-sed'.\" >&2\n  exit 1\nfi\n\nVERSION_FILE=\"./pkg/cmd/kind/version/version.go\"\n\n# update core version in go code to $1 and pre-release version to $2\nset_version() {\n  ${SED} -i \"s/versionCore = .*/versionCore = \\\"${1}\\\"/\" \"${VERSION_FILE}\"\n  ${SED} -i \"s/versionPreRelease = .*/versionPreRelease = \\\"${2}\\\"/\" \"${VERSION_FILE}\"\n  echo \"Updated ${VERSION_FILE} for ${1}\"\n}\n\n# make a commit denoting the version ($1)\nmake_commit() {\n  git add \"${VERSION_FILE}\"\n  git commit -m \"version ${1}\"\n  echo \"Created commit for ${1}\"\n}\n\n# add a git tag with $1\nadd_tag() {\n  git tag \"${1}\"\n  echo \"Tagged ${1}\"\n}\n\n# create the first version, tag and build it\nset_version \"${1}\" \"\"\nmake_commit \"v${1}\"\nadd_tag \"v${1}\"\necho \"Building ...\"\nmake clean && ./hack/release/build/cross.sh\n\n# update to the second version\nset_version \"${2}\" \"alpha\"\nmake_commit \"v${2}-alpha\"\nadd_tag \"v${2}-alpha\"\n\n# print follow-up instructions\necho \"\"\necho \"Created commits for v${1} and v${2}, you should now:\"\necho \" - git push\"\necho \" - File a PR with these pushed commits\"\necho \" - Merge the PR\"\necho \" - git push ${UPSTREAM} v${1}\"\necho \" - git push ${UPSTREAM} v${2}-alpha\"\necho \" - Create a GitHub release from the pushed tag v${1}\"\n"
  },
  {
    "path": "hack/release/get-contributors.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# inputs are:\n# - LAST_VERSION_TAG -- This is the version to get commits since\n#    like: LAST_VERSION_TAG=\"v0.8.1\"\n# - GITHUB_OATH_TOKEN -- used to avoid hitting API rate limits\nORG=\"kubernetes-sigs\"\nREPO=\"kind\"\n\n# query git for contributors since the tag\ncontributors=()\nwhile IFS='' read -r line; do contributors+=(\"$line\"); done < <(git log --format=\"%aN <%aE>\" \"${LAST_VERSION_TAG:?}..\" | sort | uniq)\n\n# query github for usernames and output bulleted list\ncontributor_logins=()\nfor contributor in \"${contributors[@]}\"; do\n    # get a commit for this author\n    commit_for_contributor=\"$(git log --author=\"${contributor}\" --pretty=format:\"%H\" -1)\"\n    # lookup the  commit info to get the login\n    contributor_logins+=(\"$(curl \\\n        -sG \\\n        ${GITHUB_OAUTH_TOKEN:+-H \"Authorization: Bearer ${GITHUB_OAUTH_TOKEN:?}\"} \\\n        --data-urlencode \"q=${contributor}\" \\\n        \"https://api.github.com/repos/${ORG}/${REPO}/commits/${commit_for_contributor}\" \\\n    | jq -r .author.login\n    )\")\ndone\n\necho \"Contributors since ${LAST_VERSION_TAG}:\"\n# echo sorted formatted list\nwhile IFS='' read -r contributor_login; do\n     echo \"- @${contributor_login}\"\ndone < <(for c in \"${contributor_logins[@]}\"; do echo \"$c\"; done | LC_COLLATE=C sort --ignore-case | uniq)\n"
  },
  {
    "path": "hack/release/push-node.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2024 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 replaces hack/release/build/push-node.sh for Kubernetes v1.31+\n# usage: push-node.sh v1.32.0\n\nset -o errexit -o nounset -o pipefail\n\nREGISTRY=\"${REGISTRY:-gcr.io/k8s-staging-kind}\"\nIMAGE_NAME=\"${IMAGE_NAME:-node}\"\n\n# cd to the repo root\nREPO_ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" &> /dev/null && pwd -P)\"\ncd \"${REPO_ROOT}\"\n\nVERSION=\"${1:-}\"\nif [[ -z \"${VERSION}\" ]]; then\n    echo >&2 \"version argument not supplied, looking up current stable ...\"\n    VERSION=\"$(curl -sL https://dl.k8s.io/release/stable.txt)\"\nfi\necho >&2 \"will build node image for Kubernetes ${VERSION} ...\"\n\n# ensure we have up to date kind\necho >&2 \"building kind ...\"\nmake build\n\n# ensure we have qemu setup so we can run cross-arch images\n# TODO: dedupe specifying this image?\necho >&2 \"ensuring binfmt_misc ...\"\ndocker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0-28@sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55 --install all\n\n# NOTE: adding platforms is costly in terms of build time\n# we will consider expanding this in the future, for now the aim is to prove\n# multi-arch and enable developers working on commonly available hardware\n# Other users are free to build their own images on additional platforms using\n# their own time and resources. Please see our docs.\nARCHES=\"${ARCHES:-amd64 arm64}\"\nIFS=\" \" read -r -a __arches__ <<< \"$ARCHES\"\n\nset -x\n# build for each arch\nIMAGE=\"${REGISTRY}/${IMAGE_NAME}:${VERSION}\"\nimages=()\nfor arch in \"${__arches__[@]}\"; do\n    image=\"${REGISTRY}/${IMAGE_NAME}-${arch}:${VERSION}\"\n    echo >&2 \"building ${image} ...\"\n    \"${REPO_ROOT}/bin/kind\" build node-image --image=\"${image}\" --arch=\"${arch}\" \"${VERSION}\"\n    images+=(\"${image}\")\ndone\n\n# combine to manifest list tagged with kubernetes version\n# images must be pushed to be referenced by docker manifest\n# we push only after all builds have succeeded\nfor image in \"${images[@]}\"; do\n    docker push \"${image}\"\ndone\ndocker manifest rm \"${IMAGE}\" || true\ndocker manifest create \"${IMAGE}\" \"${images[@]}\"\ndocker manifest push \"${IMAGE}\"\n"
  },
  {
    "path": "hack/third_party/gimme/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2018 gimme contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "hack/third_party/gimme/README.md",
    "content": "# gimme\n\nThis is an unmodified copy of [gimme], so we don't have to download it\nfrom the internet.\n\n[gimme]: https://github.com/travis-ci/gimme"
  },
  {
    "path": "hack/third_party/gimme/gimme",
    "content": "#!/usr/bin/env bash\n# vim:noexpandtab:ts=2:sw=2:\n#\n#+  Usage: $(basename $0) [flags] [go-version] [version-prefix]\n#+  -\n#+  Version: ${GIMME_VERSION}\n#+  Copyright: ${GIMME_COPYRIGHT}\n#+  License URL: ${GIMME_LICENSE_URL}\n#+  -\n#+  Install go!  There are multiple types of installations available, with 'auto' being the default.\n#+  If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing\n#+  go installation.  This behavior may be disabled by providing '-f/--force/force' as first positional\n#+  argument.\n#+  -\n#+  Option flags:\n#+          -h --help help - show this help text and exit\n#+    -V --version version - show the version only and exit\n#+        -f --force force - remove the existing go installation if present prior to install\n#+          -l --list list - list installed go versions and exit\n#+        -k --known known - list known go versions and exit\n#+    --force-known-update - when used with --known, ignores the cache and updates\n#+    -r --resolve resolve - resolve a version specifier to a version, show that and exit\n#+  -\n#+  Influential env vars:\n#+  -\n#+        GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg)\n#+    GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}',\n#+                           may be given as second positional arg)\n#+              GIMME_ARCH - arch to install (default '${GIMME_ARCH}')\n#+        GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}')\n#+        GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}')\n#+     GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}')\n#+                GIMME_OS - os to install (default '${GIMME_OS}')\n#+               GIMME_TMP - temp directory (default '${GIMME_TMP}')\n#+              GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git')\n#+                           (default '${GIMME_TYPE}')\n#+      GIMME_INSTALL_RACE - install race directory after compile if non-empty.\n#+                           If the install type is 'binary', this option is ignored.\n#+             GIMME_DEBUG - enable tracing if non-empty\n#+      GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host\n#+        GIMME_SILENT_ENV - omit the 'go version' line from env file\n#+       GIMME_CGO_ENABLED - enable build of cgo support\n#+     GIMME_CC_FOR_TARGET - cross compiler for cgo support\n#+     GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}')\n#+        GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}')\n#+   GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}')\n#+  -\n#\nset -e\nshopt -s nullglob\nshopt -s dotglob\nshopt -s extglob\nset -o pipefail\n\n[[ ${GIMME_DEBUG} ]] && set -x\n\nreadonly GIMME_VERSION=\"v1.5.4\"\nreadonly GIMME_COPYRIGHT=\"Copyright (c) 2015-2020 gimme contributors\"\nreadonly GIMME_LICENSE_URL=\"https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE\"\nexport GIMME_VERSION\nexport GIMME_COPYRIGHT\nexport GIMME_LICENSE_URL\n\nprogram_name=\"$(basename \"$0\")\"\n# shellcheck disable=SC1117\nwarn() { printf >&2 \"%s: %s\\n\" \"${program_name}\" \"${*}\"; }\ndie() {\n\twarn \"$@\"\n\texit 1\n}\n\n# We don't want to go around hitting Google's servers with requests for\n# files named HEAD@{date}.tar so we only try binary/source downloads if\n# it looks like a plausible name to us.\n# We don't need to support 0. releases of Go.\n# We don't support 5 digit major-versions of Go (limit back-tracking in RE).\n# We don't support very long versions\n#   (both to avoid annoying download server operators with attacks and\n#    because regexp backtracking can be pathological).\n# Per _assert_version_given we do assume 2.0 not 2\nALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\\.[0-9][0-9a-zA-Z_-]{0,9})+$'\n#\n# The main path which allowed these to leak upstream before has been closed\n# but a valid git repo tag or branch-name will still reach the point of\n# being _tried_ upstream.\n\n# _do_curl \"url\" \"file\"\n_do_curl() {\n\tmkdir -p \"$(dirname \"${2}\")\"\n\n\tif command -v curl >/dev/null; then\n\t\tcurl -sSLf \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v wget >/dev/null; then\n\t\twget -q \"${1}\" -O \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v fetch >/dev/null; then\n\t\tfetch -q \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\techo >&2 'error: no curl, wget, or fetch found'\n\texit 1\n}\n\n# _sha256sum \"file\"\n_sha256sum() {\n\tif command -v sha256sum &>/dev/null; then\n\t\tsha256sum \"$@\"\n\telif command -v gsha256sum &>/dev/null; then\n\t\tgsha256sum \"$@\"\n\telse\n\t\tshasum -a 256 \"$@\"\n\tfi\n}\n\n# sort versions, handling 1.10 after 1.9, not before 1.2\n# FreeBSD sort has --version-sort, none of the others do\n# Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty\nif sort --version-sort </dev/null &>/dev/null; then\n\t_version_sort() { sort --version-sort; }\nelse\n\t_version_sort() {\n\t\t# If we go to four-digit minor or patch versions, then extend the padding here\n\t\t# (but in such a world, perhaps --version-sort will have become standard by then?)\n\t\tsed -E 's/\\.([0-9](\\.|$))/.00\\1/g; s/\\.([0-9][0-9](\\.|$))/.0\\1/g' |\n\t\t\tsort --general-numeric-sort |\n\t\t\tsed 's/\\.00*/./g'\n\t}\nfi\n\n# _do_curls \"file\" \"url\" [\"url\"...]\n_do_curls() {\n\tf=\"${1}\"\n\tshift\n\tif _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\treturn 0\n\tfi\n\tfor url in \"${@}\"; do\n\t\tif _do_curl \"${url}\" \"${f}\"; then\n\t\t\tif _do_curl \"${url}.sha256\" \"${f}.sha256\"; then\n\t\t\t\techo \"$(cat \"${f}.sha256\")  ${f}\" >\"${f}.sha256.tmp\"\n\t\t\t\tmv \"${f}.sha256.tmp\" \"${f}.sha256\"\n\t\t\t\tif ! _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\t\t\t\twarn \"sha256sum failed for '${f}'\"\n\t\t\t\t\twarn 'continuing to next candidate URL'\n\t\t\t\t\tcontinue\n\t\t\t\tfi\n\t\t\tfi\n\t\t\treturn\n\t\tfi\n\tdone\n\trm -f \"${f}\"\n\treturn 1\n}\n\n# _binary \"version\" \"file.tar.gz\" \"arch\"\n_binary() {\n\tlocal version=${1}\n\tlocal file=${2}\n\tlocal arch=${3}\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz\"\n\t)\n\tif [[ \"${GIMME_OS}\" == 'darwin' && \"${GIMME_BINARY_OSX}\" ]]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz\"\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${arch}\" = 'arm' ]; then\n\t\t# attempt \"armv6l\" vs just \"arm\" first (since that's what's officially published)\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz\" # go1.6beta2 & go1.6rc1\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz\" # go1.6beta1\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip\"\n\t\t)\n\tfi\n\t_do_curls \"${file}\" \"${urls[@]}\"\n}\n\n# _source \"version\" \"file.src.tar.gz\"\n_source() {\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz\"\n\t\t\"https://github.com/golang/go/archive/go${1}.tar.gz\"\n\t)\n\t_do_curls \"${2}\" \"${urls[@]}\"\n}\n\n# _fetch \"dir\"\n_fetch() {\n\tmkdir -p \"$(dirname \"${1}\")\"\n\n\tif [[ -d \"${1}/.git\" ]]; then\n\t\t(\n\t\t\tcd \"${1}\"\n\t\t\tgit remote set-url origin \"${GIMME_GO_GIT_REMOTE}\"\n\t\t\tgit fetch -q --all && git fetch -q --tags\n\t\t)\n\t\treturn\n\tfi\n\n\tgit clone -q \"${GIMME_GO_GIT_REMOTE}\" \"${1}\"\n}\n\n# _checkout \"version\" \"dir\"\n# NB: might emit a \"renamed version\" on stdout\n_checkout() {\n\tlocal spec=\"${1:?}\" godir=\"${2:?}\"\n\t# We are called twice, once during validation that a version was given and\n\t# later during build.  We don't want to fetch twice, so we are fetching\n\t# during the validation only, in the caller.\n\n\tif [[ \"${spec}\" =~ ^[0-9a-f]{6,}$ ]]; then\n\t\t# We always treat this as a commit sha, whether instead of doing\n\t\t# branch tests etc.  It looks like a commit sha and the Go maintainers\n\t\t# aren't daft enough to use pure hex for a tag or branch.\n\t\tgit -C \"$godir\" reset -q --hard \"${spec}\" || return 1\n\t\treturn 0\n\tfi\n\n\t# If spec looks like HEAD^{something} or HEAD^^^ then trying\n\t# origin/$spec would succeed but we'd write junk to the filesystem,\n\t# propagating annoying characters out.\n\tlocal retval probe_named disallow rev\n\n\tprobe_named=1\n\tdisallow='[@^~:{}]'\n\tif [[ \"${spec}\" =~ $disallow ]]; then\n\t\tprobe_named=0\n\t\t[[ \"${spec}\" != \"@\" ]] || spec=\"HEAD\"\n\tfi\n\n\ttry_spec() { git -C \"${godir}\" reset -q --hard \"$@\" -- 2>/dev/null; }\n\n\tretval=1\n\tif ((probe_named)); then\n\t\tretval=0\n\t\ttry_spec \"origin/${spec}\" ||\n\t\t\ttry_spec \"origin/go${spec}\" ||\n\t\t\t{ [[ \"${spec}\" == \"tip\" ]] && try_spec origin/master; } ||\n\t\t\ttry_spec \"refs/tags/${spec}\" ||\n\t\t\ttry_spec \"refs/tags/go${spec}\" ||\n\t\t\tretval=1\n\tfi\n\n\tif ((retval)); then\n\t\tretval=0\n\t\t# We're about to reset anyway, if we succeed, so we should reset to a\n\t\t# known state before parsing what might be relative specs\n\t\ttry_spec origin/master &&\n\t\t\trev=\"$(git -C \"${godir}\" rev-parse --verify -q \"${spec}^{object}\")\" &&\n\t\t\ttry_spec \"${rev}\" &&\n\t\t\tgit -C \"${godir}\" rev-parse --verify -q --short=12 \"${rev}\" ||\n\t\t\tretval=1\n\t\t# that rev-parse prints to stdout, so we can affect the version seen\n\tfi\n\n\tunset -f try_spec\n\treturn $retval\n}\n\n# _extract \"file.tar.gz\" \"dir\"\n_extract() {\n\tmkdir -p \"${2}\"\n\n\tif [[ \"${1}\" == *.tar.gz ]]; then\n\t\ttar -xf \"${1}\" -C \"${2}\" --strip-components 1\n\telse\n\t\tunzip -q \"${1}\" -d \"${2}\"\n\t\tmv \"${2}\"/go/* \"${2}\"\n\t\trmdir \"${2}\"/go\n\tfi\n}\n\n# _setup_bootstrap\n_setup_bootstrap() {\n\tlocal versions=(\"1.18\" \"1.17\" \"1.16\" \"1.15\" \"1.14\" \"1.13\" \"1.12\" \"1.11\" \"1.10\" \"1.9\" \"1.8\" \"1.7\" \"1.6\" \"1.5\" \"1.4\")\n\n\t# try existing\n\tfor v in \"${versions[@]}\"; do\n\t\tfor candidate in \"${GIMME_ENV_PREFIX}/go${v}\"*\".env\"; do\n\t\t\tif [ -s \"${candidate}\" ]; then\n\t\t\t\t# shellcheck source=/dev/null\n\t\t\t\tGOROOT_BOOTSTRAP=\"$(source \"${candidate}\" 2>/dev/null && go env GOROOT)\"\n\t\t\t\texport GOROOT_BOOTSTRAP\n\t\t\t\treturn 0\n\t\t\tfi\n\t\tdone\n\tdone\n\n\t# try binary\n\tfor v in \"${versions[@]}\"; do\n\t\tif [ -n \"$(_try_binary \"${v}\" \"${GIMME_HOSTARCH}\")\" ]; then\n\t\t\texport GOROOT_BOOTSTRAP=\"${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}\"\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\techo >&2 \"Unable to setup go bootstrap from existing or binary\"\n\treturn 1\n}\n\n# _compile \"dir\"\n_compile() {\n\t(\n\t\tif grep -q GOROOT_BOOTSTRAP \"${1}/src/make.bash\" &>/dev/null; then\n\t\t\t_setup_bootstrap || return 1\n\t\tfi\n\t\tcd \"${1}\"\n\t\tif [[ -d .git ]]; then\n\t\t\tgit clean -dfx -q\n\t\tfi\n\t\tcd src\n\t\texport GOOS=\"${GIMME_OS}\" GOARCH=\"${GIMME_ARCH}\"\n\t\texport CGO_ENABLED=\"${GIMME_CGO_ENABLED}\"\n\t\texport CC_FOR_TARGET=\"${GIMME_CC_FOR_TARGET}\"\n\n\t\tlocal make_log=\"${1}/make.${GOOS}.${GOARCH}.log\"\n\t\tif [[ \"${GIMME_DEBUG}\" -ge \"2\" ]]; then\n\t\t\t./make.bash -v 2>&1 | tee \"${make_log}\" 1>&2 || return 1\n\t\telse\n\t\t\t./make.bash &>\"${make_log}\" || return 1\n\t\tfi\n\t)\n}\n\n_try_install_race() {\n\tif [[ ! \"${GIMME_INSTALL_RACE}\" ]]; then\n\t\treturn 0\n\tfi\n\t\"${1}/bin/go\" install -race std\n}\n\n_can_compile() {\n\tcat >\"${GIMME_TMP}/test.go\" <<'EOF'\npackage main\nimport \"os\"\nfunc main() {\n\tos.Exit(0)\n}\nEOF\n\t\"${1}/bin/go\" run \"${GIMME_TMP}/test.go\"\n}\n\n# _env \"dir\"\n_env() {\n\t[[ -d \"${1}/bin\" && -x \"${1}/bin/go\" ]] || return 1\n\n\t# if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source\n\t# automatically\n\tGOROOT=\"${1}\" GOFLAGS=\"\" \"${1}/bin/go\" version &>/dev/null || return 1\n\n\t# https://twitter.com/davecheney/status/431581286918934528\n\t# we have to GOROOT sometimes because we use official release binaries in unofficial locations :(\n\t#\n\t# Issue 87 leads to:\n\t#   No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it.\n\t#   The \"avoid setting it\" is _only_ for people using official releases in official locations.\n\t#   Tools like `gimme` are the reason that GOROOT-in-env exists.\n\n\techo\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" ]]; then\n\t\techo 'unset GOOS;'\n\telse\n\t\techo 'export GOOS=\"'\"${GIMME_OS}\"'\";'\n\tfi\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\techo 'unset GOARCH;'\n\telse\n\t\techo 'export GOARCH=\"'\"${GIMME_ARCH}\"'\";'\n\tfi\n\n\techo \"export GOROOT='${1}';\"\n\n\t# shellcheck disable=SC2016\n\techo 'export PATH=\"'\"${1}/bin\"':${PATH}\";'\n\tif [[ -z \"${GIMME_SILENT_ENV}\" ]]; then\n\t\techo 'go version >&2;'\n\tfi\n\techo\n}\n\n# _env_alias \"dir\" \"env-file\"\n_env_alias() {\n\tif [[ \"${GIMME_NO_ENV_ALIAS}\" ]]; then\n\t\techo \"${2}\"\n\t\treturn\n\tfi\n\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" && \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\t# GIMME_GO_VERSION might be a branch, which can contain '/'\n\t\tlocal dest=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\\//__}.env\"\n\t\tcp \"${2}\" \"${dest}\"\n\t\tln -sf \"${dest}\" \"${GIMME_ENV_PREFIX}/latest.env\"\n\t\techo \"${dest}\"\n\telse\n\t\techo \"${2}\"\n\tfi\n}\n\n_try_existing() {\n\tcase \"${1}\" in\n\tbinary)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\t\t;;\n\tsource)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\t\t;;\n\t*)\n\t\t_try_existing binary || _try_existing source\n\t\treturn $?\n\t\t;;\n\tesac\n\n\tif [[ -x \"${existing_ver}/bin/go\" && -s \"${existing_env}\" ]]; then\n\t\t# newer envs have existing semi-colon at end of line, because newer gimme\n\t\t# puts them there; envs created before that change lack those semi-colons\n\t\t# and should gain them, to make it easier for people using eval without\n\t\t# double-quoting the command substition.\n\t\tsed -e 's/\\([^;]\\)$/\\1;/' <\"${existing_env}\"\n\t\t# gimme is the corner-case where GOROOT _should_ be overriden, since if the\n\t\t# ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is\n\t\t# unset, then it will be used and the wrong golang will be picked up.\n\t\t# Lots of old installs won't have GOROOT; munge it from $PATH\n\t\tif grep -qs '^unset GOROOT' -- \"${existing_env}\"; then\n\t\t\tsed -n -e 's/^export PATH=\"\\(.*\\)\\/bin:.*$/export GOROOT='\"'\"'\\1'\"'\"';/p' <\"${existing_env}\"\n\t\t\techo\n\t\tfi\n\t\t# Export the same variables whether building new or using existing\n\t\techo \"export GIMME_ENV='${existing_env}';\"\n\t\treturn\n\tfi\n\n\treturn 1\n}\n\n# _try_binary \"version\" \"arch\"\n_try_binary() {\n\tlocal version=${1}\n\tlocal arch=${2}\n\tlocal bin_tgz=\"${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz\"\n\tlocal bin_dir=\"${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}\"\n\tlocal bin_env=\"${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env\"\n\n\t[[ \"${version}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\tbin_tgz=${bin_tgz%.tar.gz}.zip\n\tfi\n\n\t_binary \"${version}\" \"${bin_tgz}\" \"${arch}\" || return 1\n\t_extract \"${bin_tgz}\" \"${bin_dir}\" || return 1\n\t_env \"${bin_dir}\" | tee \"${bin_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${bin_dir}\" \"${bin_env}\")\\\"\"\n}\n\n_try_source() {\n\tlocal src_tgz=\"${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz\"\n\tlocal src_dir=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\tlocal src_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\n\t[[ \"${GIMME_GO_VERSION}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\t_source \"${GIMME_GO_VERSION}\" \"${src_tgz}\" || return 1\n\t_extract \"${src_tgz}\" \"${src_dir}\" || return 1\n\t_compile \"${src_dir}\" || return 1\n\t_try_install_race \"${src_dir}\" || return 1\n\t_env \"${src_dir}\" | tee \"${src_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${src_dir}\" \"${src_env}\")\\\"\"\n}\n\n# We do _not_ try to use any version caching with _try_existing(), but instead\n# build afresh each time.  We don't want to deal with someone moving the repo\n# to other-version, doing an install, then resetting it back to\n# last-version-we-saw and thus introducing conflicts.\n#\n# If you want to re-use a built-at-spec version, then avoid moving the repo\n# and source the generated .env manually.\n# Note that the env will just refer to the 'go' directory, so it's not safe\n# to reuse anyway.\n_try_git() {\n\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\tlocal git_env=\"${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env\"\n\tlocal resolved_sha\n\n\t# Any tags should have been resolved when we asserted that we were\n\t# given a version, so no need to handle that here.\n\t_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\" >/dev/null || return 1\n\t_compile \"${git_dir}\" || return 1\n\t_try_install_race \"${git_dir}\" || return 1\n\t_env \"${git_dir}\" | tee \"${git_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${git_dir}\" \"${git_env}\")\\\"\"\n}\n\n_wipe_version() {\n\tlocal env_file=\"${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\n\tif [[ -s \"${env_file}\" ]]; then\n\t\trm -rf \"$(awk -F\\\" '/GOROOT/ { print $2 }' \"${env_file}\")\"\n\t\trm -f \"${env_file}\"\n\tfi\n}\n\n_list_versions() {\n\tif [ ! -d \"${GIMME_VERSION_PREFIX}\" ]; then\n\t\treturn 0\n\tfi\n\n\tlocal current_version\n\tcurrent_version=\"$(go env GOROOT 2>/dev/null)\"\n\tcurrent_version=\"${current_version##*/go}\"\n\tcurrent_version=\"${current_version%%.${GIMME_OS}.*}\"\n\n\t# 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash\n\t# doesn't appear to have anything like that.\n\tfor d in \"${GIMME_VERSION_PREFIX}/go\"*\".${GIMME_OS}.\"*; do\n\t\tlocal cleaned=\"${d##*/go}\"\n\t\tcleaned=\"${cleaned%%.${GIMME_OS}.*}\"\n\t\techo \"${cleaned}\"\n\tdone | _version_sort | while read -r cleaned; do\n\t\techo -en \"${cleaned}\"\n\t\tif [[ \"${cleaned}\" == \"${current_version}\" ]]; then\n\t\t\techo -en ' <= current' >&2\n\t\tfi\n\t\techo\n\tdone\n}\n\n_update_remote_known_list_if_needed() {\n\t# shellcheck disable=SC1117\n\tlocal exp=\"go([[:alnum:]\\.]*)\\.src.*\" # :alnum: catches beta versions too\n\tlocal list=\"${GIMME_VERSION_PREFIX}/known-versions.txt\"\n\tlocal dlfile=\"${GIMME_TMP}/known-dl\"\n\n\tif [[ -e \"${list}\" ]] &&\n\t\t! ((force_known_update)) &&\n\t\t! _file_older_than_secs \"${list}\" \"${GIMME_KNOWN_CACHE_MAX}\"; then\n\t\techo \"${list}\"\n\t\treturn 0\n\tfi\n\n\t[[ -d \"${GIMME_VERSION_PREFIX:?}\" ]] || mkdir -p -- \"${GIMME_VERSION_PREFIX}\"\n\n\t_do_curl \"${GIMME_LIST_KNOWN}\" \"${dlfile}\"\n\n\twhile read -r line; do\n\t\tif [[ \"${line}\" =~ ${exp} ]]; then\n\t\t\techo \"${BASH_REMATCH[1]}\"\n\t\tfi\n\tdone <\"${dlfile}\" | _version_sort | uniq >\"${list}.new\"\n\trm -f \"${list}\" &>/dev/null\n\tmv \"${list}.new\" \"${list}\"\n\n\trm -f \"${dlfile}\"\n\techo \"${list}\"\n\treturn 0\n}\n\n_list_known() {\n\tlocal knownfile\n\tknownfile=\"$(_update_remote_known_list_if_needed)\"\n\n\t(\n\t\t_list_versions 2>/dev/null\n\t\tcat -- \"${knownfile}\"\n\t) | grep . | _version_sort | uniq\n}\n\n# For the \"invoked on commandline\" case, we want to always pass unknown\n# strings through, so that we can be a uniqueness filter, but for unknown\n# names we want to exit with a value other than 1, so we document that\n# we'll exit 2.  For use by other functions, 2 is as good as 1.\n_resolve_version() {\n\tcase \"${1}\" in\n\tstable)\n\t\t_get_curr_stable\n\t\treturn 0\n\t\t;;\n\toldstable)\n\t\t_get_old_stable\n\t\treturn 0\n\t\t;;\n\ttip)\n\t\techo \"tip\"\n\t\treturn 0\n\t\t;;\n\t*.x)\n\t\ttrue\n\t\t;;\n\t*)\n\t\techo \"${1}\"\n\t\tlocal GIMME_GO_VERSION=\"$1\"\n\t\tlocal ASSERT_ABORT='return'\n\t\tif _assert_version_given 2>/dev/null; then\n\t\t\treturn 0\n\t\tfi\n\t\twarn \"version specifier '${1}' unknown\"\n\t\treturn 2\n\t\t;;\n\tesac\n\t# We have a .x suffix\n\tlocal base=\"${1%.x}\"\n\tlocal ver last='' known\n\tknown=\"$(_update_remote_known_list_if_needed)\" # will be version-sorted\n\tif [[ ! \"${base}\" =~ ^[0-9.]+$ ]]; then\n\t\twarn \"resolve pattern '${base}.x' invalid for .x finding\"\n\t\treturn 2\n\tfi\n\t# The `.x` is optional; \"1.10\" matches \"1.10.x\"\n\tlocal search=\"^${base//./\\\\.}(\\\\.[0-9.]+)?\\$\"\n\t# avoid regexp attacks\n\twhile read -r ver; do\n\t\t[[ \"${ver}\" =~ $search ]] || continue\n\t\tlast=\"${ver}\"\n\tdone <\"$known\"\n\tif [[ -n \"${last}\" ]]; then\n\t\techo \"${last}\"\n\t\treturn 0\n\tfi\n\techo \"${1}\"\n\twarn \"given '${1}' but no release for '${base}' found\"\n\treturn 2\n}\n\n_realpath() {\n\t# shellcheck disable=SC2005\n\t[ -d \"$1\" ] && echo \"$(cd \"$1\" && pwd)\" || echo \"$(cd \"$(dirname \"$1\")\" && pwd)/$(basename \"$1\")\"\n}\n\n_get_curr_stable() {\n\tlocal stable=\"${GIMME_VERSION_PREFIX}/stable\"\n\n\tif _file_older_than_secs \"${stable}\" 86400; then\n\t\t_update_stable \"${stable}\"\n\tfi\n\n\tcat \"${stable}\"\n}\n\n_get_old_stable() {\n\tlocal oldstable=\"${GIMME_VERSION_PREFIX}/oldstable\"\n\n\tif _file_older_than_secs \"${oldstable}\" 86400; then\n\t\t_update_oldstable \"${oldstable}\"\n\tfi\n\n\tcat \"${oldstable}\"\n}\n\n_update_stable() {\n\tlocal stable=\"${1}\"\n\tlocal url=\"https://golang.org/VERSION?m=text\"\n\n\t_do_curl \"${url}\" \"${stable}\"\n\tsed -i.old -e 's/^go\\(.*\\)/\\1/' \"${stable}\"\n\trm -f \"${stable}.old\"\n}\n\n_update_oldstable() {\n\tlocal oldstable=\"${1}\"\n\tlocal oldstable_x\n\toldstable_x=$(_get_curr_stable | awk -F. '{\n\t\t$2--;\n\t\tprint $1 \".\" $2 \".\" \"x\"\n\t}')\n\t_resolve_version \"${oldstable_x}\" >\"${oldstable}\"\n}\n\n_last_mod_timestamp() {\n\tlocal filename=\"${1}\"\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin | *bsd)\n\t\tstat -f %m \"${filename}\"\n\t\t;;\n\tlinux)\n\t\tstat -c %Y \"${filename}\"\n\t\t;;\n\tesac\n}\n\n_file_older_than_secs() {\n\tlocal file=\"${1}\"\n\tlocal age_secs=\"${2}\"\n\tlocal ts\n\t# if the file does not exist, we return true, as the cache needs updating\n\tts=\"$(_last_mod_timestamp \"${file}\" 2>/dev/null)\" || return 0\n\t((($(date +%s) - ts) > age_secs))\n}\n\n_assert_version_given() {\n\t# By the time we're called, aliases such as \"stable\" must have been resolved\n\t# but we could be a reference in git.\n\t#\n\t# Versions can include suffices such as in \"1.8beta2\", so our assumption is that\n\t# there will always be a minor present; the first public release was \"1.0\" so\n\t# we assume \"2.0\" not \"2\".\n\n\tif [[ -z \"${GIMME_GO_VERSION}\" ]]; then\n\t\techo >&2 'error: no GIMME_GO_VERSION supplied'\n\t\techo >&2 \"  ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}\"\n\t\techo >&2 \"  ex: ${0} 1.4.1 ${*}\"\n\t\t${ASSERT_ABORT:-exit} 1\n\tfi\n\n\t# Note: _resolve_version calls back to us (_assert_version_given), but\n\t# only for cases where the version does not end with .x, so this should\n\t# be safe.\n\t# This should be untangled.  PRs accepted, good starter project.\n\tif [[ \"${GIMME_GO_VERSION}\" == *.x ]]; then\n\t\tGIMME_GO_VERSION=\"$(_resolve_version \"${GIMME_GO_VERSION}\")\" || ${ASSERT_ABORT:-exit} 1\n\tfi\n\n\tif [[ \"${GIMME_GO_VERSION}\" == +([[:digit:]]).+([[:digit:]])* ]]; then\n\t\treturn 0\n\tfi\n\n\t# Here we resolve symbolic references.  If we don't, then we get some\n\t# random git tag name being accepted as valid and then we try to\n\t# curl garbage from upstream.\n\tif [[ \"${GIMME_TYPE}\" == \"auto\" || \"${GIMME_TYPE}\" == \"git\" ]]; then\n\t\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\t\tlocal resolved_sha\n\t\t_fetch \"${git_dir}\"\n\t\tif resolved_sha=\"$(_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\")\"; then\n\t\t\tif [[ -n \"${resolved_sha}\" ]]; then\n\t\t\t\t# Break our normal silence, this one really needs to be seen on stderr\n\t\t\t\t# always; auditability and knowing what version of Go you got wins.\n\t\t\t\twarn \"resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'\"\n\t\t\t\tGIMME_GO_VERSION=\"${resolved_sha}\"\n\t\t\tfi\n\t\t\treturn 0\n\t\tfi\n\tfi\n\n\techo >&2 'error: GIMME_GO_VERSION not recognized as valid'\n\techo >&2 \"  got: ${GIMME_GO_VERSION}\"\n\t${ASSERT_ABORT:-exit} 1\n}\n\n_exclude_from_backups() {\n\t# Please avoid anything which requires elevated privileges or is obnoxious\n\t# enough to offend the invoker\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin)\n\t\t# Darwin: Time Machine is \"standard\", we can add others.  The default\n\t\t# mechanism is sticky, as an attribute on the dir, requires no\n\t\t# privileges, is idempotent (and doesn't support -- to end flags).\n\t\ttmutil addexclusion \"$@\"\n\t\t;;\n\tesac\n}\n\n_versint() {\n\tIFS=\" \" read -r -a args <<<\"${1//[^0-9]/ }\"\n\tprintf '1%03d%03d%03d%03d' \"${args[@]}\"\n}\n\n_to_goarch() {\n\tcase \"${1}\" in\n\taarch64) echo \"arm64\" ;;\n\t*) echo \"${1}\" ;;\n\tesac\n}\n\n: \"${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_ARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_HOSTARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}\"\n: \"${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}\"\n: \"${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}\"\n: \"${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}\"\n: \"${GIMME_TYPE:=auto}\" # 'auto', 'binary', 'source', or 'git'\n: \"${GIMME_BINARY_OSX:=osx10.8}\"\n: \"${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}\"\n: \"${GIMME_LIST_KNOWN:=https://golang.org/dl}\"\n: \"${GIMME_KNOWN_CACHE_MAX:=10800}\"\n\n# The version prefix must be an absolute path\ncase \"${GIMME_VERSION_PREFIX}\" in\n/*) true ;;\n*)\n\techo >&2 \" Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX\"\n\tGIMME_VERSION_PREFIX=\"$(pwd)/${GIMME_VERSION_PREFIX}\"\n\techo >&2 \" to: $GIMME_VERSION_PREFIX\"\n\t;;\nesac\n\ncase \"${GIMME_OS}\" in mingw* | msys_nt*)\n\t# Minimalist GNU for Windows\n\tGIMME_OS='windows'\n\n\tif [ \"${GIMME_ARCH}\" = 'i686' ]; then\n\t\tGIMME_ARCH=\"386\"\n\telse\n\t\tGIMME_ARCH=\"amd64\"\n\tfi\n\t;;\nesac\n\nforce_install=0\nforce_known_update=0\n\nwhile [[ $# -gt 0 ]]; do\n\tcase \"${1}\" in\n\t-h | --help | help | wat)\n\t\t_old_ifs=\"$IFS\"\n\t\tIFS=';'\n\t\tawk '/^#\\+  / {\n\t\t\t\tsub(/^#\\+  /, \"\", $0) ;\n\t\t\t\tsub(/-$/, \"\", $0) ;\n\t\t\t\tprint $0\n\t\t\t}' \"$0\" | while read -r line; do\n\t\t\teval \"echo \\\"$line\\\"\"\n\t\tdone\n\t\tIFS=\"$_old_ifs\"\n\t\texit 0\n\t\t;;\n\t-V | --version | version)\n\t\techo \"${GIMME_VERSION}\"\n\t\texit 0\n\t\t;;\n\t-r | --resolve | resolve)\n\t\t# The normal mkdir of versions is below; we don't want to move it up\n\t\t# to where we create files just if asked our version; thus\n\t\t# _resolve_version has to mkdir the versions dir itself.\n\t\tif [[ $# -ge 2 ]]; then\n\t\t\t_resolve_version \"${2}\"\n\t\telif [[ -n \"${GIMME_GO_VERSION:-}\" ]]; then\n\t\t\t_resolve_version \"${GIMME_GO_VERSION}\"\n\t\telse\n\t\t\tdie \"resolve must be given a version to resolve\"\n\t\tfi\n\t\texit $?\n\t\t;;\n\t-l | --list | list)\n\t\t_list_versions\n\t\texit 0\n\t\t;;\n\t-k | --known | known)\n\t\t_list_known\n\t\texit 0\n\t\t;;\n\t-f | --force | force)\n\t\tforce_install=1\n\t\t;;\n\t--force-known-update | force-known-update)\n\t\tforce_known_update=1\n\t\t;;\n\t-i | install)\n\t\ttrue # ignore a dummy argument\n\t\t;;\n\t*)\n\t\tbreak\n\t\t;;\n\tesac\n\tshift\ndone\n\nif [[ -n \"${1}\" ]]; then\n\tGIMME_GO_VERSION=\"${1}\"\nfi\nif [[ -n \"${2}\" ]]; then\n\tGIMME_VERSION_PREFIX=\"${2}\"\nfi\n\ncase \"${GIMME_ARCH}\" in\nx86_64) GIMME_ARCH=amd64 ;;\nx86) GIMME_ARCH=386 ;;\narm64)\n\tif [[ \"${GIMME_GO_VERSION}\" != master && \"$(_versint \"${GIMME_GO_VERSION}\")\" < \"$(_versint 1.5)\" ]]; then\n\t\techo >&2 \"error: ${GIMME_ARCH} is not supported by this go version\"\n\t\techo >&2 \"try go1.5 or newer\"\n\t\texit 1\n\tfi\n\tif [[ \"${GIMME_HOSTOS}\" == \"linux\" && \"${GIMME_HOSTARCH}\" != \"${GIMME_ARCH}\" ]]; then\n\t\t: \"${GIMME_CC_FOR_TARGET:=\"aarch64-linux-gnu-gcc\"}\"\n\tfi\n\t;;\narm*) GIMME_ARCH=arm ;;\nesac\n\ncase \"${GIMME_HOSTARCH}\" in\nx86_64) GIMME_HOSTARCH=amd64 ;;\nx86) GIMME_HOSTARCH=386 ;;\narm64) ;;\narm*) GIMME_HOSTARCH=arm ;;\nesac\n\ncase \"${GIMME_GO_VERSION}\" in\nstable) GIMME_GO_VERSION=$(_get_curr_stable) ;;\noldstable) GIMME_GO_VERSION=$(_get_old_stable) ;;\nesac\n\n_assert_version_given \"$@\"\n\n((force_install)) && _wipe_version \"${GIMME_GO_VERSION}\"\n\nunset GOARCH\nunset GOBIN\nunset GOOS\nunset GOPATH\nunset GOROOT\nunset CGO_ENABLED\nunset CC_FOR_TARGET\n# GO111MODULE breaks build of Go itself\nunset GO111MODULE\n\nmkdir -p \"${GIMME_VERSION_PREFIX}\" \"${GIMME_ENV_PREFIX}\"\n# The envs dir stays small and provides a record of what had been installed\n# whereas the versions dir grows by hundreds of MB per version and is not\n# intended to support local modifications (as that subverts the point of gimme)\n# _and_ is a cache, so we're unilaterally declaring that the contents of\n# the versions dir should be excluded from system backups.\n_exclude_from_backups \"${GIMME_VERSION_PREFIX}\"\n\nGIMME_VERSION_PREFIX=\"$(_realpath \"${GIMME_VERSION_PREFIX}\")\"\nGIMME_ENV_PREFIX=\"$(_realpath \"${GIMME_ENV_PREFIX}\")\"\n\nif ! case \"${GIMME_TYPE}\" in\n\tbinary) _try_existing binary || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" ;;\n\tsource) _try_existing source || _try_source || _try_git ;;\n\tgit) _try_git ;;\n\tauto) _try_existing || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" || _try_source || _try_git ;;\n\t*)\n\t\techo >&2 \"I don't know how to '${GIMME_TYPE}'.\"\n\t\techo >&2 \"  Try 'auto', 'binary', 'source', or 'git'.\"\n\t\texit 1\n\t\t;;\n\tesac; then\n\techo >&2 \"I don't have any idea what to do with '${GIMME_GO_VERSION}'.\"\n\techo >&2 \"  (using download type '${GIMME_TYPE}')\"\n\texit 1\nfi\n"
  },
  {
    "path": "hack/tools/.golangci.yml",
    "content": "version: \"2\"\n\nrun:\n  timeout: 3m\n\nlinters:\n  default: none\n\n  enable:\n    - errcheck\n    - govet\n    - ineffassign\n    - staticcheck\n    - gochecknoinits\n    - revive # replaces golint for now\n    - misspell\n    - unparam\n\n  settings:\n    staticcheck:\n      checks:\n        - all\n    revive:\n      rules:\n        - name: var-naming\n          arguments:\n            - []\n            - []\n            - - skip-package-name-checks: true\n\n  exclusions:\n    presets:\n      - std-error-handling\n      - common-false-positives\n    rules:\n      # this requires renaming all unused parameters, we'd rather leave a preferred\n      # placeholder name in place when implementing an interface etc.\n      # we can revisit this later, right now it's generating a lot of new warnings\n      # after upgrading golangci-lint\n      - linters:\n          - revive\n        text: \"^unused-parameter: .*\"\n      # Not all package docstrings add value, so we do not want to enforce it\n      - linters:\n          - revive\n        text: \"package-comments: should have a package comment\"\n      - linters:\n          - staticcheck\n        text: \"ST1000: at least one file in a package should have a package comment\"\n      - linters:\n          - staticcheck\n        path: pkg/cmd\n        text: \"ST1005: error strings should not be capitalized\"\n      - linters:\n          - staticcheck\n        path: images/kindnetd/cmd\n        text: \"ST1005: error strings should not be capitalized\"\n\nformatters:\n  enable:\n    - gofmt\n\n"
  },
  {
    "path": "hack/tools/README.md",
    "content": "This directory contains a stub go module used to track version of development\ntools like the Kubernetes code generators."
  },
  {
    "path": "hack/tools/boilerplate.go.txt",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n"
  },
  {
    "path": "hack/tools/go.mod",
    "content": "module sigs.k8s.io/kind/hack/tools\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/golangci/golangci-lint/v2 v2.11.3\n\tgotest.tools/gotestsum v1.12.0\n\tk8s.io/code-generator v0.31.0\n)\n\nrequire (\n\t4d63.com/gocheckcompilerdirectives v1.3.0 // indirect\n\t4d63.com/gochecknoglobals v0.2.2 // indirect\n\tcodeberg.org/chavacava/garif v0.2.0 // indirect\n\tcodeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect\n\tdev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect\n\tdev.gaijin.team/go/golib v0.6.0 // indirect\n\tgithub.com/4meepo/tagalign v1.4.3 // indirect\n\tgithub.com/Abirdcfly/dupword v0.1.7 // indirect\n\tgithub.com/AdminBenni/iota-mixing v1.0.0 // indirect\n\tgithub.com/AlwxSin/noinlineerr v1.0.5 // indirect\n\tgithub.com/Antonboom/errname v1.1.1 // indirect\n\tgithub.com/Antonboom/nilnil v1.1.1 // indirect\n\tgithub.com/Antonboom/testifylint v1.6.4 // indirect\n\tgithub.com/BurntSushi/toml v1.6.0 // indirect\n\tgithub.com/Djarvur/go-err113 v0.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/MirrexOne/unqueryvet v1.5.4 // indirect\n\tgithub.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.23.1 // indirect\n\tgithub.com/alecthomas/go-check-sumtype v0.3.1 // indirect\n\tgithub.com/alexkohler/nakedret/v2 v2.0.6 // indirect\n\tgithub.com/alexkohler/prealloc v1.1.0 // indirect\n\tgithub.com/alfatraining/structtag v1.0.0 // indirect\n\tgithub.com/alingse/asasalint v0.0.11 // indirect\n\tgithub.com/alingse/nilnesserr v0.2.0 // indirect\n\tgithub.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect\n\tgithub.com/ashanbrown/makezero/v2 v2.1.0 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bitfield/gotestdox v0.2.2 // indirect\n\tgithub.com/bkielbasa/cyclop v1.2.3 // indirect\n\tgithub.com/blizzy78/varnamelen v0.8.0 // indirect\n\tgithub.com/bombsimon/wsl/v4 v4.7.0 // indirect\n\tgithub.com/bombsimon/wsl/v5 v5.6.0 // indirect\n\tgithub.com/breml/bidichk v0.3.3 // indirect\n\tgithub.com/breml/errchkjson v0.4.1 // indirect\n\tgithub.com/butuzov/ireturn v0.4.0 // indirect\n\tgithub.com/butuzov/mirror v1.3.0 // indirect\n\tgithub.com/catenacyber/perfsprint v0.10.1 // indirect\n\tgithub.com/ccojocar/zxcvbn-go v1.0.4 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/charithe/durationcheck v0.0.11 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect\n\tgithub.com/charmbracelet/lipgloss v1.1.0 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.10.1 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect\n\tgithub.com/charmbracelet/x/term v0.2.1 // indirect\n\tgithub.com/ckaznocha/intrange v0.3.1 // indirect\n\tgithub.com/curioswitch/go-reassign v0.3.0 // indirect\n\tgithub.com/daixiang0/gci v0.13.7 // indirect\n\tgithub.com/dave/dst v0.27.3 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/denis-tingaikin/go-header v0.5.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.5 // indirect\n\tgithub.com/dnephin/pflag v1.0.7 // indirect\n\tgithub.com/ettle/strcase v0.2.0 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/fatih/structtag v1.2.0 // indirect\n\tgithub.com/firefart/nonamedreturns v1.0.6 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/fzipp/gocyclo v0.6.0 // indirect\n\tgithub.com/ghostiam/protogetter v0.3.20 // indirect\n\tgithub.com/go-critic/go-critic v0.14.3 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-toolsmith/astcast v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astcopy v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astequal v1.2.0 // indirect\n\tgithub.com/go-toolsmith/astfmt v1.1.0 // indirect\n\tgithub.com/go-toolsmith/astp v1.1.0 // indirect\n\tgithub.com/go-toolsmith/strparse v1.1.0 // indirect\n\tgithub.com/go-toolsmith/typep v1.1.0 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0 // indirect\n\tgithub.com/go-xmlfmt/xmlfmt v1.1.3 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/godoc-lint/godoc-lint v0.11.2 // indirect\n\tgithub.com/gofrs/flock v0.13.0 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/golangci/asciicheck v0.5.0 // indirect\n\tgithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect\n\tgithub.com/golangci/go-printf-func-name v0.1.1 // indirect\n\tgithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect\n\tgithub.com/golangci/golines v0.15.0 // indirect\n\tgithub.com/golangci/misspell v0.8.0 // indirect\n\tgithub.com/golangci/plugin-module-register v0.1.2 // indirect\n\tgithub.com/golangci/revgrep v0.8.0 // indirect\n\tgithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect\n\tgithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/gordonklaus/ineffassign v0.2.0 // indirect\n\tgithub.com/gostaticanalysis/analysisutil v0.7.1 // indirect\n\tgithub.com/gostaticanalysis/comment v1.5.0 // indirect\n\tgithub.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect\n\tgithub.com/gostaticanalysis/nilerr v0.1.2 // indirect\n\tgithub.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/hexops/gotextdiff v1.0.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jgautheron/goconst v1.8.2 // indirect\n\tgithub.com/jingyugao/rowserrcheck v1.1.1 // indirect\n\tgithub.com/jjti/go-spancheck v0.6.5 // indirect\n\tgithub.com/julz/importas v0.2.0 // indirect\n\tgithub.com/karamaru-alpha/copyloopvar v1.2.2 // indirect\n\tgithub.com/kisielk/errcheck v1.10.0 // indirect\n\tgithub.com/kkHAIKE/contextcheck v1.1.6 // indirect\n\tgithub.com/kulti/thelper v0.7.1 // indirect\n\tgithub.com/kunwardeep/paralleltest v1.0.15 // indirect\n\tgithub.com/lasiar/canonicalheader v1.1.2 // indirect\n\tgithub.com/ldez/exptostd v0.4.5 // indirect\n\tgithub.com/ldez/gomoddirectives v0.8.0 // indirect\n\tgithub.com/ldez/grignotin v0.10.1 // indirect\n\tgithub.com/ldez/structtags v0.6.1 // indirect\n\tgithub.com/ldez/tagliatelle v0.7.2 // indirect\n\tgithub.com/ldez/usetesting v0.5.0 // indirect\n\tgithub.com/leonklingele/grouper v1.1.2 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/macabu/inamedparam v0.2.0 // indirect\n\tgithub.com/magiconair/properties v1.8.6 // indirect\n\tgithub.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect\n\tgithub.com/manuelarte/funcorder v0.5.0 // indirect\n\tgithub.com/maratori/testableexamples v1.0.1 // indirect\n\tgithub.com/maratori/testpackage v1.1.2 // indirect\n\tgithub.com/matoous/godox v1.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect\n\tgithub.com/mgechev/revive v1.15.0 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/moricho/tparallel v0.3.2 // indirect\n\tgithub.com/muesli/termenv v0.16.0 // indirect\n\tgithub.com/nakabonne/nestif v0.3.1 // indirect\n\tgithub.com/nishanths/exhaustive v0.12.0 // indirect\n\tgithub.com/nishanths/predeclared v0.2.2 // indirect\n\tgithub.com/nunnatsa/ginkgolinter v0.23.0 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.12.1 // indirect\n\tgithub.com/prometheus/client_model v0.2.0 // indirect\n\tgithub.com/prometheus/common v0.32.1 // indirect\n\tgithub.com/prometheus/procfs v0.7.3 // indirect\n\tgithub.com/quasilyte/go-ruleguard v0.4.5 // indirect\n\tgithub.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect\n\tgithub.com/quasilyte/gogrep v0.5.0 // indirect\n\tgithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect\n\tgithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect\n\tgithub.com/raeperd/recvcheck v0.2.0 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/rogpeppe/go-internal v1.14.1 // indirect\n\tgithub.com/ryancurrah/gomodguard v1.4.1 // indirect\n\tgithub.com/ryanrolds/sqlclosecheck v0.5.1 // indirect\n\tgithub.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect\n\tgithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect\n\tgithub.com/sashamelentyev/interfacebloat v1.1.0 // indirect\n\tgithub.com/sashamelentyev/usestdlibvars v1.29.0 // indirect\n\tgithub.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08 // indirect\n\tgithub.com/sirupsen/logrus v1.9.4 // indirect\n\tgithub.com/sivchari/containedctx v1.0.3 // indirect\n\tgithub.com/sonatard/noctx v0.5.0 // indirect\n\tgithub.com/sourcegraph/go-diff v0.7.0 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/cobra v1.10.2 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/spf13/viper v1.12.0 // indirect\n\tgithub.com/ssgreg/nlreturn/v2 v2.2.1 // indirect\n\tgithub.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/stretchr/testify v1.11.1 // indirect\n\tgithub.com/subosito/gotenv v1.4.1 // indirect\n\tgithub.com/tetafro/godot v1.5.4 // indirect\n\tgithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect\n\tgithub.com/timonwong/loggercheck v0.11.0 // indirect\n\tgithub.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect\n\tgithub.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect\n\tgithub.com/ultraware/funlen v0.2.0 // indirect\n\tgithub.com/ultraware/whitespace v0.2.0 // indirect\n\tgithub.com/uudashr/gocognit v1.2.1 // indirect\n\tgithub.com/uudashr/iface v1.4.1 // indirect\n\tgithub.com/xen0n/gosmopolitan v1.3.0 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/yagipy/maintidx v1.0.0 // indirect\n\tgithub.com/yeya24/promlinter v0.3.0 // indirect\n\tgithub.com/ykadowak/zerologlint v0.1.5 // indirect\n\tgitlab.com/bosi/decorder v0.4.2 // indirect\n\tgo-simpler.org/musttag v0.14.0 // indirect\n\tgo-simpler.org/sloglint v0.11.1 // indirect\n\tgo.augendre.info/arangolint v0.4.0 // indirect\n\tgo.augendre.info/fatcontext v0.9.0 // indirect\n\tgo.uber.org/multierr v1.10.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/term v0.39.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\thonnef.co/go/tools v0.7.0 // indirect\n\tk8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tmvdan.cc/gofumpt v0.9.2 // indirect\n\tmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect\n)\n"
  },
  {
    "path": "hack/tools/go.sum",
    "content": "4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=\n4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=\n4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=\n4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncodeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=\ncodeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=\ncodeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI=\ncodeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8=\ndev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=\ndev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=\ndev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=\ndev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=\ngithub.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=\ngithub.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ=\ngithub.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4=\ngithub.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=\ngithub.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=\ngithub.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=\ngithub.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=\ngithub.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q=\ngithub.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ=\ngithub.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ=\ngithub.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=\ngithub.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=\ngithub.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=\ngithub.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k=\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/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ=\ngithub.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU=\ngithub.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=\ngithub.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=\ngithub.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=\ngithub.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=\ngithub.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=\ngithub.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=\ngithub.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=\ngithub.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=\ngithub.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M=\ngithub.com/alexkohler/prealloc v1.1.0/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig=\ngithub.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=\ngithub.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=\ngithub.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=\ngithub.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=\ngithub.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w=\ngithub.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg=\ngithub.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo=\ngithub.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c=\ngithub.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE=\ngithub.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE=\ngithub.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY=\ngithub.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=\ngithub.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo=\ngithub.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M=\ngithub.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=\ngithub.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=\ngithub.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=\ngithub.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8=\ngithub.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU=\ngithub.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=\ngithub.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=\ngithub.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=\ngithub.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=\ngithub.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=\ngithub.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=\ngithub.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=\ngithub.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=\ngithub.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=\ngithub.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=\ngithub.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=\ngithub.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=\ngithub.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=\ngithub.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=\ngithub.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=\ngithub.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=\ngithub.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=\ngithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=\ngithub.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=\ngithub.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=\ngithub.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=\ngithub.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=\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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs=\ngithub.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88=\ngithub.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ=\ngithub.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ=\ngithub.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=\ngithub.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=\ngithub.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=\ngithub.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=\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/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=\ngithub.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=\ngithub.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=\ngithub.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk=\ngithub.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE=\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/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=\ngithub.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=\ngithub.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=\ngithub.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=\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/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=\ngithub.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=\ngithub.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=\ngithub.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=\ngithub.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=\ngithub.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=\ngithub.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=\ngithub.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=\ngithub.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=\ngithub.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=\ngithub.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\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-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=\ngithub.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\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-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=\ngithub.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=\ngithub.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=\ngithub.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw=\ngithub.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4=\ngithub.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ=\ngithub.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw=\ngithub.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY=\ngithub.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco=\ngithub.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4=\ngithub.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA=\ngithub.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA=\ngithub.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk=\ngithub.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus=\ngithub.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=\ngithub.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw=\ngithub.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ=\ngithub.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=\ngithub.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=\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/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=\ngithub.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM=\ngithub.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo=\ngithub.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=\ngithub.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=\ngithub.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=\ngithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=\ngithub.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=\ngithub.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=\ngithub.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=\ngithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=\ngithub.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=\ngithub.com/golangci/golangci-lint/v2 v2.11.3 h1:ySX1GtLwlwOEzcLKJifI/aIVesrcHDno+5mrro8rWes=\ngithub.com/golangci/golangci-lint/v2 v2.11.3/go.mod h1:HmDEVZuxz77cNLumPfNNHAFyMX/b7IbA0tpmAbwiVfo=\ngithub.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=\ngithub.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=\ngithub.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg=\ngithub.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg=\ngithub.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg=\ngithub.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=\ngithub.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=\ngithub.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=\ngithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=\ngithub.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=\ngithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=\ngithub.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=\ngithub.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=\ngithub.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=\ngithub.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=\ngithub.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM=\ngithub.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8=\ngithub.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc=\ngithub.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk=\ngithub.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY=\ngithub.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU=\ngithub.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA=\ngithub.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=\ngithub.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8=\ngithub.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs=\ngithub.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo=\ngithub.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=\ngithub.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=\ngithub.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=\ngithub.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=\ngithub.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=\ngithub.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=\ngithub.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=\ngithub.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0=\ngithub.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=\ngithub.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw=\ngithub.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=\ngithub.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98=\ngithub.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs=\ngithub.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w=\ngithub.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk=\ngithub.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4=\ngithub.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=\ngithub.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=\ngithub.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=\ngithub.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk=\ngithub.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q=\ngithub.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=\ngithub.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=\ngithub.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk=\ngithub.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY=\ngithub.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk=\ngithub.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI=\ngithub.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc=\ngithub.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ=\ngithub.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=\ngithub.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=\ngithub.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=\ngithub.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=\ngithub.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=\ngithub.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=\ngithub.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=\ngithub.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=\ngithub.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=\ngithub.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=\ngithub.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=\ngithub.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc=\ngithub.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=\ngithub.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=\ngithub.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=\ngithub.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q=\ngithub.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A=\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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=\ngithub.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=\ngithub.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=\ngithub.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=\ngithub.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=\ngithub.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=\ngithub.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=\ngithub.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8=\ngithub.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4=\ngithub.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=\ngithub.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=\ngithub.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=\ngithub.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=\ngithub.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=\ngithub.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=\ngithub.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=\ngithub.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=\ngithub.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=\ngithub.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=\ngithub.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=\ngithub.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA=\ngithub.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=\ngithub.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY=\ngithub.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=\ngithub.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=\ngithub.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng=\ngithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU=\ngithub.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=\ngithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=\ngithub.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=\ngithub.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=\ngithub.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=\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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=\ngithub.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=\ngithub.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=\ngithub.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=\ngithub.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=\ngithub.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=\ngithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=\ngithub.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=\ngithub.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=\ngithub.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=\ngithub.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=\ngithub.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08 h1:AoLtJX4WUtZkhhUUMFy3GgecAALp/Mb4S1iyQOA2s0U=\ngithub.com/securego/gosec/v2 v2.24.8-0.20260309165252-619ce2117e08/go.mod h1:+XLCJiRE95ga77XInNELh2M6zQP+PdqiT9Zpm0D9Wpk=\ngithub.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=\ngithub.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=\ngithub.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=\ngithub.com/sonatard/noctx v0.5.0 h1:e/jdaqAsuWVOKQ0P6NWiIdDNHmHT5SwuuSfojFjzwrw=\ngithub.com/sonatard/noctx v0.5.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=\ngithub.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=\ngithub.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\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/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=\ngithub.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=\ngithub.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=\ngithub.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=\ngithub.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=\ngithub.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\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/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=\ngithub.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=\ngithub.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA=\ngithub.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0=\ngithub.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag=\ngithub.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=\ngithub.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg=\ngithub.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=\ngithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=\ngithub.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=\ngithub.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=\ngithub.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=\ngithub.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is=\ngithub.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo=\ngithub.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=\ngithub.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=\ngithub.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=\ngithub.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=\ngithub.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=\ngithub.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=\ngithub.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4=\ngithub.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q=\ngithub.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=\ngithub.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=\ngithub.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM=\ngithub.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=\ngithub.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=\ngithub.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=\ngithub.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=\ngithub.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=\ngithub.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=\ngitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8=\ngo-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=\ngo-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=\ngo-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=\ngo-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=\ngo-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=\ngo-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=\ngo.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50=\ngo.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA=\ngo.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE=\ngo.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\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/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=\ngo.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=\ngolang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=\ngolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=\ngolang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=\ngolang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=\ngolang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk=\ngolang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\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.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=\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.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.4.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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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-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.4.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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.18.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.1.0/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.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=\ngolang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\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.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=\ngolang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=\ngolang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=\ngolang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\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=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/gotestsum v1.12.0 h1:CmwtaGDkHxrZm4Ib0Vob89MTfpc3GrEFMJKovliPwGk=\ngotest.tools/gotestsum v1.12.0/go.mod h1:fAvqkSptospfSbQw26CTYzNwnsE/ztqLeyhP0h67ARY=\ngotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=\ngotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=\nhonnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=\nk8s.io/code-generator v0.31.0 h1:w607nrMi1KeDKB3/F/J4lIoOgAwc+gV9ZKew4XRfMp8=\nk8s.io/code-generator v0.31.0/go.mod h1:84y4w3es8rOJOUUP1rLsIiGlO1JuEaPFXQPA9e/K6U0=\nk8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo=\nk8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nmvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4=\nmvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s=\nmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI=\nmvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "hack/tools/tools.go",
    "content": "//go:build tools\n// +build tools\n\n/*\nPackage tools is used to track binary dependencies with go modules\nhttps://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module\n*/\npackage tools\n\nimport (\n\t// linter(s)\n\t_ \"github.com/golangci/golangci-lint/v2/cmd/golangci-lint\"\n\n\t// kubernetes code generators\n\t_ \"k8s.io/code-generator/cmd/deepcopy-gen\"\n\n\t// test runner\n\t_ \"gotest.tools/gotestsum\"\n)\n"
  },
  {
    "path": "images/Makefile.common.in",
    "content": "# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# shared makefile for all images\n\n# get image name from directory we're building\nIMAGE_NAME?=$(notdir $(CURDIR))\n# docker image registry, default to upstream\nREGISTRY?=gcr.io/k8s-staging-kind\n# for appending build-meta like \"_containerd-v1.7.1\"\nTAG_SUFFIX?=\n# tag based on date-sha\nTAG?=$(shell echo \"$$(date +v%Y%m%d)-$$(git log --pretty=format:'%h' -n 1)\")\n# the full image tag\nIMAGE?=$(REGISTRY)/$(IMAGE_NAME):$(TAG)$(TAG_SUFFIX)\n# Go version to use, respected by images that build go binaries\nGO_VERSION=$(shell cat $(CURDIR)/../../.go-version | head -n1)\n\n# build with buildx\nPLATFORMS?=linux/amd64,linux/arm64\nOUTPUT?=\nPROGRESS=auto\nEXTRA_BUILD_OPT?=\nbuild: ensure-buildx\n\tdocker buildx build $(if $(PLATFORMS),--platform=$(PLATFORMS),) $(OUTPUT) --progress=$(PROGRESS) -t ${IMAGE} --pull --build-arg GO_VERSION=$(GO_VERSION) $(EXTRA_BUILD_OPT) .\n\n# push the cross built image\npush: OUTPUT=--push\npush: build\n\n# quick can be used to do a build that will be imported into the local docker \n# for sanity checking before doing a cross build push\n# cross builds cannot be imported locally at the moment\n# https://github.com/docker/buildx/issues/59\nquick: PLATFORMS=\nquick: OUTPUT=--load\nquick: build\n\n# enable buildx\nensure-buildx:\n\t./../../hack/build/init-buildx.sh\n\n.PHONY: push build quick ensure-buildx\n"
  },
  {
    "path": "images/base/Dockerfile",
    "content": "# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# kind node base image\n#\n# For systemd + docker configuration used below, see the following references:\n# https://systemd.io/CONTAINER_INTERFACE/\n\n# start from debian slim, this image is reasonably small as a starting point\n# for a kubernetes node image, it doesn't contain much (anything?) we don't need\n# this stage will install basic files and packages\nARG BASE_IMAGE=debian:trixie-slim\nFROM $BASE_IMAGE AS base\n\n# copy in static files\n# all scripts and directories are 0755 (rwx r-x r-x)\n# all non-scripts are 0644 (rw- r-- r--)\nCOPY --chmod=0755 files/usr/local/bin/* /usr/local/bin/\n\nCOPY --chmod=0644 files/kind/ /kind/\n# COPY only applies to files, not the directory itself, so the permissions are\n# fixed in RUN below with a chmod.\nCOPY --chmod=0755 files/kind/bin/ /kind/bin/\n\nCOPY --chmod=0644 files/LICENSES/* /LICENSES/*\nCOPY --chmod=0644 files/etc/* /etc/\nCOPY --chmod=0644 files/etc/containerd/* /etc/containerd/\nCOPY --chmod=0644 files/etc/default/* /etc/default/\nCOPY --chmod=0644 files/etc/sysctl.d/* /etc/sysctl.d/\nCOPY --chmod=0644 files/etc/systemd/system/* /etc/systemd/system/\nCOPY --chmod=0644 files/etc/systemd/system/kubelet.service.d/* /etc/systemd/system/kubelet.service.d/\n\n# Install dependencies, first from apt, then from release tarballs.\n# NOTE: we use one RUN to minimize layers.\n#\n# The base image already has a basic userspace + apt but we need to install more packages.\n# Packages installed are broken down into (each on a line):\n# - packages needed to run services (systemd)\n# - packages needed for kubernetes components\n# - packages needed for networked backed storage with kubernetes\n# - packages used by kubernetes storage e2e tests\n# - packages needed by the container runtime\n# - misc packages kind uses itself\n# - packages that provide semi-core kubernetes functionality\n# After installing packages we cleanup by:\n# - removing unwanted systemd services\n# - disabling kmsg in journald (these log entries would be confusing)\n#\n# Then we install containerd from our nightly build infrastructure, as this\n# build for multiple architectures and allows us to upgrade to patched releases\n# more quickly.\n#\n# Next we download and extract crictl and CNI plugin binaries from upstream.\n#\n# Next we ensure the /etc/kubernetes/manifests directory exists. Normally\n# a kubeadm debian / rpm package would ensure that this exists but we install\n# freshly built binaries directly when we build the node image.\n#\n# Finally we adjust tempfiles cleanup to be 1 minute after \"boot\" instead of 15m\n# This is plenty after we've done initial setup for a node, but before we are\n# likely to try to export logs etc.\nRUN chmod 755 /kind/bin && \\\n    echo \"Installing Packages ...\" \\\n    && DEBIAN_FRONTEND=noninteractive clean-install \\\n      systemd \\\n      conntrack iptables nftables iproute2 ethtool util-linux mount kmod \\\n      libseccomp2 pigz fuse-overlayfs \\\n      nfs-common open-iscsi \\\n      e2fsprogs \\\n      bash ca-certificates curl jq procps \\\n    && find /lib/systemd/system/sysinit.target.wants/ -name \"systemd-tmpfiles-setup.service\" -delete \\\n    && rm -f /lib/systemd/system/multi-user.target.wants/* \\\n    && rm -f /etc/systemd/system/*.wants/* \\\n    && rm -f /lib/systemd/system/local-fs.target.wants/* \\\n    && rm -f /lib/systemd/system/sockets.target.wants/*udev* \\\n    && rm -f /lib/systemd/system/sockets.target.wants/*initctl* \\\n    && rm -f /lib/systemd/system/basic.target.wants/* \\\n    && echo \"ReadKMsg=no\" >> /etc/systemd/journald.conf \\\n    && ln -s /usr/lib/systemd/systemd /sbin/init\n\n# NOTE: systemd-binfmt.service will register things into binfmt_misc which is kernel-global\nRUN echo \"Enabling / Disabling services ... \" \\\n    && systemctl enable kubelet.service \\\n    && systemctl enable containerd.service \\\n    && systemctl enable undo-mount-hacks.service \\\n    && systemctl mask systemd-binfmt.service\n\nRUN echo \"Ensuring /etc/kubernetes/manifests\" \\\n    && mkdir -p /etc/kubernetes/manifests\n\n# shared stage to setup go version for building binaries\n# NOTE we will be cross-compiling for performance reasons\n# This is also why we start again FROM the same base image but a different\n# platform and only the files needed for building\n# We will copy the built binaries from later stages to the final stage(s)\nFROM --platform=$BUILDPLATFORM $BASE_IMAGE AS go-build\nCOPY --chmod=0755 files/usr/local/bin/* /usr/local/bin/\nCOPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/\nCOPY --chmod=0755 scripts/target-cc /usr/local/bin/\n# tools needed at build-time only\n# first ensure we can install packages for both architectures\nRUN dpkg --add-architecture arm64 && dpkg --add-architecture amd64 \\\n    && clean-install bash ca-certificates curl git make pkg-config \\\n    build-essential crossbuild-essential-amd64 crossbuild-essential-arm64 \\\n    libseccomp-dev:amd64 libseccomp-dev:arm64\n# set by makefile to .go-version\nARG GO_VERSION\nRUN eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest\n\n\n# stage for building containerd\nFROM go-build AS build-containerd\nARG TARGETARCH GO_VERSION\nARG CONTAINERD_VERSION=\"v2.2.1\"\nARG CONTAINERD_CLONE_URL=\"https://github.com/containerd/containerd\"\n# we don't build with optional snapshotters, we never select any of these\n# they're not ideal inside kind anyhow, and we save some disk space\nARG BUILDTAGS=\"no_aufs no_zfs no_btrfs no_devmapper\"\nRUN git clone --filter=tree:0 \"${CONTAINERD_CLONE_URL}\" /containerd \\\n    && cd /containerd \\\n    && git checkout \"${CONTAINERD_VERSION}\" \\\n    && eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \\\n    && make bin/ctr bin/containerd bin/containerd-shim-runc-v2 \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES \\\n        ./cmd/ctr ./cmd/containerd ./cmd/containerd-shim-runc-v2\n\n# stage for building runc\nFROM go-build AS build-runc\nARG TARGETARCH GO_VERSION\nARG RUNC_VERSION=\"v1.3.4\"\nARG RUNC_CLONE_URL=\"https://github.com/opencontainers/runc\"\nRUN git clone --filter=tree:0 \"${RUNC_CLONE_URL}\" /runc \\\n    && cd /runc \\\n    && git checkout \"${RUNC_VERSION}\" \\\n    && eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \\\n    && make runc \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES .\n\n# stage for building crictl\nFROM go-build AS build-crictl\nARG TARGETARCH GO_VERSION\nARG CRI_TOOLS_CLONE_URL=\"https://github.com/kubernetes-sigs/cri-tools\"\nARG CRICTL_VERSION=\"v1.33.0\"\nRUN git clone --filter=tree:0 \"${CRI_TOOLS_CLONE_URL}\" /cri-tools \\\n    && cd /cri-tools \\\n    && git checkout \"${CRICTL_VERSION}\" \\\n    && eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \\\n    && make BUILD_BIN_PATH=./build crictl \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/crictl\n\n# stage for building cni-plugins\nFROM go-build AS build-cni\nARG TARGETARCH GO_VERSION\nARG CNI_PLUGINS_VERSION=\"v1.8.0\"\nARG CNI_PLUGINS_CLONE_URL=\"https://github.com/containernetworking/plugins\"\nRUN git clone --filter=tree:0 \"${CNI_PLUGINS_CLONE_URL}\" /cni-plugins \\\n    && cd /cni-plugins \\\n    && git checkout \"${CNI_PLUGINS_VERSION}\" \\\n    && eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && mkdir ./bin \\\n    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=0 \\\n    && go build -o ./bin/host-local -mod=vendor ./plugins/ipam/host-local \\\n    && go build -o ./bin/loopback -mod=vendor ./plugins/main/loopback \\\n    && go build -o ./bin/ptp -mod=vendor ./plugins/main/ptp \\\n    && go build -o ./bin/portmap -mod=vendor ./plugins/meta/portmap \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES \\\n        ./plugins/ipam/host-local \\\n        ./plugins/main/loopback ./plugins/main/ptp \\\n        ./plugins/meta/portmap\n\n# stage for building containerd-fuse-overlayfs\nFROM go-build AS build-fuse-overlayfs\nARG TARGETARCH GO_VERSION\nARG CONTAINERD_FUSE_OVERLAYFS_VERSION=\"v2.1.7\"\nARG CONTAINERD_FUSE_OVERLAYFS_CLONE_URL=\"https://github.com/containerd/fuse-overlayfs-snapshotter\"\nRUN git clone --filter=tree:0 \"${CONTAINERD_FUSE_OVERLAYFS_CLONE_URL}\" /fuse-overlayfs-snapshotter \\\n    && cd /fuse-overlayfs-snapshotter \\\n    && git checkout \"${CONTAINERD_FUSE_OVERLAYFS_VERSION}\" \\\n    && eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \\\n    && make bin/containerd-fuse-overlayfs-grpc \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/containerd-fuse-overlayfs-grpc\n\n\n# build final image layout from other stages\nFROM base AS build\n# copy over containerd build and install\nCOPY --from=build-containerd /containerd/bin/containerd /usr/local/bin/\nCOPY --from=build-containerd /containerd/bin/ctr /usr/local/bin/\nCOPY --from=build-containerd /containerd/bin/containerd-shim-runc-v2 /usr/local/bin/\nRUN ctr oci spec \\\n        | jq '.hooks.createContainer[.hooks.createContainer| length] |= . + {\"path\": \"/kind/bin/mount-product-files.sh\"}' \\\n        | jq 'del(.process.rlimits)' \\\n        > /etc/containerd/cri-base.json \\\n    && containerd --version\nCOPY --from=build-containerd /_LICENSES/* /LICENSES/\n# copy over runc build and install\nCOPY --from=build-runc /runc/runc /usr/local/sbin/runc\nRUN runc --version\nCOPY --from=build-runc /_LICENSES/* /LICENSES/\n# copy over crictl build and install\nCOPY --from=build-crictl /cri-tools/build/crictl /usr/local/bin/\nCOPY --from=build-crictl /_LICENSES/* /LICENSES/\n# copy over CNI plugins build and install\nRUN  mkdir -p /opt/cni/bin\nCOPY --from=build-cni /cni-plugins/bin/host-local /opt/cni/bin/\nCOPY --from=build-cni /cni-plugins/bin/loopback /opt/cni/bin/\nCOPY --from=build-cni /cni-plugins/bin/ptp /opt/cni/bin/\nCOPY --from=build-cni /cni-plugins/bin/portmap /opt/cni/bin/\nCOPY --from=build-cni /_LICENSES/* /LICENSES/\n# copy over containerd-fuse-overlayfs and install\nCOPY --from=build-fuse-overlayfs /fuse-overlayfs-snapshotter/bin/containerd-fuse-overlayfs-grpc /usr/local/bin/\nCOPY --from=build-fuse-overlayfs /_LICENSES/* /LICENSES/\n\n# squash down to one compressed layer, without any lingering whiteout files etc\nFROM scratch\nCOPY --from=build / /\n# add metadata, must be done after the squashing\n# first tell systemd that it is in docker (it will check for the container env)\n# https://systemd.io/CONTAINER_INTERFACE/\nENV container=docker\n# systemd exits on SIGRTMIN+3, not SIGTERM (which re-executes it)\n# https://bugzilla.redhat.com/show_bug.cgi?id=1201657\nSTOPSIGNAL SIGRTMIN+3\n# NOTE: this is *only* for documentation, the entrypoint is overridden later\nENTRYPOINT [ \"/usr/local/bin/entrypoint\", \"/sbin/init\" ]\n"
  },
  {
    "path": "images/base/Makefile",
    "content": "# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ninclude $(CURDIR)/../Makefile.common.in\n"
  },
  {
    "path": "images/base/README.md",
    "content": "<!--TODO(bentheelder): fill this in much more thoroughly-->\n# images/base\n\nThis directory contains sources for building the `kind` base \"node\" image.\n\nThe image can be built with `make quick`.\n\n## Maintenance\n\nThis image needs to do a number of unusual things to support running systemd,\nnested containers, and Kubernetes. All of what we do and why we do it\nis documented inline in the [Dockerfile](./Dockerfile).\n\nIf you make any changes to this image, please continue to document exactly\nwhy we do what we do, citing upstream documentation where possible.\n\nSee also [`pkg/cluster`](./../../pkg/cluster) for logic that interacts with this image.\n\n\n## Alternate Sources\n\nKind frequently picks up new releases of dependent projects including\ncontainerd, runc, cni, and crictl. If you choose to use the provided Dockerfile\nbut use build arguments to specify a different base image or application version\nfor dependencies, be aware that you may possibly encounter bugs and undesired\nbehavior.\n\n## Design\n\nSee [base-image](https://kind.sigs.k8s.io/docs/design/base-image/) for more design details.\n"
  },
  {
    "path": "images/base/cloudbuild.yaml",
    "content": "# See https://cloud.google.com/cloud-build/docs/build-config\noptions:\n  substitution_option: ALLOW_LOOSE\n  machineType: E2_HIGHCPU_32\nsteps:\n- name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:latest\n  entrypoint: make\n  args: ['-C', 'images/base', 'push']\n"
  },
  {
    "path": "images/base/files/LICENSES/README.txt",
    "content": "This directory contains license files and notices from binaries built for this\nimage and the dependencies of those binaries,\nas collected by https://github.com/google/go-licenses.\n"
  },
  {
    "path": "images/base/files/etc/containerd/config.toml",
    "content": "# explicitly use v2 config format\nversion = 2\n\n[proxy_plugins]\n# fuse-overlayfs is used for rootless\n[proxy_plugins.\"fuse-overlayfs\"]\n  type = \"snapshot\"\n  address = \"/run/containerd-fuse-overlayfs.sock\"\n\n[plugins.\"io.containerd.grpc.v1.cri\".containerd]\n  # save disk space when using a single snapshotter\n  discard_unpacked_layers = true\n  # explicitly use default snapshotter so we can sed it in entrypoint\n  snapshotter = \"overlayfs\"\n  # explicit default here, as we're configuring it below\n  default_runtime_name = \"runc\"\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc]\n  # set default runtime handler to v2, which has a per-pod shim\n  runtime_type = \"io.containerd.runc.v2\"\n  # Generated by \"ctr oci spec\" and modified at base container to mount poduct_uuid\n  base_runtime_spec = \"/etc/containerd/cri-base.json\"\n  [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n    # use systemd cgroup by default\n    SystemdCgroup = true\n\n# Setup a runtime with the magic name (\"test-handler\") used for Kubernetes\n# runtime class tests ...\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.test-handler]\n  # same settings as runc\n  runtime_type = \"io.containerd.runc.v2\"\n  base_runtime_spec = \"/etc/containerd/cri-base.json\"\n  [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.test-handler.options]\n    SystemdCgroup = true\n\n[plugins.\"io.containerd.grpc.v1.cri\"]\n  # use fixed sandbox image\n  sandbox_image = \"registry.k8s.io/pause:3.10\"\n  # allow hugepages controller to be missing\n  # see https://github.com/containerd/cri/pull/1501\n  tolerate_missing_hugepages_controller = true\n  # restrict_oom_score_adj needs to be true when running inside UserNS (rootless)\n  restrict_oom_score_adj = false\n"
  },
  {
    "path": "images/base/files/etc/crictl.yaml",
    "content": "runtime-endpoint: unix:///run/containerd/containerd.sock"
  },
  {
    "path": "images/base/files/etc/default/kubelet",
    "content": "KUBELET_EXTRA_ARGS=--runtime-cgroups=/system.slice/containerd.service"
  },
  {
    "path": "images/base/files/etc/sysctl.d/10-network-magic.conf",
    "content": "# Do not consider loopback addresses as martian source or destination while routing\n# - Docker with custom networks uses an embedded DNS server with address 172.0.0.11\n# - Kubernetes pods mount the node resolv.conf, so they can't use a loopack address\n# that is only reachable from the node.\n#\n# KIND rewrites the well-known docker DNS address 127.0.0.11 by a non-loopback address.\n# The DNS traffic coming from a pod will have to route to a localhost address to be NATed,\n# hence we have to enable route_localnet or pods DNS will not work.\n# Kubernetes mitigates the possible security issue caused by enabling this option.\n# ref: https://nvd.nist.gov/vuln/detail/CVE-2020-8558\nnet.ipv4.conf.all.route_localnet=1\n\n# The global kernel parameter net.ipv4.conf.all.arp_ignore governs the\n# conditions under which ARP requests will be accepted or ignored. This global\n# setting will override any individual interface settings. Some host systems\n# might set this global parameter to a more restrictive setting of 2 (or\n# greater). Specifically, in mode 2, the system ignores ARP requests directed to\n# /32 addresses (this is what kindnet assigns to its veth interfaces) because\n# the request originates from a different subnet. This behavior breaks routing\n# for kindnet (specifically the ptp plugin), so we opinionatedly set this to 0\n# when kindnet is in use to ensure proper networking.\n#\n# For more information see:\n# https://www.kernel.org/doc/html/latest/networking/ip-sysctl.html#arp_ignore\nnet.ipv4.conf.all.arp_ignore=0\n"
  },
  {
    "path": "images/base/files/etc/sysctl.d/10-network-security.conf",
    "content": "# Turn on Source Address Verification in all interfaces to\n# prevent some spoofing attacks.\nnet.ipv4.conf.default.rp_filter=1\nnet.ipv4.conf.all.rp_filter=1\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/containerd-fuse-overlayfs.service",
    "content": "[Unit]\nDescription=containerd fuse-overlayfs snapshotter\nPartOf=containerd.service\n\n[Service]\nExecStart=/usr/local/bin/containerd-fuse-overlayfs-grpc /run/containerd-fuse-overlayfs.sock /var/lib/containerd-fuse-overlayfs\nType=notify\nRestart=always\nRestartSec=1\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/containerd.service",
    "content": "# derived containerd systemd service file from the official:\n# https://github.com/containerd/containerd/blob/master/containerd.service\n[Unit]\nDescription=containerd container runtime\nDocumentation=https://containerd.io\nAfter=network.target local-fs.target\n# disable rate limiting\nStartLimitIntervalSec=0\n\n[Service]\nExecStartPre=-/sbin/modprobe overlay\nExecStart=/usr/local/bin/containerd\n\nType=notify\nDelegate=yes\nKillMode=process\nRestart=always\nRestartSec=1\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\nTasksMax=infinity\nOOMScoreAdjust=-999\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/kubelet.service",
    "content": "# slightly modified from:\n# https://github.com/kubernetes/kubernetes/blob/ba8fcafaf8c502a454acd86b728c857932555315/build/debs/kubelet.service\n[Unit]\nDescription=kubelet: The Kubernetes Node Agent\nDocumentation=http://kubernetes.io/docs/\n# NOTE: kind deviates from upstream here to avoid crashlooping\n# This does *not* support altering the kubelet config path though.\n# We intend to upstream this change but first need to solve the upstream\n# Packaging problem (all kubernetes versions use the same files out of tree).\nConditionPathExists=/var/lib/kubelet/config.yaml\n\n[Service]\nExecStart=/usr/bin/kubelet\nRestart=always\nStartLimitInterval=0\n# NOTE: kind deviates from upstream here with a lower RestartSec\nRestartSec=1s\n# And by adding the [Service] lines below\nCPUAccounting=true\nMemoryAccounting=true\nSlice=kubelet.slice\nKillMode=process\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/kubelet.service.d/10-kubeadm.conf",
    "content": "# https://github.com/kubernetes/kubernetes/blob/ba8fcafaf8c502a454acd86b728c857932555315/build/debs/10-kubeadm.conf\n# Note: This dropin only works with kubeadm and kubelet v1.11+\n[Service]\nEnvironment=\"KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf\"\nEnvironment=\"KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml\"\n# This is a file that \"kubeadm init\" and \"kubeadm join\" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically\nEnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env\n# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use\n# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.\nEnvironmentFile=-/etc/default/kubelet\nExecStart=\nExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/kubelet.service.d/11-kind.conf",
    "content": "# kind specific additions go in this file\n[Service]\n# On cgroup v1, the /kubelet cgroup is created in the entrypoint script before running systemd.\n# On cgroup v2, the /kubelet cgroup is created here. (See the comments in the entrypoint script for the reason.)\nExecStartPre=/bin/sh -euc \"if [ -f /sys/fs/cgroup/cgroup.controllers ]; then /kind/bin/create-kubelet-cgroup-v2.sh; fi\"\n# on WSL2 (and potentially other distros without systemd) /sys/fs/cgroup/systemd is created after the entrypoint, during /sbin/init.\n# This eventually leads to kubelet failing to start, see: https://github.com/kubernetes-sigs/kind/issues/2323\nExecStartPre=/bin/sh -euc \"if [ ! -f /sys/fs/cgroup/cgroup.controllers ] && [ ! -d /sys/fs/cgroup/systemd/kubelet ]; then mkdir -p /sys/fs/cgroup/systemd/kubelet; fi\"\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/kubelet.slice",
    "content": "[Unit]\nDescription=slice used to run Kubernetes / Kubelet\nBefore=slices.target\n\n[Slice]\nMemoryAccounting=true\nCPUAccounting=true\n"
  },
  {
    "path": "images/base/files/etc/systemd/system/undo-mount-hacks.service",
    "content": "[Unit]\nDescription=Undo KIND mount hacks\nAfter=slices.target\nBefore=containerd.service kubelet.service\n\n[Service]\nType=oneshot\nExecStart=/kind/bin/undo-mount-hacks.sh\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "images/base/files/kind/README.txt",
    "content": "This directory is reserved for KIND [^1] internal purposes.\n\nModifying or depending on the contents of this directory is NOT supported.\n\nHere be dragons.\n\n[^1]: https://kind.sigs.k8s.io/\n"
  },
  {
    "path": "images/base/files/kind/bin/create-kubelet-cgroup-v2.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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\nset -o nounset\nset -o pipefail\nif [[ ! -f \"/sys/fs/cgroup/cgroup.controllers\" ]]; then\n\techo 'ERROR: this script should not be called on cgroup v1 hosts' >&2\n\texit 1\nfi\n\n# NOTE: we can't use `test -s` because cgroup.procs is not a regular file.\nif grep -qv '^0$' /sys/fs/cgroup/cgroup.procs ; then\n\techo 'ERROR: this script needs /sys/fs/cgroup/cgroup.procs to be empty (for writing the top-level cgroup.subtree_control)' >&2\n\t# So, this script needs to be called after launching systemd.\n\t# This script cannot be called from /usr/local/bin/entrypoint.\n\texit 1\nfi\n\nensure_subtree_control() {\n\tlocal group=$1\n\t# When cgroup.controllers is like \"cpu cpuset memory io pids\",\n\t# cgroup.subtree_control is written with \"+cpu +cpuset +memory +io +pids\" .\n\tsed -e 's/ / +/g' -e 's/^/+/' <\"/sys/fs/cgroup/$group/cgroup.controllers\" >\"/sys/fs/cgroup/$group/cgroup.subtree_control\"\n}\n\n# kubelet requires all the controllers (including hugetlb) in /sys/fs/cgroup/cgroup.controllers to be available in\n# /sys/fs/cgroup/kubelet/cgroup.subtree_control.\n#\n# We need to update the top-level cgroup.subtree_controllers as well, because hugetlb is not present in the file by default.\nensure_subtree_control /\nmkdir -p /sys/fs/cgroup/kubelet\nensure_subtree_control /kubelet\n# again for kubelet.slice for systemd cgroup driver\nmkdir -p /sys/fs/cgroup/kubelet.slice\nensure_subtree_control /kubelet.slice\n"
  },
  {
    "path": "images/base/files/kind/bin/mount-product-files.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 a createContainer hook [1] that replicates the functionality from entrypoint script to mount product_name and product_uuid but from a product_name and product_uuid copied into the contianer rootfs to prevent all the containers from bind mounting the same file. Sharing the same bind mount between all the containers increases the latency accessing the container, preventing it from accessing in some cases.\n#\n# [1] https://github.com/opencontainers/runtime-spec/blob/master/config.md#createcontainer-hooks\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# Explicitly set PATH so as not to be inherited from the container\n# We have no reason to be using any binary from the container, and the\n# container PATH may lack normal \"host\" (kind node container in this case)\n# system paths.\n#\n# See: https://github.com/kubernetes-sigs/kind/issues/2551\n#\n# All of the binaries this script needs are in /usr/bin currently, but this is\n# the full normal PATH for this image with pretty standard linux paths.\nexport PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\n\n# The bundle represents the dir path to container filesystem, container runtime state [1] is\n# passed to the hook's stdin\n#\n# [1] https://github.com/opencontainers/runtime-spec/blob/master/runtime.md#state\n#\nbundle=$(jq -r .bundle)\n\ncp /kind/product_* \"${bundle:?}/rootfs/\"\nif [[ -f /sys/class/dmi/id/product_name ]]; then\n  mount -o ro,bind \"${bundle:?}\"/rootfs/product_name \"${bundle:?}\"/rootfs/sys/class/dmi/id/product_name\nfi\n\nif [[ -f /sys/class/dmi/id/product_uuid ]]; then\n  mount -o ro,bind \"${bundle:?}\"/rootfs/product_uuid \"${bundle:?}\"/rootfs/sys/class/dmi/id/product_uuid\nfi\n\nif [[ -f /sys/devices/virtual/dmi/id/product_uuid ]]; then\n  mount -o ro,bind \"${bundle:?}\"/rootfs/product_uuid \"${bundle:?}\"/rootfs/sys/devices/virtual/dmi/id/product_uuid\nfi\n"
  },
  {
    "path": "images/base/files/kind/bin/undo-mount-hacks.sh",
    "content": "#!/bin/bash\n\n# Copyright 2023 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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\nset -o nounset\nset -o pipefail\n\n# don't run on cgroups v2\nif [[ -f \"/sys/fs/cgroup/cgroup.controllers\" ]]; then\n    exit 0\nfi\n\n# on cgroups v1 we want to undo bind mounting over this to hide misc\n# see entrypoint\numount /proc/cgroups || true\n"
  },
  {
    "path": "images/base/files/usr/local/bin/clean-install",
    "content": "#!/bin/sh\n\n# Copyright 2017 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# A script encapsulating a common Dockerimage pattern for installing packages\n# and then cleaning up the unnecessary install artifacts.\n# e.g. clean-install iptables ebtables conntrack\n\nset -o errexit\n\nif [ $# = 0 ]; then\n  echo >&2 \"No packages specified\"\n  exit 1\nfi\n\napt-get update\napt-get upgrade -y\napt-get install -y --no-install-recommends \"$@\"\napt-get clean -y\nrm -rf \\\n   /var/cache/debconf/* \\\n   /var/lib/apt/lists/* \\\n   /var/log/* \\\n   /tmp/* \\\n   /var/tmp/* \\\n   /usr/share/doc/* \\\n   /usr/share/doc-base/* \\\n   /usr/share/man/* \\\n   /usr/share/local/*\n"
  },
  {
    "path": "images/base/files/usr/local/bin/entrypoint",
    "content": "#!/bin/bash\n\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS 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\nset -o nounset\nset -o pipefail\n\n# logging helpers\nlog_info() {\n  echo \"INFO: $1\" >&2\n}\nlog_warn() {\n  echo \"WARN: $1\" >&2\n}\nlog_error() {\n  echo \"ERROR: $1\" >&2\n}\n\n# If /proc/self/uid_map 4294967295 mappings, we are in the initial user namespace, i.e. the host.\n# Otherwise we are in a non-initial user namespace.\n# https://github.com/opencontainers/runc/blob/v1.0.0-rc92/libcontainer/system/linux.go#L109-L118\nuserns=\"\"\nif grep -Eqv \"0[[:space:]]+0[[:space:]]+4294967295\" /proc/self/uid_map; then\n  userns=\"1\"\n  log_info 'running in a user namespace (experimental)'\nfi\n\ngrep_allow_nomatch() {\n  # grep exits 0 on match, 1 on no match, 2 on error\n  grep \"$@\" || [[ $? == 1 ]]\n}\n\n# regex_escape_ip converts IP address string $1 to a regex-escaped literal\nregex_escape_ip(){\n  sed -e 's#\\.#\\\\.#g' -e 's#\\[#\\\\[#g' -e 's#\\]#\\\\]#g' <<<\"$1\"\n}\n\nvalidate_userns() {\n  if [[ -z \"${userns}\" ]]; then\n    return\n  fi\n\n  local nofile_hard\n  nofile_hard=\"$(ulimit -Hn)\"\n  local nofile_hard_expected=\"64000\"\n  if [[ \"${nofile_hard}\" -lt \"${nofile_hard_expected}\" ]]; then\n    log_warn \"UserNS: expected RLIMIT_NOFILE to be at least ${nofile_hard_expected}, got ${nofile_hard}\"\n  fi\n\n  if [[ -f \"/sys/fs/cgroup/cgroup.controllers\" ]]; then\n    for f in cpu memory pids; do\n      if ! grep -qw $f /sys/fs/cgroup/cgroup.controllers; then\n        log_error \"UserNS: $f controller needs to be delegated\"\n        exit 1\n      fi\n    done\n  fi\n}\n\noverlayfs_preferrable() {\n  if [[ -z \"$userns\" ]]; then\n    # If we are outside userns, we can always assume overlayfs is preferrable\n    return 0\n  fi\n\n  # Debian 10 and 11 supports overlayfs in userns with a \"permit_mount_in_userns\" kernel patch,\n  # but known to be unstable, so we avoid using it https://github.com/moby/moby/issues/42302\n  if [[ -e \"/sys/module/overlay/parameters/permit_mounts_in_userns\" ]]; then\n    log_info \"UserNS: kernel seems supporting overlayfs with permit_mounts_in_userns, but avoiding due to instability.\"\n    return 1\n  fi\n\n  # Check overlayfs availability, by attempting to mount it.\n  #\n  # Overlayfs inside userns is known to be available for the following environments:\n  # - Kernel >= 5.11 (but 5.11 and 5.12 have issues on SELinux hosts. Fixed in 5.13.)\n  # - Ubuntu kernel\n  # - Debian kernel (but avoided due to instability, see the /sys/module/overlay/... check above)\n  # - Sysbox\n  tmp=$(mktemp -d)\n  mkdir -p \"${tmp}/l\" \"${tmp}/u\" \"${tmp}/w\" \"${tmp}/m\"\n  if ! mount -t overlay -o lowerdir=\"${tmp}/l,upperdir=${tmp}/u,workdir=${tmp}/w\" overlay \"${tmp}/m\"; then\n    log_info \"UserNS: kernel does not seem to support overlayfs.\"\n    rm -rf \"${tmp}\"\n    return 1\n  fi\n  umount \"${tmp}/m\"\n  rm -rf \"${tmp}\"\n\n  # Detect whether SELinux is Enforcing (or Permitted) by grepping /proc/self/attr/current .\n  # Note that we cannot use `getenforce` command here because /sys/fs/selinux is typically not mounted for containers.\n  if grep -q \"_t:\" \"/proc/self/attr/current\"; then\n    # When the kernel is before v5.13 and SELinux is enforced, fuse-overlayfs might be safer, so we print a warning (but not an error).\n    # https://github.com/torvalds/linux/commit/7fa2e79a6bb924fa4b2de5766dab31f0f47b5ab6\n    log_warn \"UserNS: SELinux might be Enforcing. If you see an error related to overlayfs, try setting \\`KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER=fuse-overlayfs\\` .\"\n  fi\n  return 0\n}\n\nconfigure_containerd() {\n  local snapshotter=${KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER:-}\n\n  # if we have not already overridden the snapshotter, attempt to auto select\n  if [[ -z \"$snapshotter\" ]]; then\n    # we need to switch to 'native' or 'fuse-overlayfs' on zfs\n    container_filesystem=\"$(stat -f -c %T /kind)\"\n    if [[ \"$container_filesystem\" == 'zfs' ]]; then\n      # we do not use the ZFS snapshotter because of skew issues vs the host\n      snapshotter=\"native\"\n    # fuse should imply fuse-overlayfs, we should switch to fuse-overlayfs (or native)\n    elif [[ \"$container_filesystem\" == 'fuseblk' ]]; then\n      snapshotter=\"fuse-overlayfs\"\n    # Otherwise use fuse-overlayfs if overlayfs is not preferrable\n    # https://github.com/kubernetes-sigs/kind/issues/2275\n    elif [[ -n \"$userns\" ]] && ! overlayfs_preferrable; then\n      snapshotter=\"fuse-overlayfs\"\n    fi\n  fi\n\n  # handle userns (rootless)\n  if [[ -n \"$userns\" ]]; then\n    # enable restrict_oom_score_adj\n    sed -i 's/restrict_oom_score_adj = false/restrict_oom_score_adj = true/' /etc/containerd/config.toml\n  fi\n\n  # if we've overridden or auto-selected the snapshotter vs the default, update containerd\n  if [[ -n \"$snapshotter\" ]]; then\n    log_info \"changing snapshotter from \\\"overlayfs\\\" to \\\"$snapshotter\\\"\"\n    sed -i \"s/snapshotter = \\\"overlayfs\\\"/snapshotter = \\\"$snapshotter\\\"/\" /etc/containerd/config.toml\n    if [[ \"$snapshotter\" = \"fuse-overlayfs\" ]]; then\n      log_info 'enabling containerd-fuse-overlayfs service'\n      systemctl enable containerd-fuse-overlayfs\n    fi\n  fi\n}\n\nconfigure_proxy() {\n  # ensure all processes receive the proxy settings by default\n  # https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html\n  mkdir -p /etc/systemd/system.conf.d/\n  cat <<EOF >/etc/systemd/system.conf.d/proxy-default-environment.conf\n[Manager]\nDefaultEnvironment=\"HTTP_PROXY=${HTTP_PROXY:-}\" \"HTTPS_PROXY=${HTTPS_PROXY:-}\" \"NO_PROXY=${NO_PROXY:-}\"\nEOF\n}\n\nfix_mount() {\n  log_info 'ensuring we can execute mount/umount even with userns-remap'\n  # necessary only when userns-remap is enabled on the host, but harmless\n  # The binary /bin/mount should be owned by root and have the setuid bit\n  chown root:root \"$(which mount)\" \"$(which umount)\"\n  chmod -s \"$(which mount)\" \"$(which umount)\"\n\n  # This is a workaround to an AUFS bug that might cause `Text file\n  # busy` on `mount` command below. See more details in\n  # https://github.com/moby/moby/issues/9547\n  if [[ \"$(stat -f -c %T \"$(which mount)\")\" == 'aufs' ]]; then\n    log_info 'detected aufs, calling sync'\n    sync\n  fi\n\n  log_info 'remounting /sys read-only'\n  # systemd-in-a-container should have read only /sys\n  # https://systemd.io/CONTAINER_INTERFACE/\n  # however, we need other things from `docker run --privileged` ...\n  # and this flag also happens to make /sys rw, amongst other things\n  #\n  # This step is ignored when running inside UserNS, because it fails with EACCES.\n  if ! mount -o remount,ro /sys; then\n    if [[ -n \"$userns\" ]]; then\n      log_info 'UserNS: ignoring mount fail'\n    else\n      exit 1\n    fi\n  fi\n\n  log_info 'making mounts shared'\n  # for mount propagation\n  mount --make-rshared /\n}\n\n# helper used by mount_kubelet_cgroup_root\nmount_kubelet_cgroup_root_subsystem() {\n  local cgroup_root=$1\n  local subsystem=$2\n  if [ -z \"${cgroup_root}\" ]; then\n    return 0\n  fi\n  mkdir -p \"${subsystem}/${cgroup_root}\"\n  if [ \"${subsystem}\" == \"/sys/fs/cgroup/cpuset\" ]; then\n    # This is needed. Otherwise, assigning process to the cgroup\n    # (or any nested cgroup) would result in ENOSPC.\n    cat \"${subsystem}/cpuset.cpus\" > \"${subsystem}/${cgroup_root}/cpuset.cpus\"\n    cat \"${subsystem}/cpuset.mems\" > \"${subsystem}/${cgroup_root}/cpuset.mems\"\n  fi\n  # We need to perform a self bind mount here because otherwise,\n  # systemd might delete the cgroup unintentionally before the\n  # kubelet starts.\n  mount --bind \"${subsystem}/${cgroup_root}\" \"${subsystem}/${cgroup_root}\"\n}\n\n# helper used by fix_cgroup\nmount_kubelet_cgroup_root() {\n  local cgroup_subsystems=$1\n  echo \"${cgroup_subsystems}\" |\n  while IFS= read -r subsystem; do\n    mount_kubelet_cgroup_root_subsystem /kubelet \"${subsystem}\"\n    mount_kubelet_cgroup_root_subsystem /kubelet.slice \"${subsystem}\"\n  done\n  # workaround for hosts not running systemd\n  # we only do this for kubelet.slice because it's not relevant when not using\n  # the systemd cgroup driver\n  if [[ ! \"${cgroup_subsystems}\" = */sys/fs/cgroup/systemd* ]]; then\n    mount_kubelet_cgroup_root_subsystem /kubelet.slice /sys/fs/cgroup/systemd\n  fi\n}\n\n# helper for cgroups v1, to eliminate the misc cgroup\n# see: https://github.com/kubernetes-sigs/kind/issues/3223\n# basically: this cgroup is not very useful for us, and until recently wasn't\n# supported by runc on cgroups v1 anyhow.\n# on cgroupsv2 we can leave it, but on v1 the mismatch in support from container\n# nesting causes problems\nremove_misc_controller() {\n  local misc_controller='/sys/fs/cgroup/misc'\n  if [[ ! -d $misc_controller ]]; then\n    return 0\n  fi\n  log_info \"removing misc controller\"\n  umount $misc_controller || log_warn \"failed umount $misc_controller\"\n  rmdir $misc_controller || log_warn \"failed rmdir $misc_controller\"\n  # systemd will discover misc controller is available here and re-mount it\n  # we will pretend it isn't available with a bind mount\n  # this mount will be removed by undo-mount-hacks.service\n  grep -v 'misc' /proc/cgroups >/kind/fake-cgroups\n  mount --bind /kind/fake-cgroups /proc/cgroups\n}\n\nfix_cgroup() {\n  if [[ -f \"/sys/fs/cgroup/cgroup.controllers\" ]]; then\n    log_info 'detected cgroup v2'\n    # Both Docker and Podman enable CgroupNS on cgroup v2 hosts by default.\n    #\n    # So mostly we do not need to mess around with the cgroup path stuff,\n    # however, we still need to create the \"/kubelet\" cgroup at least.\n    # (Otherwise kubelet fails with `cgroup-root [\"kubelet\"] doesn't exist` error, see #1969)\n    #\n    # The \"/kubelet\" cgroup is created in ExecStartPre of the kubeadm service.\n    #\n    # [FAQ: Why not create \"/kubelet\" cgroup here?]\n    # We can't create the cgroup with controllers here, because /sys/fs/cgroup/cgroup.subtree_control is empty.\n    # And yet we can't write controllers to /sys/fs/cgroup/cgroup.subtree_control by ourselves either, because\n    # /sys/fs/cgroup/cgroup.procs is not empty at this moment.\n    #\n    # After switching from this entrypoint script to systemd, systemd evacuates the processes in the root\n    # group to \"/init.scope\" group, so we can write the root subtree_control and create \"/kubelet\" cgroup.\n    return\n  fi\n  log_info 'detected cgroup v1'\n  # We're looking for the cgroup-path for the cpu controller for the\n  # current process. this tells us what cgroup-path the container is in.\n  local current_cgroup\n  current_cgroup=$(grep -E '^[^:]*:([^:]*,)?cpu(,[^,:]*)?:.*' /proc/self/cgroup | cut -d: -f3)\n  if [ \"$current_cgroup\" = \"/\" ]; then\n    log_info 'detected cgroupns'\n    # we don't need or want the misc controller, see comments on this function\n    remove_misc_controller\n    # kubelet will try to manage cgroups / pods that are not owned by it when\n    # \"nesting\" clusters, unless we instruct it to use a different cgroup root.\n    # We do this, and when doing so we must fixup this alternative root\n    # currently this is hardcoded to be /kubelet\n    # under systemd cgroup driver, kubelet appends .slice\n    local cgroup_subsystems\n    cgroup_subsystems=$(findmnt -lun -o source,target -t cgroup | grep -F \"${current_cgroup}\" | awk '{print $2}')\n    mount --make-rprivate /sys/fs/cgroup\n    mount_kubelet_cgroup_root \"${cgroup_subsystems}\"\n    return\n  fi\n\n  # NOTE The rest of this function deals with the unfortunate situation of\n  # cgroup v1 with no cgroupns enabled. One fine day every user will have\n  # cgroupns enabled (or switch or cgroup v2 which has it enabled by default).\n  # Once that happens, this function can be removed completely.\n\n  log_warn 'cgroupns not enabled! Please use cgroup v2, or cgroup v1 with cgroupns enabled.'\n\n  # See: https://d2iq.com/blog/running-kind-inside-a-kubernetes-cluster-for-continuous-integration\n  # Capture initial state before modifying\n  #\n  # Then we collect the subsystems that are active on our current process.\n  # We assume the cpu controller is in use on all node containers,\n  # and other controllers use the same sub-path.\n  #\n  # See: https://man7.org/linux/man-pages/man7/cgroups.7.html\n  log_info 'fixing cgroup mounts for all subsystems'\n  local cgroup_subsystems\n  cgroup_subsystems=$(findmnt -lun -o source,target -t cgroup | grep -F \"${current_cgroup}\" | awk '{print $2}')\n  # Unmount the cgroup subsystems that are not known to runtime used to\n  # run the container we are in. Those subsystems are not properly scoped\n  # (i.e. the root cgroup is exposed, rather than something like docker/xxxx).\n  # In case a runtime (which is aware of more subsystems -- such as rdma,\n  # misc, or unified) is used inside the container, it may create cgroups for\n  # these subsystems, and as they are not scoped, they will leak to the host\n  # and thus will become non-removable.\n  #\n  # See https://github.com/kubernetes/kubernetes/issues/109182\n  local unsupported_cgroups\n  unsupported_cgroups=$(findmnt -lun -o source,target -t cgroup | grep_allow_nomatch -v -F \"${current_cgroup}\" | awk '{print $2}')\n  if [ -n \"$unsupported_cgroups\" ]; then\n    local mnt\n    echo \"$unsupported_cgroups\" |\n    while IFS= read -r mnt; do\n      log_info \"unmounting and removing $mnt\"\n      umount \"$mnt\" || log_warn \"failed to unmount $mnt\"\n      rmdir \"$mnt\" || log_warn \"failed to rmdir $mnt\"\n    done\n  fi\n  # always remove misc on v1, see comments on this function\n  remove_misc_controller\n\n  # For each cgroup subsystem, Docker does a bind mount from the current\n  # cgroup to the root of the cgroup subsystem. For instance:\n  #   /sys/fs/cgroup/memory/docker/<cid> -> /sys/fs/cgroup/memory\n  #\n  # This will confuse Kubelet and cadvisor and will dump the following error\n  # messages in kubelet log:\n  #   `summary_sys_containers.go:47] Failed to get system container stats for \".../kubelet.service\"`\n  #\n  # This is because `/proc/<pid>/cgroup` is not affected by the bind mount.\n  # The following is a workaround to recreate the original cgroup\n  # environment by doing another bind mount for each subsystem.\n  local cgroup_mounts\n  # xref: https://github.com/kubernetes/minikube/pull/9508\n  # Example inputs:\n  #\n  # Docker:               /docker/562a56986a84b3cd38d6a32ac43fdfcc8ad4d2473acf2839cbf549273f35c206 /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:143 master:23 - cgroup devices rw,devices\n  # podman:               /libpod_parent/libpod-73a4fb9769188ae5dc51cb7e24b9f2752a4af7b802a8949f06a7b2f2363ab0e9 ...\n  # Cloud Shell:          /kubepods/besteffort/pod3d6beaa3004913efb68ce073d73494b0/accdf94879f0a494f317e9a0517f23cdd18b35ff9439efd0175f17bbc56877c4 /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:19 - cgroup cgroup rw,memory\n  # GitHub actions #9304: /actions_job/0924fbbcf7b18d2a00c171482b4600747afc367a9dfbeac9d6b14b35cda80399 /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:263 master:24 - cgroup cgroup rw,memory\n  cgroup_mounts=$(grep -E -o '/[[:alnum:]].* /sys/fs/cgroup.*.*cgroup' /proc/self/mountinfo || true)\n  if [[ -n \"${cgroup_mounts}\" ]]; then\n    local mount_root\n    mount_root=$(head -n 1 <<<\"${cgroup_mounts}\" | cut -d' ' -f1)\n    for mount_point in $(echo \"${cgroup_mounts}\" | cut -d' ' -f 2); do\n      # bind mount each mount_point to mount_point + mount_root\n      # mount --bind /sys/fs/cgroup/cpu /sys/fs/cgroup/cpu/docker/fb07bb6daf7730a3cb14fc7ff3e345d1e47423756ce54409e66e01911bab2160\n      local target=\"${mount_point}${mount_root}\"\n      if ! findmnt \"${target}\"; then\n        mkdir -p \"${target}\"\n        mount --bind \"${mount_point}\" \"${target}\"\n      fi\n    done\n  fi\n  # kubelet will try to manage cgroups / pods that are not owned by it when\n  # \"nesting\" clusters, unless we instruct it to use a different cgroup root.\n  # We do this, and when doing so we must fixup this alternative root\n  # currently this is hardcoded to be /kubelet\n  # under systemd cgroup driver, kubelet appends .slice\n  mount --make-rprivate /sys/fs/cgroup\n  cgroup_subsystems=$(findmnt -lun -o source,target -t cgroup | grep -F \"${current_cgroup}\" | awk '{print $2}')\n  mount_kubelet_cgroup_root \"${cgroup_subsystems}\"\n}\n\nfix_machine_id() {\n  # Deletes the machine-id embedded in the node image and generates a new one.\n  # This is necessary because both kubelet and other components like weave net\n  # use machine-id internally to distinguish nodes.\n  log_info 'clearing and regenerating /etc/machine-id'\n  rm -f /etc/machine-id\n  systemd-machine-id-setup\n}\n\nfix_product_name() {\n  # this is a small fix to hide the underlying hardware and fix issue #426\n  # https://github.com/kubernetes-sigs/kind/issues/426\n  if [[ -f /sys/class/dmi/id/product_name ]]; then\n    log_info 'faking /sys/class/dmi/id/product_name to be \"kind\"'\n    echo 'kind' > /kind/product_name\n    mount -o ro,bind /kind/product_name /sys/class/dmi/id/product_name\n  fi\n}\n\nfix_product_uuid() {\n  # The system UUID is usually read from DMI via sysfs, the problem is that\n  # in the kind case this means that all (container) nodes share the same\n  # system/product uuid, as they share the same DMI.\n  # Note: The UUID is read from DMI, this tool is overwriting the sysfs files\n  # which should fix the attached issue, but this workaround does not address\n  # the issue if a tool is reading directly from DMI.\n  # https://github.com/kubernetes-sigs/kind/issues/1027\n  [[ ! -f /kind/product_uuid ]] && cat /proc/sys/kernel/random/uuid > /kind/product_uuid\n  if [[ -f /sys/class/dmi/id/product_uuid ]]; then\n    log_info 'faking /sys/class/dmi/id/product_uuid to be random'\n    mount -o ro,bind /kind/product_uuid /sys/class/dmi/id/product_uuid\n  fi\n  if [[ -f /sys/devices/virtual/dmi/id/product_uuid ]]; then\n    log_info 'faking /sys/devices/virtual/dmi/id/product_uuid as well'\n    mount -o ro,bind /kind/product_uuid /sys/devices/virtual/dmi/id/product_uuid\n  fi\n}\n\nselect_iptables() {\n  # based on: https://github.com/kubernetes-sigs/iptables-wrappers/blob/97b01f43a8e8db07840fc4b95e833a37c0d36b12/iptables-wrapper-installer.sh\n  local mode num_legacy_lines num_nft_lines\n  num_legacy_lines=$( (iptables-legacy-save || true; ip6tables-legacy-save || true) 2>/dev/null | grep -c '^-' || true)\n  num_nft_lines=$( (timeout 5 sh -c \"iptables-nft-save; ip6tables-nft-save\" || true) 2>/dev/null | grep -c '^-' || true)\n  if [ \"${num_legacy_lines}\" -ge \"${num_nft_lines}\" ]; then\n    mode=legacy\n  else\n    mode=nft\n  fi\n  log_info \"setting iptables to detected mode: ${mode}\"\n  update-alternatives --set iptables \"/usr/sbin/iptables-${mode}\" > /dev/null\n  update-alternatives --set ip6tables \"/usr/sbin/ip6tables-${mode}\" > /dev/null\n}\n\nfix_certificate() {\n  local apiserver_crt_file=\"/etc/kubernetes/pki/apiserver.crt\"\n  local apiserver_key_file=\"/etc/kubernetes/pki/apiserver.key\"\n\n  # Skip if this Node doesn't run kube-apiserver\n  if [[ ! -f ${apiserver_crt_file} ]] || [[ ! -f ${apiserver_key_file} ]]; then\n    return\n  fi\n\n  # Deletes the certificate for kube-apiserver and generates a new one.\n  # This is necessary because the old one doesn't match the current IP.\n  log_info 'clearing and regenerating the certificate for serving the Kubernetes API'\n  rm -f ${apiserver_crt_file} ${apiserver_key_file}\n  kubeadm init phase certs apiserver --config /kind/kubeadm.conf\n}\n\nenable_network_magic(){\n  # well-known docker embedded DNS is at 127.0.0.11:53\n  local docker_embedded_dns_ip='127.0.0.11'\n\n  # first we need to detect an IP to use for reaching the docker host\n  local docker_host_ip\n  docker_host_ip=\"$( (head -n1 <(timeout 5 getent ahostsv4 'host.docker.internal') | cut -d' ' -f1) || true)\"\n  # if the ip doesn't exist or is a loopback address use the default gateway\n  if [[ -z \"${docker_host_ip}\" ]] || [[ $docker_host_ip =~ ^127\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n    docker_host_ip=$(ip -4 route show default | cut -d' ' -f3)\n  fi\n\n  # patch docker's iptables rules to switch out the DNS IP\n  iptables-save \\\n    | sed \\\n      `# switch docker DNS DNAT rules to our chosen IP` \\\n      -e \"s/-d ${docker_embedded_dns_ip}/-d ${docker_host_ip}/g\" \\\n      `# we need to also apply these rules to non-local traffic (from pods)` \\\n      -e 's/-A OUTPUT \\(.*\\) -j DOCKER_OUTPUT/\\0\\n-A PREROUTING \\1 -j DOCKER_OUTPUT/' \\\n      `# switch docker DNS SNAT rules rules to our chosen IP` \\\n      -e \"s/--to-source :53/--to-source ${docker_host_ip}:53/g\"\\\n      `# nftables incompatibility between 1.8.8 and 1.8.7 omit the --dport flag on DNAT rules` \\\n      `# ensure --dport on DNS rules, due to https://github.com/kubernetes-sigs/kind/issues/3054` \\\n      -e \"s/p -j DNAT --to-destination ${docker_embedded_dns_ip}/p --dport 53 -j DNAT --to-destination ${docker_embedded_dns_ip}/g\" \\\n    | iptables-restore\n\n  # now we can ensure that DNS is configured to use our IP\n  cp /etc/resolv.conf /etc/resolv.conf.original\n  replaced=\"$(sed -e \"s/${docker_embedded_dns_ip}/${docker_host_ip}/g\" /etc/resolv.conf.original)\"\n  if [[ \"${KIND_DNS_SEARCH+x}\" == \"\" ]]; then\n    # No DNS search set, just pass through as is\n    echo \"$replaced\" >/etc/resolv.conf\n  elif [[ -z \"$KIND_DNS_SEARCH\" ]]; then\n    # Empty search - remove all current search clauses\n    echo \"$replaced\" | grep -v \"^search\" >/etc/resolv.conf\n  else\n    # Search set - remove all current search clauses, and add the configured search\n    {\n      echo \"search $KIND_DNS_SEARCH\";\n      echo \"$replaced\" | grep -v \"^search\";\n    } >/etc/resolv.conf\n  fi\n\n  local files_to_update=(\n    /etc/kubernetes/manifests/etcd.yaml\n    /etc/kubernetes/manifests/kube-apiserver.yaml\n    /etc/kubernetes/manifests/kube-controller-manager.yaml\n    /etc/kubernetes/manifests/kube-scheduler.yaml\n    /etc/kubernetes/controller-manager.conf\n    /etc/kubernetes/scheduler.conf\n    /etc/kubernetes/kubelet.conf\n    /kind/kubeadm.conf\n    /var/lib/kubelet/kubeadm-flags.env\n  )\n  local should_fix_certificate=false\n  # fixup IPs in manifests ...\n  curr_ipv4=\"$( (head -n1 <(timeout 5 getent ahostsv4 \"$(hostname)\") | cut -d' ' -f1) || true)\"\n  log_info \"detected IPv4 address: ${curr_ipv4}\"\n  if [ -f /kind/old-ipv4 ]; then\n    old_ipv4=$(cat /kind/old-ipv4)\n    log_info \"detected old IPv4 address: ${old_ipv4}\"\n    # sanity check that we have a current address\n    if [[ -z $curr_ipv4 ]]; then\n      log_error \"have an old IPv4 address but no current IPv4 address (!)\"\n      exit 1\n    fi\n    if [[ \"${old_ipv4}\" != \"${curr_ipv4}\" ]]; then\n      should_fix_certificate=true\n      sed_ipv4_command=\"s#\\b$(regex_escape_ip \"${old_ipv4}\")\\b#${curr_ipv4}#g\"\n      for f in \"${files_to_update[@]}\"; do\n        # kubernetes manifests are only present on control-plane nodes\n        if [[ -f \"$f\" ]]; then\n          sed -i \"${sed_ipv4_command}\" \"$f\"\n        fi\n      done\n    fi\n  fi\n  if [[ -n $curr_ipv4 ]]; then\n    echo -n \"${curr_ipv4}\" >/kind/old-ipv4\n  fi\n\n  # do IPv6\n  curr_ipv6=\"$( (head -n1 <(timeout 5 getent ahostsv6 \"$(hostname)\") | cut -d' ' -f1) || true)\"\n  log_info \"detected IPv6 address: ${curr_ipv6}\"\n  if [ -f /kind/old-ipv6 ]; then\n    old_ipv6=$(cat /kind/old-ipv6)\n    log_info \"detected old IPv6 address: ${old_ipv6}\"\n    # sanity check that we have a current address\n    if [[ -z $curr_ipv6 ]]; then\n      log_error \"have an old IPv6 address but no current IPv6 address (!)\"\n    fi\n    if [[ \"${old_ipv6}\" != \"${curr_ipv6}\" ]]; then\n      should_fix_certificate=true\n      sed_ipv6_command=\"s#\\b$(regex_escape_ip \"${old_ipv6}\")\\b#${curr_ipv6}#g\"\n      for f in \"${files_to_update[@]}\"; do\n        # kubernetes manifests are only present on control-plane nodes\n        if [[ -f \"$f\" ]]; then\n          sed -i \"${sed_ipv6_command}\" \"$f\"\n        fi\n      done\n    fi\n  fi\n  if [[ -n $curr_ipv6 ]]; then\n    echo -n \"${curr_ipv6}\" >/kind/old-ipv6\n  fi\n\n  if $should_fix_certificate; then\n    fix_certificate\n  fi\n}\n\n# validate state\nvalidate_userns\n\n# run pre-init fixups\n# NOTE: it's important that we do configure* first in this order to avoid races\nconfigure_containerd\nconfigure_proxy\nfix_mount\nfix_cgroup\nfix_machine_id\nfix_product_name\nfix_product_uuid\nselect_iptables\nenable_network_magic\n\n# we want the command (expected to be systemd) to be PID1, so exec to it\nlog_info 'starting init'\nexec \"$@\"\n"
  },
  {
    "path": "images/base/scripts/target-cc",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2023 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 maps buildx TARGETARCH to $CC\n\nset -o errexit -o nounset -o pipefail\n\ncase $TARGETARCH in\n  arm64)\n    echo -n 'aarch64-linux-gnu-gcc' ;;\n  amd64)\n    echo -n 'x86_64-linux-gnu-gcc' ;;\n  *)\n    exit 1 ;;\nesac\n"
  },
  {
    "path": "images/base/scripts/third_party/gimme/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2018 gimme contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "images/base/scripts/third_party/gimme/README.md",
    "content": "# gimme\n\nThis is an unmodified copy of [gimme], so we don't have to download it\nfrom the internet.\n\n[gimme]: https://github.com/travis-ci/gimme"
  },
  {
    "path": "images/base/scripts/third_party/gimme/gimme",
    "content": "#!/usr/bin/env bash\n# vim:noexpandtab:ts=2:sw=2:\n#\n#+  Usage: $(basename $0) [flags] [go-version] [version-prefix]\n#+  -\n#+  Version: ${GIMME_VERSION}\n#+  Copyright: ${GIMME_COPYRIGHT}\n#+  License URL: ${GIMME_LICENSE_URL}\n#+  -\n#+  Install go!  There are multiple types of installations available, with 'auto' being the default.\n#+  If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing\n#+  go installation.  This behavior may be disabled by providing '-f/--force/force' as first positional\n#+  argument.\n#+  -\n#+  Option flags:\n#+          -h --help help - show this help text and exit\n#+    -V --version version - show the version only and exit\n#+        -f --force force - remove the existing go installation if present prior to install\n#+          -l --list list - list installed go versions and exit\n#+        -k --known known - list known go versions and exit\n#+    --force-known-update - when used with --known, ignores the cache and updates\n#+    -r --resolve resolve - resolve a version specifier to a version, show that and exit\n#+  -\n#+  Influential env vars:\n#+  -\n#+        GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg)\n#+    GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}',\n#+                           may be given as second positional arg)\n#+              GIMME_ARCH - arch to install (default '${GIMME_ARCH}')\n#+        GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}')\n#+        GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}')\n#+     GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}')\n#+                GIMME_OS - os to install (default '${GIMME_OS}')\n#+               GIMME_TMP - temp directory (default '${GIMME_TMP}')\n#+              GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git')\n#+                           (default '${GIMME_TYPE}')\n#+      GIMME_INSTALL_RACE - install race directory after compile if non-empty.\n#+                           If the install type is 'binary', this option is ignored.\n#+             GIMME_DEBUG - enable tracing if non-empty\n#+      GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host\n#+        GIMME_SILENT_ENV - omit the 'go version' line from env file\n#+       GIMME_CGO_ENABLED - enable build of cgo support\n#+     GIMME_CC_FOR_TARGET - cross compiler for cgo support\n#+     GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}')\n#+        GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}')\n#+   GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}')\n#+  -\n#\nset -e\nshopt -s nullglob\nshopt -s dotglob\nshopt -s extglob\nset -o pipefail\n\n[[ ${GIMME_DEBUG} ]] && set -x\n\nreadonly GIMME_VERSION=\"v1.5.4\"\nreadonly GIMME_COPYRIGHT=\"Copyright (c) 2015-2020 gimme contributors\"\nreadonly GIMME_LICENSE_URL=\"https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE\"\nexport GIMME_VERSION\nexport GIMME_COPYRIGHT\nexport GIMME_LICENSE_URL\n\nprogram_name=\"$(basename \"$0\")\"\n# shellcheck disable=SC1117\nwarn() { printf >&2 \"%s: %s\\n\" \"${program_name}\" \"${*}\"; }\ndie() {\n\twarn \"$@\"\n\texit 1\n}\n\n# We don't want to go around hitting Google's servers with requests for\n# files named HEAD@{date}.tar so we only try binary/source downloads if\n# it looks like a plausible name to us.\n# We don't need to support 0. releases of Go.\n# We don't support 5 digit major-versions of Go (limit back-tracking in RE).\n# We don't support very long versions\n#   (both to avoid annoying download server operators with attacks and\n#    because regexp backtracking can be pathological).\n# Per _assert_version_given we do assume 2.0 not 2\nALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\\.[0-9][0-9a-zA-Z_-]{0,9})+$'\n#\n# The main path which allowed these to leak upstream before has been closed\n# but a valid git repo tag or branch-name will still reach the point of\n# being _tried_ upstream.\n\n# _do_curl \"url\" \"file\"\n_do_curl() {\n\tmkdir -p \"$(dirname \"${2}\")\"\n\n\tif command -v curl >/dev/null; then\n\t\tcurl -sSLf \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v wget >/dev/null; then\n\t\twget -q \"${1}\" -O \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v fetch >/dev/null; then\n\t\tfetch -q \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\techo >&2 'error: no curl, wget, or fetch found'\n\texit 1\n}\n\n# _sha256sum \"file\"\n_sha256sum() {\n\tif command -v sha256sum &>/dev/null; then\n\t\tsha256sum \"$@\"\n\telif command -v gsha256sum &>/dev/null; then\n\t\tgsha256sum \"$@\"\n\telse\n\t\tshasum -a 256 \"$@\"\n\tfi\n}\n\n# sort versions, handling 1.10 after 1.9, not before 1.2\n# FreeBSD sort has --version-sort, none of the others do\n# Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty\nif sort --version-sort </dev/null &>/dev/null; then\n\t_version_sort() { sort --version-sort; }\nelse\n\t_version_sort() {\n\t\t# If we go to four-digit minor or patch versions, then extend the padding here\n\t\t# (but in such a world, perhaps --version-sort will have become standard by then?)\n\t\tsed -E 's/\\.([0-9](\\.|$))/.00\\1/g; s/\\.([0-9][0-9](\\.|$))/.0\\1/g' |\n\t\t\tsort --general-numeric-sort |\n\t\t\tsed 's/\\.00*/./g'\n\t}\nfi\n\n# _do_curls \"file\" \"url\" [\"url\"...]\n_do_curls() {\n\tf=\"${1}\"\n\tshift\n\tif _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\treturn 0\n\tfi\n\tfor url in \"${@}\"; do\n\t\tif _do_curl \"${url}\" \"${f}\"; then\n\t\t\tif _do_curl \"${url}.sha256\" \"${f}.sha256\"; then\n\t\t\t\techo \"$(cat \"${f}.sha256\")  ${f}\" >\"${f}.sha256.tmp\"\n\t\t\t\tmv \"${f}.sha256.tmp\" \"${f}.sha256\"\n\t\t\t\tif ! _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\t\t\t\twarn \"sha256sum failed for '${f}'\"\n\t\t\t\t\twarn 'continuing to next candidate URL'\n\t\t\t\t\tcontinue\n\t\t\t\tfi\n\t\t\tfi\n\t\t\treturn\n\t\tfi\n\tdone\n\trm -f \"${f}\"\n\treturn 1\n}\n\n# _binary \"version\" \"file.tar.gz\" \"arch\"\n_binary() {\n\tlocal version=${1}\n\tlocal file=${2}\n\tlocal arch=${3}\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz\"\n\t)\n\tif [[ \"${GIMME_OS}\" == 'darwin' && \"${GIMME_BINARY_OSX}\" ]]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz\"\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${arch}\" = 'arm' ]; then\n\t\t# attempt \"armv6l\" vs just \"arm\" first (since that's what's officially published)\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz\" # go1.6beta2 & go1.6rc1\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz\" # go1.6beta1\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip\"\n\t\t)\n\tfi\n\t_do_curls \"${file}\" \"${urls[@]}\"\n}\n\n# _source \"version\" \"file.src.tar.gz\"\n_source() {\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz\"\n\t\t\"https://github.com/golang/go/archive/go${1}.tar.gz\"\n\t)\n\t_do_curls \"${2}\" \"${urls[@]}\"\n}\n\n# _fetch \"dir\"\n_fetch() {\n\tmkdir -p \"$(dirname \"${1}\")\"\n\n\tif [[ -d \"${1}/.git\" ]]; then\n\t\t(\n\t\t\tcd \"${1}\"\n\t\t\tgit remote set-url origin \"${GIMME_GO_GIT_REMOTE}\"\n\t\t\tgit fetch -q --all && git fetch -q --tags\n\t\t)\n\t\treturn\n\tfi\n\n\tgit clone -q \"${GIMME_GO_GIT_REMOTE}\" \"${1}\"\n}\n\n# _checkout \"version\" \"dir\"\n# NB: might emit a \"renamed version\" on stdout\n_checkout() {\n\tlocal spec=\"${1:?}\" godir=\"${2:?}\"\n\t# We are called twice, once during validation that a version was given and\n\t# later during build.  We don't want to fetch twice, so we are fetching\n\t# during the validation only, in the caller.\n\n\tif [[ \"${spec}\" =~ ^[0-9a-f]{6,}$ ]]; then\n\t\t# We always treat this as a commit sha, whether instead of doing\n\t\t# branch tests etc.  It looks like a commit sha and the Go maintainers\n\t\t# aren't daft enough to use pure hex for a tag or branch.\n\t\tgit -C \"$godir\" reset -q --hard \"${spec}\" || return 1\n\t\treturn 0\n\tfi\n\n\t# If spec looks like HEAD^{something} or HEAD^^^ then trying\n\t# origin/$spec would succeed but we'd write junk to the filesystem,\n\t# propagating annoying characters out.\n\tlocal retval probe_named disallow rev\n\n\tprobe_named=1\n\tdisallow='[@^~:{}]'\n\tif [[ \"${spec}\" =~ $disallow ]]; then\n\t\tprobe_named=0\n\t\t[[ \"${spec}\" != \"@\" ]] || spec=\"HEAD\"\n\tfi\n\n\ttry_spec() { git -C \"${godir}\" reset -q --hard \"$@\" -- 2>/dev/null; }\n\n\tretval=1\n\tif ((probe_named)); then\n\t\tretval=0\n\t\ttry_spec \"origin/${spec}\" ||\n\t\t\ttry_spec \"origin/go${spec}\" ||\n\t\t\t{ [[ \"${spec}\" == \"tip\" ]] && try_spec origin/master; } ||\n\t\t\ttry_spec \"refs/tags/${spec}\" ||\n\t\t\ttry_spec \"refs/tags/go${spec}\" ||\n\t\t\tretval=1\n\tfi\n\n\tif ((retval)); then\n\t\tretval=0\n\t\t# We're about to reset anyway, if we succeed, so we should reset to a\n\t\t# known state before parsing what might be relative specs\n\t\ttry_spec origin/master &&\n\t\t\trev=\"$(git -C \"${godir}\" rev-parse --verify -q \"${spec}^{object}\")\" &&\n\t\t\ttry_spec \"${rev}\" &&\n\t\t\tgit -C \"${godir}\" rev-parse --verify -q --short=12 \"${rev}\" ||\n\t\t\tretval=1\n\t\t# that rev-parse prints to stdout, so we can affect the version seen\n\tfi\n\n\tunset -f try_spec\n\treturn $retval\n}\n\n# _extract \"file.tar.gz\" \"dir\"\n_extract() {\n\tmkdir -p \"${2}\"\n\n\tif [[ \"${1}\" == *.tar.gz ]]; then\n\t\ttar -xf \"${1}\" -C \"${2}\" --strip-components 1\n\telse\n\t\tunzip -q \"${1}\" -d \"${2}\"\n\t\tmv \"${2}\"/go/* \"${2}\"\n\t\trmdir \"${2}\"/go\n\tfi\n}\n\n# _setup_bootstrap\n_setup_bootstrap() {\n\tlocal versions=(\"1.18\" \"1.17\" \"1.16\" \"1.15\" \"1.14\" \"1.13\" \"1.12\" \"1.11\" \"1.10\" \"1.9\" \"1.8\" \"1.7\" \"1.6\" \"1.5\" \"1.4\")\n\n\t# try existing\n\tfor v in \"${versions[@]}\"; do\n\t\tfor candidate in \"${GIMME_ENV_PREFIX}/go${v}\"*\".env\"; do\n\t\t\tif [ -s \"${candidate}\" ]; then\n\t\t\t\t# shellcheck source=/dev/null\n\t\t\t\tGOROOT_BOOTSTRAP=\"$(source \"${candidate}\" 2>/dev/null && go env GOROOT)\"\n\t\t\t\texport GOROOT_BOOTSTRAP\n\t\t\t\treturn 0\n\t\t\tfi\n\t\tdone\n\tdone\n\n\t# try binary\n\tfor v in \"${versions[@]}\"; do\n\t\tif [ -n \"$(_try_binary \"${v}\" \"${GIMME_HOSTARCH}\")\" ]; then\n\t\t\texport GOROOT_BOOTSTRAP=\"${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}\"\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\techo >&2 \"Unable to setup go bootstrap from existing or binary\"\n\treturn 1\n}\n\n# _compile \"dir\"\n_compile() {\n\t(\n\t\tif grep -q GOROOT_BOOTSTRAP \"${1}/src/make.bash\" &>/dev/null; then\n\t\t\t_setup_bootstrap || return 1\n\t\tfi\n\t\tcd \"${1}\"\n\t\tif [[ -d .git ]]; then\n\t\t\tgit clean -dfx -q\n\t\tfi\n\t\tcd src\n\t\texport GOOS=\"${GIMME_OS}\" GOARCH=\"${GIMME_ARCH}\"\n\t\texport CGO_ENABLED=\"${GIMME_CGO_ENABLED}\"\n\t\texport CC_FOR_TARGET=\"${GIMME_CC_FOR_TARGET}\"\n\n\t\tlocal make_log=\"${1}/make.${GOOS}.${GOARCH}.log\"\n\t\tif [[ \"${GIMME_DEBUG}\" -ge \"2\" ]]; then\n\t\t\t./make.bash -v 2>&1 | tee \"${make_log}\" 1>&2 || return 1\n\t\telse\n\t\t\t./make.bash &>\"${make_log}\" || return 1\n\t\tfi\n\t)\n}\n\n_try_install_race() {\n\tif [[ ! \"${GIMME_INSTALL_RACE}\" ]]; then\n\t\treturn 0\n\tfi\n\t\"${1}/bin/go\" install -race std\n}\n\n_can_compile() {\n\tcat >\"${GIMME_TMP}/test.go\" <<'EOF'\npackage main\nimport \"os\"\nfunc main() {\n\tos.Exit(0)\n}\nEOF\n\t\"${1}/bin/go\" run \"${GIMME_TMP}/test.go\"\n}\n\n# _env \"dir\"\n_env() {\n\t[[ -d \"${1}/bin\" && -x \"${1}/bin/go\" ]] || return 1\n\n\t# if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source\n\t# automatically\n\tGOROOT=\"${1}\" GOFLAGS=\"\" \"${1}/bin/go\" version &>/dev/null || return 1\n\n\t# https://twitter.com/davecheney/status/431581286918934528\n\t# we have to GOROOT sometimes because we use official release binaries in unofficial locations :(\n\t#\n\t# Issue 87 leads to:\n\t#   No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it.\n\t#   The \"avoid setting it\" is _only_ for people using official releases in official locations.\n\t#   Tools like `gimme` are the reason that GOROOT-in-env exists.\n\n\techo\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" ]]; then\n\t\techo 'unset GOOS;'\n\telse\n\t\techo 'export GOOS=\"'\"${GIMME_OS}\"'\";'\n\tfi\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\techo 'unset GOARCH;'\n\telse\n\t\techo 'export GOARCH=\"'\"${GIMME_ARCH}\"'\";'\n\tfi\n\n\techo \"export GOROOT='${1}';\"\n\n\t# shellcheck disable=SC2016\n\techo 'export PATH=\"'\"${1}/bin\"':${PATH}\";'\n\tif [[ -z \"${GIMME_SILENT_ENV}\" ]]; then\n\t\techo 'go version >&2;'\n\tfi\n\techo\n}\n\n# _env_alias \"dir\" \"env-file\"\n_env_alias() {\n\tif [[ \"${GIMME_NO_ENV_ALIAS}\" ]]; then\n\t\techo \"${2}\"\n\t\treturn\n\tfi\n\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" && \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\t# GIMME_GO_VERSION might be a branch, which can contain '/'\n\t\tlocal dest=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\\//__}.env\"\n\t\tcp \"${2}\" \"${dest}\"\n\t\tln -sf \"${dest}\" \"${GIMME_ENV_PREFIX}/latest.env\"\n\t\techo \"${dest}\"\n\telse\n\t\techo \"${2}\"\n\tfi\n}\n\n_try_existing() {\n\tcase \"${1}\" in\n\tbinary)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\t\t;;\n\tsource)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\t\t;;\n\t*)\n\t\t_try_existing binary || _try_existing source\n\t\treturn $?\n\t\t;;\n\tesac\n\n\tif [[ -x \"${existing_ver}/bin/go\" && -s \"${existing_env}\" ]]; then\n\t\t# newer envs have existing semi-colon at end of line, because newer gimme\n\t\t# puts them there; envs created before that change lack those semi-colons\n\t\t# and should gain them, to make it easier for people using eval without\n\t\t# double-quoting the command substition.\n\t\tsed -e 's/\\([^;]\\)$/\\1;/' <\"${existing_env}\"\n\t\t# gimme is the corner-case where GOROOT _should_ be overriden, since if the\n\t\t# ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is\n\t\t# unset, then it will be used and the wrong golang will be picked up.\n\t\t# Lots of old installs won't have GOROOT; munge it from $PATH\n\t\tif grep -qs '^unset GOROOT' -- \"${existing_env}\"; then\n\t\t\tsed -n -e 's/^export PATH=\"\\(.*\\)\\/bin:.*$/export GOROOT='\"'\"'\\1'\"'\"';/p' <\"${existing_env}\"\n\t\t\techo\n\t\tfi\n\t\t# Export the same variables whether building new or using existing\n\t\techo \"export GIMME_ENV='${existing_env}';\"\n\t\treturn\n\tfi\n\n\treturn 1\n}\n\n# _try_binary \"version\" \"arch\"\n_try_binary() {\n\tlocal version=${1}\n\tlocal arch=${2}\n\tlocal bin_tgz=\"${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz\"\n\tlocal bin_dir=\"${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}\"\n\tlocal bin_env=\"${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env\"\n\n\t[[ \"${version}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\tbin_tgz=${bin_tgz%.tar.gz}.zip\n\tfi\n\n\t_binary \"${version}\" \"${bin_tgz}\" \"${arch}\" || return 1\n\t_extract \"${bin_tgz}\" \"${bin_dir}\" || return 1\n\t_env \"${bin_dir}\" | tee \"${bin_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${bin_dir}\" \"${bin_env}\")\\\"\"\n}\n\n_try_source() {\n\tlocal src_tgz=\"${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz\"\n\tlocal src_dir=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\tlocal src_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\n\t[[ \"${GIMME_GO_VERSION}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\t_source \"${GIMME_GO_VERSION}\" \"${src_tgz}\" || return 1\n\t_extract \"${src_tgz}\" \"${src_dir}\" || return 1\n\t_compile \"${src_dir}\" || return 1\n\t_try_install_race \"${src_dir}\" || return 1\n\t_env \"${src_dir}\" | tee \"${src_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${src_dir}\" \"${src_env}\")\\\"\"\n}\n\n# We do _not_ try to use any version caching with _try_existing(), but instead\n# build afresh each time.  We don't want to deal with someone moving the repo\n# to other-version, doing an install, then resetting it back to\n# last-version-we-saw and thus introducing conflicts.\n#\n# If you want to re-use a built-at-spec version, then avoid moving the repo\n# and source the generated .env manually.\n# Note that the env will just refer to the 'go' directory, so it's not safe\n# to reuse anyway.\n_try_git() {\n\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\tlocal git_env=\"${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env\"\n\tlocal resolved_sha\n\n\t# Any tags should have been resolved when we asserted that we were\n\t# given a version, so no need to handle that here.\n\t_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\" >/dev/null || return 1\n\t_compile \"${git_dir}\" || return 1\n\t_try_install_race \"${git_dir}\" || return 1\n\t_env \"${git_dir}\" | tee \"${git_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${git_dir}\" \"${git_env}\")\\\"\"\n}\n\n_wipe_version() {\n\tlocal env_file=\"${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\n\tif [[ -s \"${env_file}\" ]]; then\n\t\trm -rf \"$(awk -F\\\" '/GOROOT/ { print $2 }' \"${env_file}\")\"\n\t\trm -f \"${env_file}\"\n\tfi\n}\n\n_list_versions() {\n\tif [ ! -d \"${GIMME_VERSION_PREFIX}\" ]; then\n\t\treturn 0\n\tfi\n\n\tlocal current_version\n\tcurrent_version=\"$(go env GOROOT 2>/dev/null)\"\n\tcurrent_version=\"${current_version##*/go}\"\n\tcurrent_version=\"${current_version%%.${GIMME_OS}.*}\"\n\n\t# 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash\n\t# doesn't appear to have anything like that.\n\tfor d in \"${GIMME_VERSION_PREFIX}/go\"*\".${GIMME_OS}.\"*; do\n\t\tlocal cleaned=\"${d##*/go}\"\n\t\tcleaned=\"${cleaned%%.${GIMME_OS}.*}\"\n\t\techo \"${cleaned}\"\n\tdone | _version_sort | while read -r cleaned; do\n\t\techo -en \"${cleaned}\"\n\t\tif [[ \"${cleaned}\" == \"${current_version}\" ]]; then\n\t\t\techo -en ' <= current' >&2\n\t\tfi\n\t\techo\n\tdone\n}\n\n_update_remote_known_list_if_needed() {\n\t# shellcheck disable=SC1117\n\tlocal exp=\"go([[:alnum:]\\.]*)\\.src.*\" # :alnum: catches beta versions too\n\tlocal list=\"${GIMME_VERSION_PREFIX}/known-versions.txt\"\n\tlocal dlfile=\"${GIMME_TMP}/known-dl\"\n\n\tif [[ -e \"${list}\" ]] &&\n\t\t! ((force_known_update)) &&\n\t\t! _file_older_than_secs \"${list}\" \"${GIMME_KNOWN_CACHE_MAX}\"; then\n\t\techo \"${list}\"\n\t\treturn 0\n\tfi\n\n\t[[ -d \"${GIMME_VERSION_PREFIX:?}\" ]] || mkdir -p -- \"${GIMME_VERSION_PREFIX}\"\n\n\t_do_curl \"${GIMME_LIST_KNOWN}\" \"${dlfile}\"\n\n\twhile read -r line; do\n\t\tif [[ \"${line}\" =~ ${exp} ]]; then\n\t\t\techo \"${BASH_REMATCH[1]}\"\n\t\tfi\n\tdone <\"${dlfile}\" | _version_sort | uniq >\"${list}.new\"\n\trm -f \"${list}\" &>/dev/null\n\tmv \"${list}.new\" \"${list}\"\n\n\trm -f \"${dlfile}\"\n\techo \"${list}\"\n\treturn 0\n}\n\n_list_known() {\n\tlocal knownfile\n\tknownfile=\"$(_update_remote_known_list_if_needed)\"\n\n\t(\n\t\t_list_versions 2>/dev/null\n\t\tcat -- \"${knownfile}\"\n\t) | grep . | _version_sort | uniq\n}\n\n# For the \"invoked on commandline\" case, we want to always pass unknown\n# strings through, so that we can be a uniqueness filter, but for unknown\n# names we want to exit with a value other than 1, so we document that\n# we'll exit 2.  For use by other functions, 2 is as good as 1.\n_resolve_version() {\n\tcase \"${1}\" in\n\tstable)\n\t\t_get_curr_stable\n\t\treturn 0\n\t\t;;\n\toldstable)\n\t\t_get_old_stable\n\t\treturn 0\n\t\t;;\n\ttip)\n\t\techo \"tip\"\n\t\treturn 0\n\t\t;;\n\t*.x)\n\t\ttrue\n\t\t;;\n\t*)\n\t\techo \"${1}\"\n\t\tlocal GIMME_GO_VERSION=\"$1\"\n\t\tlocal ASSERT_ABORT='return'\n\t\tif _assert_version_given 2>/dev/null; then\n\t\t\treturn 0\n\t\tfi\n\t\twarn \"version specifier '${1}' unknown\"\n\t\treturn 2\n\t\t;;\n\tesac\n\t# We have a .x suffix\n\tlocal base=\"${1%.x}\"\n\tlocal ver last='' known\n\tknown=\"$(_update_remote_known_list_if_needed)\" # will be version-sorted\n\tif [[ ! \"${base}\" =~ ^[0-9.]+$ ]]; then\n\t\twarn \"resolve pattern '${base}.x' invalid for .x finding\"\n\t\treturn 2\n\tfi\n\t# The `.x` is optional; \"1.10\" matches \"1.10.x\"\n\tlocal search=\"^${base//./\\\\.}(\\\\.[0-9.]+)?\\$\"\n\t# avoid regexp attacks\n\twhile read -r ver; do\n\t\t[[ \"${ver}\" =~ $search ]] || continue\n\t\tlast=\"${ver}\"\n\tdone <\"$known\"\n\tif [[ -n \"${last}\" ]]; then\n\t\techo \"${last}\"\n\t\treturn 0\n\tfi\n\techo \"${1}\"\n\twarn \"given '${1}' but no release for '${base}' found\"\n\treturn 2\n}\n\n_realpath() {\n\t# shellcheck disable=SC2005\n\t[ -d \"$1\" ] && echo \"$(cd \"$1\" && pwd)\" || echo \"$(cd \"$(dirname \"$1\")\" && pwd)/$(basename \"$1\")\"\n}\n\n_get_curr_stable() {\n\tlocal stable=\"${GIMME_VERSION_PREFIX}/stable\"\n\n\tif _file_older_than_secs \"${stable}\" 86400; then\n\t\t_update_stable \"${stable}\"\n\tfi\n\n\tcat \"${stable}\"\n}\n\n_get_old_stable() {\n\tlocal oldstable=\"${GIMME_VERSION_PREFIX}/oldstable\"\n\n\tif _file_older_than_secs \"${oldstable}\" 86400; then\n\t\t_update_oldstable \"${oldstable}\"\n\tfi\n\n\tcat \"${oldstable}\"\n}\n\n_update_stable() {\n\tlocal stable=\"${1}\"\n\tlocal url=\"https://golang.org/VERSION?m=text\"\n\n\t_do_curl \"${url}\" \"${stable}\"\n\tsed -i.old -e 's/^go\\(.*\\)/\\1/' \"${stable}\"\n\trm -f \"${stable}.old\"\n}\n\n_update_oldstable() {\n\tlocal oldstable=\"${1}\"\n\tlocal oldstable_x\n\toldstable_x=$(_get_curr_stable | awk -F. '{\n\t\t$2--;\n\t\tprint $1 \".\" $2 \".\" \"x\"\n\t}')\n\t_resolve_version \"${oldstable_x}\" >\"${oldstable}\"\n}\n\n_last_mod_timestamp() {\n\tlocal filename=\"${1}\"\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin | *bsd)\n\t\tstat -f %m \"${filename}\"\n\t\t;;\n\tlinux)\n\t\tstat -c %Y \"${filename}\"\n\t\t;;\n\tesac\n}\n\n_file_older_than_secs() {\n\tlocal file=\"${1}\"\n\tlocal age_secs=\"${2}\"\n\tlocal ts\n\t# if the file does not exist, we return true, as the cache needs updating\n\tts=\"$(_last_mod_timestamp \"${file}\" 2>/dev/null)\" || return 0\n\t((($(date +%s) - ts) > age_secs))\n}\n\n_assert_version_given() {\n\t# By the time we're called, aliases such as \"stable\" must have been resolved\n\t# but we could be a reference in git.\n\t#\n\t# Versions can include suffices such as in \"1.8beta2\", so our assumption is that\n\t# there will always be a minor present; the first public release was \"1.0\" so\n\t# we assume \"2.0\" not \"2\".\n\n\tif [[ -z \"${GIMME_GO_VERSION}\" ]]; then\n\t\techo >&2 'error: no GIMME_GO_VERSION supplied'\n\t\techo >&2 \"  ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}\"\n\t\techo >&2 \"  ex: ${0} 1.4.1 ${*}\"\n\t\t${ASSERT_ABORT:-exit} 1\n\tfi\n\n\t# Note: _resolve_version calls back to us (_assert_version_given), but\n\t# only for cases where the version does not end with .x, so this should\n\t# be safe.\n\t# This should be untangled.  PRs accepted, good starter project.\n\tif [[ \"${GIMME_GO_VERSION}\" == *.x ]]; then\n\t\tGIMME_GO_VERSION=\"$(_resolve_version \"${GIMME_GO_VERSION}\")\" || ${ASSERT_ABORT:-exit} 1\n\tfi\n\n\tif [[ \"${GIMME_GO_VERSION}\" == +([[:digit:]]).+([[:digit:]])* ]]; then\n\t\treturn 0\n\tfi\n\n\t# Here we resolve symbolic references.  If we don't, then we get some\n\t# random git tag name being accepted as valid and then we try to\n\t# curl garbage from upstream.\n\tif [[ \"${GIMME_TYPE}\" == \"auto\" || \"${GIMME_TYPE}\" == \"git\" ]]; then\n\t\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\t\tlocal resolved_sha\n\t\t_fetch \"${git_dir}\"\n\t\tif resolved_sha=\"$(_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\")\"; then\n\t\t\tif [[ -n \"${resolved_sha}\" ]]; then\n\t\t\t\t# Break our normal silence, this one really needs to be seen on stderr\n\t\t\t\t# always; auditability and knowing what version of Go you got wins.\n\t\t\t\twarn \"resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'\"\n\t\t\t\tGIMME_GO_VERSION=\"${resolved_sha}\"\n\t\t\tfi\n\t\t\treturn 0\n\t\tfi\n\tfi\n\n\techo >&2 'error: GIMME_GO_VERSION not recognized as valid'\n\techo >&2 \"  got: ${GIMME_GO_VERSION}\"\n\t${ASSERT_ABORT:-exit} 1\n}\n\n_exclude_from_backups() {\n\t# Please avoid anything which requires elevated privileges or is obnoxious\n\t# enough to offend the invoker\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin)\n\t\t# Darwin: Time Machine is \"standard\", we can add others.  The default\n\t\t# mechanism is sticky, as an attribute on the dir, requires no\n\t\t# privileges, is idempotent (and doesn't support -- to end flags).\n\t\ttmutil addexclusion \"$@\"\n\t\t;;\n\tesac\n}\n\n_versint() {\n\tIFS=\" \" read -r -a args <<<\"${1//[^0-9]/ }\"\n\tprintf '1%03d%03d%03d%03d' \"${args[@]}\"\n}\n\n_to_goarch() {\n\tcase \"${1}\" in\n\taarch64) echo \"arm64\" ;;\n\t*) echo \"${1}\" ;;\n\tesac\n}\n\n: \"${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_ARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_HOSTARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}\"\n: \"${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}\"\n: \"${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}\"\n: \"${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}\"\n: \"${GIMME_TYPE:=auto}\" # 'auto', 'binary', 'source', or 'git'\n: \"${GIMME_BINARY_OSX:=osx10.8}\"\n: \"${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}\"\n: \"${GIMME_LIST_KNOWN:=https://golang.org/dl}\"\n: \"${GIMME_KNOWN_CACHE_MAX:=10800}\"\n\n# The version prefix must be an absolute path\ncase \"${GIMME_VERSION_PREFIX}\" in\n/*) true ;;\n*)\n\techo >&2 \" Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX\"\n\tGIMME_VERSION_PREFIX=\"$(pwd)/${GIMME_VERSION_PREFIX}\"\n\techo >&2 \" to: $GIMME_VERSION_PREFIX\"\n\t;;\nesac\n\ncase \"${GIMME_OS}\" in mingw* | msys_nt*)\n\t# Minimalist GNU for Windows\n\tGIMME_OS='windows'\n\n\tif [ \"${GIMME_ARCH}\" = 'i686' ]; then\n\t\tGIMME_ARCH=\"386\"\n\telse\n\t\tGIMME_ARCH=\"amd64\"\n\tfi\n\t;;\nesac\n\nforce_install=0\nforce_known_update=0\n\nwhile [[ $# -gt 0 ]]; do\n\tcase \"${1}\" in\n\t-h | --help | help | wat)\n\t\t_old_ifs=\"$IFS\"\n\t\tIFS=';'\n\t\tawk '/^#\\+  / {\n\t\t\t\tsub(/^#\\+  /, \"\", $0) ;\n\t\t\t\tsub(/-$/, \"\", $0) ;\n\t\t\t\tprint $0\n\t\t\t}' \"$0\" | while read -r line; do\n\t\t\teval \"echo \\\"$line\\\"\"\n\t\tdone\n\t\tIFS=\"$_old_ifs\"\n\t\texit 0\n\t\t;;\n\t-V | --version | version)\n\t\techo \"${GIMME_VERSION}\"\n\t\texit 0\n\t\t;;\n\t-r | --resolve | resolve)\n\t\t# The normal mkdir of versions is below; we don't want to move it up\n\t\t# to where we create files just if asked our version; thus\n\t\t# _resolve_version has to mkdir the versions dir itself.\n\t\tif [[ $# -ge 2 ]]; then\n\t\t\t_resolve_version \"${2}\"\n\t\telif [[ -n \"${GIMME_GO_VERSION:-}\" ]]; then\n\t\t\t_resolve_version \"${GIMME_GO_VERSION}\"\n\t\telse\n\t\t\tdie \"resolve must be given a version to resolve\"\n\t\tfi\n\t\texit $?\n\t\t;;\n\t-l | --list | list)\n\t\t_list_versions\n\t\texit 0\n\t\t;;\n\t-k | --known | known)\n\t\t_list_known\n\t\texit 0\n\t\t;;\n\t-f | --force | force)\n\t\tforce_install=1\n\t\t;;\n\t--force-known-update | force-known-update)\n\t\tforce_known_update=1\n\t\t;;\n\t-i | install)\n\t\ttrue # ignore a dummy argument\n\t\t;;\n\t*)\n\t\tbreak\n\t\t;;\n\tesac\n\tshift\ndone\n\nif [[ -n \"${1}\" ]]; then\n\tGIMME_GO_VERSION=\"${1}\"\nfi\nif [[ -n \"${2}\" ]]; then\n\tGIMME_VERSION_PREFIX=\"${2}\"\nfi\n\ncase \"${GIMME_ARCH}\" in\nx86_64) GIMME_ARCH=amd64 ;;\nx86) GIMME_ARCH=386 ;;\narm64)\n\tif [[ \"${GIMME_GO_VERSION}\" != master && \"$(_versint \"${GIMME_GO_VERSION}\")\" < \"$(_versint 1.5)\" ]]; then\n\t\techo >&2 \"error: ${GIMME_ARCH} is not supported by this go version\"\n\t\techo >&2 \"try go1.5 or newer\"\n\t\texit 1\n\tfi\n\tif [[ \"${GIMME_HOSTOS}\" == \"linux\" && \"${GIMME_HOSTARCH}\" != \"${GIMME_ARCH}\" ]]; then\n\t\t: \"${GIMME_CC_FOR_TARGET:=\"aarch64-linux-gnu-gcc\"}\"\n\tfi\n\t;;\narm*) GIMME_ARCH=arm ;;\nesac\n\ncase \"${GIMME_HOSTARCH}\" in\nx86_64) GIMME_HOSTARCH=amd64 ;;\nx86) GIMME_HOSTARCH=386 ;;\narm64) ;;\narm*) GIMME_HOSTARCH=arm ;;\nesac\n\ncase \"${GIMME_GO_VERSION}\" in\nstable) GIMME_GO_VERSION=$(_get_curr_stable) ;;\noldstable) GIMME_GO_VERSION=$(_get_old_stable) ;;\nesac\n\n_assert_version_given \"$@\"\n\n((force_install)) && _wipe_version \"${GIMME_GO_VERSION}\"\n\nunset GOARCH\nunset GOBIN\nunset GOOS\nunset GOPATH\nunset GOROOT\nunset CGO_ENABLED\nunset CC_FOR_TARGET\n# GO111MODULE breaks build of Go itself\nunset GO111MODULE\n\nmkdir -p \"${GIMME_VERSION_PREFIX}\" \"${GIMME_ENV_PREFIX}\"\n# The envs dir stays small and provides a record of what had been installed\n# whereas the versions dir grows by hundreds of MB per version and is not\n# intended to support local modifications (as that subverts the point of gimme)\n# _and_ is a cache, so we're unilaterally declaring that the contents of\n# the versions dir should be excluded from system backups.\n_exclude_from_backups \"${GIMME_VERSION_PREFIX}\"\n\nGIMME_VERSION_PREFIX=\"$(_realpath \"${GIMME_VERSION_PREFIX}\")\"\nGIMME_ENV_PREFIX=\"$(_realpath \"${GIMME_ENV_PREFIX}\")\"\n\nif ! case \"${GIMME_TYPE}\" in\n\tbinary) _try_existing binary || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" ;;\n\tsource) _try_existing source || _try_source || _try_git ;;\n\tgit) _try_git ;;\n\tauto) _try_existing || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" || _try_source || _try_git ;;\n\t*)\n\t\techo >&2 \"I don't know how to '${GIMME_TYPE}'.\"\n\t\techo >&2 \"  Try 'auto', 'binary', 'source', or 'git'.\"\n\t\texit 1\n\t\t;;\n\tesac; then\n\techo >&2 \"I don't have any idea what to do with '${GIMME_GO_VERSION}'.\"\n\techo >&2 \"  (using download type '${GIMME_TYPE}')\"\n\texit 1\nfi\n"
  },
  {
    "path": "images/haproxy/Dockerfile",
    "content": "# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 image is a haproxy image + minimal config so the container will not exit\n# while we rewrite the config at runtime and signal haproxy to reload.\n\nARG BASE=\"debian:trixie-slim\"\nFROM ${BASE} AS build\n\n# NOTE: copyrights.tar.gz is a quirk of Kubernetes's debian-base image\n# We extract these here so we can grab the relevant files are easily\n# staged for copying into our final image.\nRUN [ ! -f /usr/share/copyrights.tar.gz ] || tar -C / -xzvf /usr/share/copyrights.tar.gz\n\n# install:\n# - haproxy (see: https://haproxy.debian.net/)\n# - bash (ldd is a bash script and debian-base removes bash)\n# - procps (for `kill` which kind needs)\nRUN apt update && \\\n    apt install -y --no-install-recommends haproxy \\\n      procps bash\n\n# copy in script for staging distro provided binary to distroless\nCOPY --chmod=0755 stage-binary-and-deps.sh /usr/local/bin/\n\n# stage everything for copying into the final image\n# NOTE: kind currently also uses \"mkdir\" and \"cp\" to write files within the container\n# TODO: mkdir especially should be unnecessary, with a little refactoring\n# NOTE: kill is used to signal haproxy to reload\nARG STAGE_DIR=\"/opt/stage\"\nRUN mkdir -p \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh haproxy \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh cp \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh mkdir \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh kill \"${STAGE_DIR}\" && \\\n    find \"${STAGE_DIR}\"\n\n################################################################################\n\n# See: https://github.com/GoogleContainerTools/distroless/tree/main/base\n# This has /etc/passwd, tzdata, cacerts\nFROM \"gcr.io/distroless/static-debian13\"\n\nARG STAGE_DIR=\"/opt/stage\"\n\n# copy staged binary + deps + copyright\nCOPY --from=build \"${STAGE_DIR}/\" /\n\n# add our minimal config\nCOPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg\n\n# below roughly matches the standard haproxy image\nSTOPSIGNAL SIGUSR1\nENTRYPOINT [\"haproxy\", \"-W\", \"-db\", \"-f\", \"/usr/local/etc/haproxy/haproxy.cfg\"]\n"
  },
  {
    "path": "images/haproxy/Makefile",
    "content": "include $(CURDIR)/../Makefile.common.in"
  },
  {
    "path": "images/haproxy/README.md",
    "content": "# haproxy\n\nThis image is used internally by kind to implement kubeadm's \"HA\" mode,\nspecifically to load balance the API server.\n\nWe cannot merely use the upstream haproxy image as haproxy will exit without\na minimal config, so we introduce one that will list on the intended port and\nhot reload it at runtime with the actual desired config.\n\n## Building\n\nYou can `make quick` in this directory to build a test image.\n\nTo push an actual image use `make push`.\n"
  },
  {
    "path": "images/haproxy/cloudbuild.yaml",
    "content": "# See https://cloud.google.com/cloud-build/docs/build-config\noptions:\n  substitution_option: ALLOW_LOOSE\n  machineType: E2_HIGHCPU_8\nsteps:\n- name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:latest\n  entrypoint: make\n  args: ['-C', 'images/haproxy', 'push']\n"
  },
  {
    "path": "images/haproxy/haproxy.cfg",
    "content": "# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# minimal config file to avoid haproxy exiting due to invalid / missing config\n# kind will rewrite this config at runtime\nglobal\n    # limit memory usage to approximately 18 MB\n    maxconn 100000\n\nfrontend controlPlane\n    bind 0.0.0.0:6443\n    mode tcp\n    default_backend kube-apiservers\n\nbackend kube-apiservers\n    mode tcp\n"
  },
  {
    "path": "images/haproxy/stage-binary-and-deps.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# USAGE: stage-binary-and-deps.sh haproxy /opt/stage\n#\n# Stages $1 and it's dependencies + their copyright files to $2\n#\n# This is intended to be used in a multi-stage docker build with a distroless/base\n# or distroless/cc image.\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# file_to_package identifies the debian package that provided the file $1\nfile_to_package() {\n    # `dpkg-query --search $file-pattern` outputs lines with the format: \"$package: $file-path\"\n    # where $file-path belongs to $package\n    # https://manpages.debian.org/jessie/dpkg/dpkg-query.1.en.html\n    (dpkg-query --search \"$(realpath \"${1}\")\") | cut -d':' -f1\n}\n\n# package_to_copyright gives the path to the copyright file for the package $1\npackage_to_copyright() {\n    echo \"/usr/share/doc/${1}/copyright\"\n}\n\n# stage_file stages the filepath $1 to $2, following symlinks\n# and staging copyrights\nstage_file() {\n    # /lib is a symlink to /usr/lib in debian 12, means we just stick to\n    # /usr/lib for all libraries to avoid separating symlinks with the actual binaries\n    # ditto /lib64\n    from=\"${1}\"\n    if [[ $from = /lib*/* ]]; then\n        from=\"/usr$from\"\n    fi\n    cp -a --parents \"${from}\" \"${2}\"\n\n    # recursively follow symlinks\n    if [[ -L \"${from}\" ]]; then\n        stage_file \"$(cd \"$(dirname \"${from}\")\"; realpath -s \"$(readlink \"${from}\")\")\" \"${2}\"\n    fi\n    # get the package so we can stage package metadata as well\n    package=\"$(file_to_package \"${from}\")\"\n\n    # files like /usr/lib/x86_64-linux-gnu/libc.so.6 will return no package\n    if [[ \"$package\" != \"\" ]]; then\n        # stage the copyright for the file\n        cp -a --parents \"$(package_to_copyright \"${package}\")\" \"${2}\"\n        # stage the package status mimicking bazel\n        # https://github.com/bazelbuild/rules_docker/commit/f5432b813e0a11491cf2bf83ff1a923706b36420\n        # instead of parsing the control file, we can just get the actual package status with dpkg\n        dpkg -s \"${package}\" > \"${2}/var/lib/dpkg/status.d/${package}\"\n    fi\n}\n\n# binary_to_libraries identifies the library files needed by the binary $1 with ldd\nbinary_to_libraries() {\n    # see: https://man7.org/linux/man-pages/man1/ldd.1.html\n    ldd \"${1}\" \\\n    `# strip the leading '${name} => ' if any so only '/lib-foo.so (0xf00)' remains` \\\n    | sed -E 's#.* => /#/#' \\\n    `# we want only the path remaining, not the (0x${LOCATION})` \\\n    | awk '{print $1}' \\\n    `# linux-vdso.so.1 is a special virtual shared object from the kernel` \\\n    `# see: http://man7.org/linux/man-pages/man7/vdso.7.html` \\\n    | grep -v 'linux-vdso.so.1'\n}\n\n# main script logic\nmain(){\n    local BINARY=$1\n    local STAGE_DIR=\"${2}/\"\n\n    # locate the path to the binary\n    local binary_path\n    binary_path=\"$(which \"${BINARY}\")\"\n\n    # ensure package metadata dir\n    mkdir -p \"${STAGE_DIR}\"/var/lib/dpkg/status.d/\n\n    # stage the binary itself\n    stage_file \"${binary_path}\" \"${STAGE_DIR}\"\n\n    # stage the dependencies of the binary\n    while IFS= read -r c_dep; do\n        stage_file \"${c_dep}\" \"${STAGE_DIR}\"\n    done < <(binary_to_libraries \"${binary_path}\")\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "images/kindnetd/Dockerfile",
    "content": "# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# first stage build kindnetd binary\n# NOTE: the actual go version will be overridden\nFROM --platform=$BUILDPLATFORM docker.io/library/golang:latest\nWORKDIR /go/src\nCOPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/\n# make deps fetching cacheable\nCOPY go.mod go.sum ./\n# set by makefile to .go-version\nARG GO_VERSION\nRUN eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && go mod download \\\n    && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest\n# build\nCOPY . .\nARG TARGETARCH\nRUN eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o ./kindnetd ./cmd/kindnetd \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/kindnetd\n\n# build real kindnetd image\nFROM registry.k8s.io/build-image/distroless-iptables:v0.8.7\nCOPY --from=0 --chown=root:root ./go/src/kindnetd /bin/kindnetd\nCOPY --from=0 /_LICENSES/* /LICENSES/\nCOPY --chmod=0644 files/LICENSES/* /LICENSES/*\nCMD [\"/bin/kindnetd\"]\n"
  },
  {
    "path": "images/kindnetd/LICENSE",
    "content": "                                 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": "images/kindnetd/Makefile",
    "content": "include $(CURDIR)/../Makefile.common.in"
  },
  {
    "path": "images/kindnetd/README.md",
    "content": "# kindnetd\n\n`kindnetd` is a simple networking daemon with the following responsibilities:\n\n- IP masquerade (of traffic leaving the nodes that is headed out of the cluster)\n- Ensuring netlink routes to pod CIDRs via the host node IP for each\n- Ensuring a simple CNI config based on the standard [ptp] / [host-local] [plugins] and the node's pod CIDR\n\nkindnetd is based on [aojea/kindnet] which is in turn based on [leblancd/kube-v6-test].\n\nWe use this to implement KIND's standard CNI / cluster networking configuration.\n\n## Building\n\ncd to this directory on mac / linux with docker installed and run `make quick`.\n\nTo push an image run `make push`.\n\n[ptp]: https://www.cni.dev/plugins/current/main/ptp/\n[host-local]: https://www.cni.dev/plugins/current/ipam/host-local/\n[plugins]: https://github.com/containernetworking/plugins\n[aojea/kindnet]: https://github.com/aojea/kindnet\n[leblancd/kube-v6-test]: https://github.com/leblancd/kube-v6-test/tree/master\n"
  },
  {
    "path": "images/kindnetd/cloudbuild.yaml",
    "content": "# See https://cloud.google.com/cloud-build/docs/build-config\noptions:\n  substitution_option: ALLOW_LOOSE\n  machineType: E2_HIGHCPU_32\nsteps:\n- name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:latest\n  entrypoint: make\n  args: ['-C', 'images/kindnetd', 'push']\n"
  },
  {
    "path": "images/kindnetd/cmd/kindnetd/cni.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tstdnet \"net\"\n\t\"os\"\n\t\"reflect\"\n\t\"text/template\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n/* cni config management */\n\n// CNIConfigInputs is supplied to the CNI config template\ntype CNIConfigInputs struct {\n\tPodCIDRs      []string\n\tDefaultRoutes []string\n\tMtu           int\n}\n\n// ComputeCNIConfigInputs computes the template inputs for CNIConfigWriter\nfunc ComputeCNIConfigInputs(node *corev1.Node) CNIConfigInputs {\n\n\tdefaultRoutes := []string{\"0.0.0.0/0\", \"::/0\"}\n\t// check if is a dualstack cluster\n\tif len(node.Spec.PodCIDRs) > 1 {\n\t\treturn CNIConfigInputs{\n\t\t\tPodCIDRs:      node.Spec.PodCIDRs,\n\t\t\tDefaultRoutes: defaultRoutes,\n\t\t}\n\t}\n\t// the cluster is single stack\n\t// we use the legacy node.Spec.PodCIDR for backwards compatibility\n\tpodCIDRs := []string{node.Spec.PodCIDR}\n\t// This is a single stack cluster\n\tdefaultRoute := defaultRoutes[:1]\n\tif isIPv6CIDRString(podCIDRs[0]) {\n\t\tdefaultRoute = defaultRoutes[1:]\n\t}\n\treturn CNIConfigInputs{\n\t\tPodCIDRs:      podCIDRs,\n\t\tDefaultRoutes: defaultRoute,\n\t}\n}\n\n// computeBridgeMTU finds the mtu for the eth0 interface\n// otherwise it defaults to ptp default behavior of being set by kernel\nfunc computeBridgeMTU() (int, error) {\n\tinterfaces, err := stdnet.Interfaces()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfor _, inter := range interfaces {\n\t\tif inter.Name == \"eth0\" {\n\t\t\treturn inter.MTU, nil\n\t\t}\n\t}\n\treturn 0, errors.New(\"Found no eth0 device\")\n}\n\n// cniConfigPath is where kindnetd will write the computed CNI config\nconst cniConfigPath = \"/etc/cni/net.d/10-kindnet.conflist\"\n\nconst cniConfigTemplate = `\n{\n\t\"cniVersion\": \"0.3.1\",\n\t\"name\": \"kindnet\",\n\t\"plugins\": [\n\t{\n\t\t\"type\": \"ptp\",\n\t\t\"ipMasq\": false,\n\t\t\"ipam\": {\n\t\t\t\"type\": \"host-local\",\n\t\t\t\"dataDir\": \"/run/cni-ipam-state\",\n\t\t\t\"routes\": [\n\t\t\t\t{{$first := true}}\n\t\t\t\t{{- range $route := .DefaultRoutes}}\n\t\t\t\t{{if $first}}{{$first = false}}{{else}},{{end}}\n\t\t\t\t{ \"dst\": \"{{ $route }}\" }\n\t\t\t\t{{- end}}\n\t\t\t],\n\t\t\t\"ranges\": [\n\t\t\t\t{{$first := true}}\n\t\t\t\t{{- range $cidr := .PodCIDRs}}\n\t\t\t\t{{if $first}}{{$first = false}}{{else}},{{end}}\n\t\t\t\t[ { \"subnet\": \"{{ $cidr }}\" } ]\n\t\t\t\t{{- end}}\n\t\t\t]\n\t\t}\n\t\t{{if .Mtu}},\n\t\t\"mtu\": {{ .Mtu }}\n\t\t{{end}}\n\t},\n\t{\n\t\t\"type\": \"portmap\",\n\t\t\"capabilities\": {\n\t\t\t\"portMappings\": true\n\t\t}\n\t}\n\t]\n}\n`\n\n// CNIConfigWriter no-ops re-writing config with the same inputs\n// NOTE: should only be called from a single goroutine\ntype CNIConfigWriter struct {\n\tpath       string\n\tlastInputs CNIConfigInputs\n\tmtu        int\n}\n\n// Write will write the config based on\nfunc (c *CNIConfigWriter) Write(inputs CNIConfigInputs) error {\n\tinputs.Mtu = c.mtu\n\tif reflect.DeepEqual(inputs, c.lastInputs) {\n\t\treturn nil\n\t}\n\n\t// use an extension not recognized by CNI to write the contents initially\n\t// https://github.com/containerd/go-cni/blob/891c2a41e18144b2d7921f971d6c9789a68046b2/opts.go#L170\n\t// then we can rename to atomically make the file appear\n\tf, err := os.Create(c.path + \".temp\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// actually write the config\n\tif err := writeCNIConfig(f, cniConfigTemplate, inputs); err != nil {\n\t\tf.Close()\n\t\tos.Remove(f.Name())\n\t\treturn err\n\t}\n\t_ = f.Sync()\n\t_ = f.Close()\n\n\t// then we can rename to the target config path\n\tif err := os.Rename(f.Name(), c.path); err != nil {\n\t\treturn err\n\t}\n\n\t// we're safely done now, record the inputs\n\tc.lastInputs = inputs\n\treturn nil\n}\n\nfunc writeCNIConfig(w io.Writer, rawTemplate string, data CNIConfigInputs) error {\n\tt, err := template.New(\"cni-json\").Parse(rawTemplate)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse cni template: %w\", err)\n\t}\n\treturn t.Execute(w, &data)\n}\n"
  },
  {
    "path": "images/kindnetd/cmd/kindnetd/main.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package main is the main entrypoint for kindnetd.\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/util/sets\"\n\t\"k8s.io/client-go/informers\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/klog/v2\"\n\t\"sigs.k8s.io/kube-network-policies/pkg/networkpolicy\"\n)\n\nconst (\n\tprobeTCPtimeout = 1 * time.Second\n)\n\n// kindnetd is a simple networking daemon to complete kind's CNI implementation\n// kindnetd will ensure routes to the other node's PodCIDR via their InternalIP\n// kindnetd will ensure pod to pod communication will not be masquerade\n// kindnetd will also write a templated cni config supplied with PodCIDR\n//\n// input envs:\n// - HOST_IP: should be populated by downward API\n// - POD_IP: should be populated by downward API\n// - CNI_CONFIG_TEMPLATE: the cni .conflist template, run with {{ .PodCIDR }}\n// - CONTROL_PLANE_ENDPOINT: control-plane endpoint format host:port\n\n// TODO: improve logging & error handling\n\n// IPFamily defines kindnet networking operating model\ntype IPFamily string\n\nconst (\n\t// IPv4Family sets IPFamily to ipv4\n\tIPv4Family IPFamily = \"ipv4\"\n\t// IPv6Family sets IPFamily to ipv6\n\tIPv6Family IPFamily = \"ipv6\"\n\t// DualStackFamily sets ClusterIPFamily to DualStack\n\tDualStackFamily IPFamily = \"dualstack\"\n)\n\nfunc main() {\n\t// enable logging\n\tklog.InitFlags(nil)\n\t_ = flag.Set(\"logtostderr\", \"true\")\n\tflag.Parse()\n\n\t// create a Kubernetes client\n\tconfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\t// use protobuf to improve performance\n\tconfig.AcceptContentTypes = \"application/vnd.kubernetes.protobuf,application/json\"\n\tconfig.ContentType = \"application/vnd.kubernetes.protobuf\"\n\n\t// override the internal apiserver endpoint to avoid\n\t// waiting for kube-proxy to install the services rules.\n\t// If the endpoint is not reachable, fallback the internal endpoint\n\tcontrolPlaneEndpoint := os.Getenv(\"CONTROL_PLANE_ENDPOINT\")\n\tif controlPlaneEndpoint != \"\" {\n\t\t// check that the apiserver is reachable before continue\n\t\t// to fail fast and avoid waiting until the client operations timeout\n\t\tvar ok bool\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tok = probeTCP(controlPlaneEndpoint, probeTCPtimeout)\n\t\t\tif ok {\n\t\t\t\tconfig.Host = \"https://\" + controlPlaneEndpoint\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tklog.Infof(\"apiserver not reachable, attempt %d ... retrying\", i)\n\t\t\ttime.Sleep(time.Second * time.Duration(i))\n\t\t}\n\t}\n\t// create the clientset to connect the apiserver\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tklog.Infof(\"connected to apiserver: %s\", config.Host)\n\n\t// trap Ctrl+C and call cancel on the context\n\tctx := context.Background()\n\tctx, cancel := context.WithCancel(ctx)\n\n\t// Enable signal handler\n\tsignalCh := make(chan os.Signal, 2)\n\tdefer func() {\n\t\tclose(signalCh)\n\t\tcancel()\n\t}()\n\tsignal.Notify(signalCh, os.Interrupt, unix.SIGINT)\n\n\tgo func() {\n\t\tselect {\n\t\tcase <-signalCh:\n\t\t\tklog.Infof(\"Exiting: received signal\")\n\t\t\tcancel()\n\t\tcase <-ctx.Done():\n\t\t}\n\t}()\n\n\tinformersFactory := informers.NewSharedInformerFactory(clientset, 0)\n\tnodeInformer := informersFactory.Core().V1().Nodes()\n\tnodeLister := nodeInformer.Lister()\n\n\t// obtain the host and pod ip addresses\n\t// if both ips are different we are not using the host network\n\thostIP, podIP := os.Getenv(\"HOST_IP\"), os.Getenv(\"POD_IP\")\n\tklog.Infof(\"hostIP = %s\\npodIP = %s\\n\", hostIP, podIP)\n\tif hostIP != podIP {\n\t\tklog.Warningf(\n\t\t\t\"hostIP(= %q) != podIP(= %q) but must be running with host network: \",\n\t\t\thostIP, podIP,\n\t\t)\n\t}\n\n\tmtu, err := computeBridgeMTU()\n\tklog.Infof(\"setting mtu %d for CNI \\n\", mtu)\n\tif err != nil {\n\t\tklog.Infof(\"Failed to get MTU size from interface eth0, using kernel default MTU size error:%v\", err)\n\t}\n\t// used to track if the cni config inputs changed and write the config\n\tcniConfigWriter := &CNIConfigWriter{\n\t\tpath: cniConfigPath,\n\t\tmtu:  mtu,\n\t}\n\n\t// enforce ip masquerade rules\n\tpodSubnetEnv := os.Getenv(\"POD_SUBNET\")\n\tif podSubnetEnv == \"\" {\n\t\tpanic(\"missing environment variable POD_SUBNET\")\n\t}\n\tpodSubnetEnv = strings.TrimSpace(podSubnetEnv)\n\tpodSubnets := strings.Split(podSubnetEnv, \",\")\n\tclusterIPv4Subnets, clusterIPv6Subnets := splitCIDRs(podSubnets)\n\n\t// detect the cluster IP family based on the Cluster CIDR aka PodSubnet\n\tvar ipFamily IPFamily\n\tif len(clusterIPv4Subnets) > 0 && len(clusterIPv6Subnets) > 0 {\n\t\tipFamily = DualStackFamily\n\t} else if len(clusterIPv6Subnets) > 0 {\n\t\tipFamily = IPv6Family\n\t} else if len(clusterIPv4Subnets) > 0 {\n\t\tipFamily = IPv4Family\n\t} else {\n\t\tpanic(fmt.Sprintf(\"podSubnets ClusterCIDR/Pod_Subnet: %v\", podSubnetEnv))\n\t}\n\tklog.Infof(\"kindnetd IP family: %q\", ipFamily)\n\n\t// create an ipMasqAgent for IPv4\n\tif len(clusterIPv4Subnets) > 0 {\n\t\tklog.Infof(\"noMask IPv4 subnets: %v\", clusterIPv4Subnets)\n\t\tmasqAgentIPv4, err := NewIPMasqAgent(false, clusterIPv4Subnets)\n\t\tif err != nil {\n\t\t\tpanic(err.Error())\n\t\t}\n\t\tgo func() {\n\t\t\tif err := masqAgentIPv4.SyncRulesForever(ctx, time.Second*60); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}()\n\t}\n\n\t// create an ipMasqAgent for IPv6\n\tif len(clusterIPv6Subnets) > 0 {\n\t\tklog.Infof(\"noMask IPv6 subnets: %v\", clusterIPv6Subnets)\n\t\tmasqAgentIPv6, err := NewIPMasqAgent(true, clusterIPv6Subnets)\n\t\tif err != nil {\n\t\t\tpanic(err.Error())\n\t\t}\n\n\t\tgo func() {\n\t\t\tif err := masqAgentIPv6.SyncRulesForever(ctx, time.Second*60); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t}()\n\t}\n\n\t// setup nodes reconcile function, closes over arguments\n\treconcileNodes := makeNodesReconciler(cniConfigWriter, hostIP, ipFamily)\n\n\t// network policies\n\n\t// on kind nodes the hostname matches the node name\n\tnodeName, err := os.Hostname()\n\tif err != nil {\n\t\tklog.Fatalf(\"couldn't determine hostname: %v\", err)\n\t}\n\n\tcfg := networkpolicy.Config{\n\t\tFailOpen:            true,\n\t\tQueueID:             101,\n\t\tNodeName:            nodeName,\n\t\tNetfilterBug1766Fix: true,\n\t\tNFTableName:         \"kindnet-network-policies\",\n\t}\n\n\tnetworkPolicyController, err := networkpolicy.NewController(\n\t\tclientset,\n\t\tinformersFactory.Networking().V1().NetworkPolicies(),\n\t\tinformersFactory.Core().V1().Namespaces(),\n\t\tinformersFactory.Core().V1().Pods(),\n\t\tnodeInformer,\n\t\tnil,\n\t\tnil,\n\t\tnil,\n\t\tcfg)\n\tif err != nil {\n\t\tklog.Infof(\"Error creating network policy controller: %v, skipping network policies\", err)\n\t} else {\n\t\tgo func() {\n\t\t\t_ = networkPolicyController.Run(ctx)\n\t\t}()\n\t}\n\n\t// main control loop\n\tinformersFactory.Start(ctx.Done())\n\tticker := time.NewTicker(10 * time.Second)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tvar nodes []*corev1.Node\n\t\tvar err error\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tnodes, err = nodeLister.List(labels.Everything())\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tklog.Infof(\"Failed to get nodes, retrying after error: %v\", err)\n\t\t\ttime.Sleep(time.Second * time.Duration(i))\n\t\t}\n\t\tif err != nil {\n\t\t\tpanic(\"Reached maximum retries obtaining node list: \" + err.Error())\n\t\t}\n\n\t\t// reconcile the nodes with retries\n\t\tfor i := 0; i < 5; i++ {\n\t\t\terr = reconcileNodes(nodes)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tklog.Infof(\"Failed to reconcile routes, retrying after error: %v\", err)\n\t\t\ttime.Sleep(time.Second * time.Duration(i))\n\t\t}\n\t\tif err != nil {\n\t\t\tpanic(\"Maximum retries reconciling node routes: \" + err.Error())\n\t\t}\n\n\t\t// rate limit\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\t// grace period to cleanup resources\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t}\n\t}\n}\n\n// nodeNodesReconciler returns a reconciliation func for nodes\nfunc makeNodesReconciler(cniConfig *CNIConfigWriter, hostIP string, ipFamily IPFamily) func([]*corev1.Node) error {\n\t// reconciles a node\n\treconcileNode := func(node *corev1.Node) error {\n\t\t// first get this node's IPs\n\t\t// we don't support more than one IP address per IP family for simplification\n\t\tnodeIPs := internalIPs(node)\n\t\tklog.Infof(\"Handling node with IPs: %v\\n\", nodeIPs)\n\t\t// This is our node. We don't need to add routes,\n\t\t// but we might need to update the cni config\n\t\tif nodeIPs.Has(hostIP) {\n\t\t\tklog.Info(\"handling current node\\n\")\n\t\t\t// compute the current cni config inputs\n\t\t\tif err := cniConfig.Write(\n\t\t\t\tComputeCNIConfigInputs(node),\n\t\t\t); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// we're done handling this node\n\t\t\treturn nil\n\t\t}\n\n\t\t// This is another node. Add routes to the POD subnets in the other nodes\n\t\t// don't do anything unless there is a non-empty PodCIDR\n\t\tvar podCIDRs []string\n\t\tif ipFamily == DualStackFamily {\n\t\t\tpodCIDRs = node.Spec.PodCIDRs\n\t\t} else if node.Spec.PodCIDR != \"\" {\n\t\t\tpodCIDRs = []string{node.Spec.PodCIDR}\n\t\t}\n\t\tif len(podCIDRs) == 0 {\n\t\t\tfmt.Printf(\"Node %v has no CIDR, ignoring\\n\", node.Name)\n\t\t\treturn nil\n\t\t}\n\t\tklog.Infof(\"Node %v has CIDR %s \\n\", node.Name, podCIDRs)\n\t\tpodCIDRsv4, podCIDRsv6 := splitCIDRs(podCIDRs)\n\n\t\t// obtain the PodCIDR gateway\n\t\tvar nodeIPv4, nodeIPv6 string\n\t\tfor _, ip := range nodeIPs.UnsortedList() {\n\t\t\tif isIPv6String(ip) {\n\t\t\t\tnodeIPv6 = ip\n\t\t\t} else {\n\t\t\t\tnodeIPv4 = ip\n\t\t\t}\n\t\t}\n\n\t\tif nodeIPv4 != \"\" && len(podCIDRsv4) > 0 {\n\t\t\tif err := syncRoute(nodeIPv4, podCIDRsv4); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif nodeIPv6 != \"\" && len(podCIDRsv6) > 0 {\n\t\t\tif err := syncRoute(nodeIPv6, podCIDRsv6); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// return a reconciler for all the nodes\n\treturn func(nodes []*corev1.Node) error {\n\t\tfor _, node := range nodes {\n\t\t\tif err := reconcileNode(node); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// internalIPs returns the internal IP addresses for node\nfunc internalIPs(node *corev1.Node) sets.Set[string] {\n\tips := sets.New[string]()\n\t// check the node.Status.Addresses\n\tfor _, address := range node.Status.Addresses {\n\t\tif address.Type == \"InternalIP\" {\n\t\t\tips.Insert(address.Address)\n\t\t}\n\t}\n\treturn ips\n}\n\n// splitCIDRs given a slice of strings with CIDRs it returns 2 slice of strings per IP family\n// The order returned is always v4 v6\nfunc splitCIDRs(cidrs []string) ([]string, []string) {\n\tvar v4subnets, v6subnets []string\n\tfor _, subnet := range cidrs {\n\t\tif isIPv6CIDRString(subnet) {\n\t\t\tv6subnets = append(v6subnets, subnet)\n\t\t} else {\n\t\t\tv4subnets = append(v4subnets, subnet)\n\t\t}\n\t}\n\treturn v4subnets, v6subnets\n}\n\n// Modified from agnhost connect command in k/k\n// https://github.com/kubernetes/kubernetes/blob/c241a237f9a635286c76c20d07b103a663b1cfa4/test/images/agnhost/connect/connect.go#L66\nfunc probeTCP(address string, timeout time.Duration) bool {\n\tklog.Infof(\"probe TCP address %s\", address)\n\tif _, err := net.ResolveTCPAddr(\"tcp\", address); err != nil {\n\t\tklog.Warningf(\"DNS problem %s: %v\", address, err)\n\t\treturn false\n\t}\n\n\tconn, err := net.DialTimeout(\"tcp\", address, timeout)\n\tif err == nil {\n\t\tconn.Close()\n\t\treturn true\n\t}\n\tif opErr, ok := err.(*net.OpError); ok {\n\t\tif opErr.Timeout() {\n\t\t\tklog.Warningf(\"TIMEOUT %s\", address)\n\t\t} else if syscallErr, ok := opErr.Err.(*os.SyscallError); ok {\n\t\t\tif syscallErr.Err == syscall.ECONNREFUSED {\n\t\t\t\tklog.Warningf(\"REFUSED %s\", address)\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tklog.Warningf(\"OTHER %s: %v\", address, err)\n\treturn false\n}\n\n// isIPv6String returns if ip is IPv6.\nfunc isIPv6String(ip string) bool {\n\tnetIP := net.ParseIP(ip)\n\treturn netIP != nil && netIP.To4() == nil\n}\n\n// isIPv6CIDRString returns if cidr is IPv6.\n// This assumes cidr is a valid CIDR.\nfunc isIPv6CIDRString(cidr string) bool {\n\tip, _, _ := net.ParseCIDR(cidr)\n\treturn ip != nil && ip.To4() == nil\n}\n"
  },
  {
    "path": "images/kindnetd/cmd/kindnetd/masq.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/coreos/go-iptables/iptables\"\n)\n\n// NewIPMasqAgent returns a new IPMasqAgent\nfunc NewIPMasqAgent(ipv6 bool, noMasqueradeCIDRs []string) (*IPMasqAgent, error) {\n\tprotocol := iptables.ProtocolIPv4\n\tif ipv6 {\n\t\tprotocol = iptables.ProtocolIPv6\n\t}\n\tipt, err := iptables.NewWithProtocol(protocol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// TODO: validate cidrs\n\treturn &IPMasqAgent{\n\t\tiptables:          ipt,\n\t\tmasqChain:         masqChainName,\n\t\tnoMasqueradeCIDRs: noMasqueradeCIDRs,\n\t}, nil\n}\n\n// IPMasqAgent is based on https://github.com/kubernetes-incubator/ip-masq-agent\n// but collapsed into kindnetd and made ipv6 aware in an opinionated and simplified\n// fashion using \"github.com/coreos/go-iptables\"\ntype IPMasqAgent struct {\n\tiptables          *iptables.IPTables\n\tmasqChain         string\n\tnoMasqueradeCIDRs []string\n}\n\n// SyncRulesForever syncs ip masquerade rules forever\n// these rules only needs to be installed once, but we run it periodically to check that are\n// not deleted by an external program. It fails if can't sync the rules during 3 iterations\nfunc (ma *IPMasqAgent) SyncRulesForever(ctx context.Context, interval time.Duration) error {\n\tvar errs []error\n\tticker := time.NewTicker(interval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tif err := ma.SyncRules(); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"failed to synchronize rules at %s: %v\", time.Now(), err))\n\t\t\tif len(errs) > 3 {\n\t\t\t\treturn fmt.Errorf(\"Can't synchronize rules after 3 attempts: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\terrs = errs[:0]\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn errors.Join(errs...)\n\t\tcase <-ticker.C:\n\t\t}\n\t}\n}\n\n// name of nat chain for iptables masquerade rules\nconst masqChainName = \"KIND-MASQ-AGENT\"\n\n// SyncRules syncs ip masquerade rules\nfunc (ma *IPMasqAgent) SyncRules() error {\n\t// make sure our custom chain for non-masquerade exists\n\texists := false\n\tchains, err := ma.iptables.ListChains(\"nat\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list chains: %v\", err)\n\t}\n\tfor _, ch := range chains {\n\t\tif ch == ma.masqChain {\n\t\t\texists = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !exists {\n\t\tif err = ma.iptables.NewChain(\"nat\", ma.masqChain); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Packets to this network should not be masquerade, pods should be able to talk to other pods\n\tfor _, cidr := range ma.noMasqueradeCIDRs {\n\t\tif err := ma.iptables.AppendUnique(\"nat\", ma.masqChain, \"-d\", cidr, \"-j\", \"RETURN\", \"-m\", \"comment\", \"--comment\", \"kind-masq-agent: local traffic is not subject to MASQUERADE\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Masquerade all the other traffic\n\tif err := ma.iptables.AppendUnique(\"nat\", ma.masqChain, \"-j\", \"MASQUERADE\", \"-m\", \"comment\", \"--comment\", \"kind-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain)\"); err != nil {\n\t\treturn err\n\t}\n\n\t// Send all non-LOCAL destination traffic to our custom KIND-MASQ-AGENT chain\n\treturn ma.iptables.AppendUnique(\"nat\", \"POSTROUTING\", \"-m\", \"addrtype\", \"!\", \"--dst-type\", \"LOCAL\", \"-j\", ma.masqChain, \"-m\", \"comment\", \"--comment\", \"kind-masq-agent: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom KIND-MASQ-AGENT chain\")\n}\n"
  },
  {
    "path": "images/kindnetd/cmd/kindnetd/routes.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"net\"\n\n\t\"github.com/vishvananda/netlink\"\n\t\"github.com/vishvananda/netlink/nl\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc syncRoute(nodeIP string, podCIDRs []string) error {\n\tip := net.ParseIP(nodeIP)\n\n\tfor _, podCIDR := range podCIDRs {\n\t\t// parse subnet\n\t\tdst, err := netlink.ParseIPNet(podCIDR)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Declare the wanted route.\n\t\trouteToDst := netlink.Route{Dst: dst, Gw: ip}\n\t\t// List all routes which have the same dst set.\n\t\t// RouteListFiltered ignores the gw for filtering because of the passed filterMask.\n\t\troutes, err := netlink.RouteListFiltered(nl.GetIPFamily(ip), &routeToDst, netlink.RT_FILTER_DST)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Check if the wanted route exists and delete wrong routes\n\t\tfound := false\n\t\tfor _, route := range routes {\n\t\t\tif route.Gw.Equal(ip) {\n\t\t\t\tfound = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Delete wrong route because of invalid gateway.\n\t\t\tklog.Infof(\"Removing invalid route %v\\n\", route)\n\t\t\tif err := netlink.RouteDel(&route); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// Add route if not present\n\t\tif !found {\n\t\t\tklog.Infof(\"Adding route %v \\n\", routeToDst)\n\t\t\tif err := netlink.RouteAdd(&routeToDst); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "images/kindnetd/files/LICENSES/README.txt",
    "content": "This directory contains license files and notices from binaries built for this\nimage and the dependencies of those binaries,\nas collected by https://github.com/google/go-licenses.\n"
  },
  {
    "path": "images/kindnetd/go.mod",
    "content": "module sigs.k8s.io/kind/images/kindnetd\n\ngo 1.24.0\n\nrequire (\n\tgithub.com/coreos/go-iptables v0.8.0\n\tgithub.com/vishvananda/netlink v1.3.1\n\tgolang.org/x/sys v0.33.0\n\tk8s.io/api v0.33.0\n\tk8s.io/apimachinery v0.33.0\n\tk8s.io/client-go v0.33.0\n\tk8s.io/klog/v2 v2.130.1\n\tsigs.k8s.io/kube-network-policies v0.8.0\n)\n\nrequire (\n\tgithub.com/armon/go-radix v1.0.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/containerd/nri v0.9.0 // indirect\n\tgithub.com/containerd/ttrpc v1.2.7 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/florianl/go-nfqueue v1.3.2 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.8.0 // indirect\n\tgithub.com/go-logr/logr v1.4.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.1 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.1 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/google/gnostic-models v0.6.9 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/nftables v0.3.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/knqyf263/go-plugin v0.9.0 // indirect\n\tgithub.com/mailru/easyjson v0.9.0 // indirect\n\tgithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect\n\tgithub.com/mdlayher/socket v0.5.1 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/opencontainers/runtime-spec v1.2.1 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/prometheus/client_golang v1.22.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.63.0 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/tetratelabs/wazero v1.9.0 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgolang.org/x/net v0.40.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.14.0 // indirect\n\tgolang.org/x/term v0.32.0 // indirect\n\tgolang.org/x/text v0.25.0 // indirect\n\tgolang.org/x/time v0.11.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect\n\tgoogle.golang.org/grpc v1.72.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/cri-api v0.33.0 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect\n\tk8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect\n\tsigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect\n\tsigs.k8s.io/network-policy-api v0.1.6-0.20250401132235-45061d10895e // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect\n\tsigs.k8s.io/yaml v1.4.0 // indirect\n)\n"
  },
  {
    "path": "images/kindnetd/go.sum",
    "content": "github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\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/nri v0.9.0 h1:jribDJs/oQ95vLO4Yn19HKFYriZGWKiG6nKWjl9Y/x4=\ngithub.com/containerd/nri v0.9.0/go.mod h1:sDRoMy5U4YolsWthg7TjTffAwPb6LEr//83O+D3xVU4=\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/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=\ngithub.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=\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/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/florianl/go-nfqueue v1.3.2 h1:8DPzhKJHywpHJAE/4ktgcqveCL7qmMLsEsVD68C4x4I=\ngithub.com/florianl/go-nfqueue v1.3.2/go.mod h1:eSnAor2YCfMCVYrVNEhkLGN/r1L+J4uDjc0EUy0tfq4=\ngithub.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=\ngithub.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/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-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=\ngithub.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=\ngithub.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=\ngithub.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=\ngithub.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=\ngithub.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=\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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=\ngithub.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.9/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=\ngithub.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=\ngithub.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=\ngithub.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=\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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\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/knqyf263/go-plugin v0.9.0 h1:CQs2+lOPIlkZVtcb835ZYDEoyyWJWLbSTWeCs0EwTwI=\ngithub.com/knqyf263/go-plugin v0.9.0/go.mod h1:2z5lCO1/pez6qGo8CvCxSlBFSEat4MEp1DrnA+f7w8Q=\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/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=\ngithub.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=\ngithub.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=\ngithub.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=\ngithub.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=\ngithub.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=\ngithub.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=\ngithub.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=\ngithub.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=\ngithub.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=\ngithub.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\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_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=\ngithub.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=\ngithub.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=\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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=\ngithub.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=\ngithub.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=\ngithub.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=\ngithub.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=\ngithub.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=\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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=\ngo.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=\ngo.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=\ngo.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=\ngo.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=\ngo.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=\ngo.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=\ngo.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=\ngo.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=\ngo.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=\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/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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\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-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=\ngolang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=\ngolang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\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-20210423082822-04245dca01da/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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=\ngolang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=\ngolang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=\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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=\ngolang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=\ngolang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=\ngolang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=\ngolang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=\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=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=\ngoogle.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=\ngopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\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=\nk8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=\nk8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=\nk8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=\nk8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=\nk8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=\nk8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=\nk8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k=\nk8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI=\nk8s.io/cri-api v0.33.0 h1:YyGNgWmuSREqFPlP3XCstlHLilYdW898KwtKoaTYwBs=\nk8s.io/cri-api v0.33.0/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=\nk8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=\nk8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg=\nk8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=\nsigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/kube-network-policies v0.8.0 h1:dzecRNzdmg+VmHEQWTIjxnCgf412J+8CKHqbKgCiWkk=\nsigs.k8s.io/kube-network-policies v0.8.0/go.mod h1:QNAmb/exAiL032oNq0/QhfYl5H+jEFFDF0qVXpIOkeI=\nsigs.k8s.io/network-policy-api v0.1.6-0.20250401132235-45061d10895e h1:b5Vm5aa0I37gToQmFhQ+gQMZVsPsxSiJzxZWtSCs99w=\nsigs.k8s.io/network-policy-api v0.1.6-0.20250401132235-45061d10895e/go.mod h1:8J+z3UB9HgeqbmYprl6OKIobEDv27A3KRHPcteJ61rw=\nsigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=\nsigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "images/kindnetd/scripts/third_party/gimme/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2018 gimme contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "images/kindnetd/scripts/third_party/gimme/README.md",
    "content": "# gimme\n\nThis is an unmodified copy of [gimme], so we don't have to download it\nfrom the internet.\n\n[gimme]: https://github.com/travis-ci/gimme"
  },
  {
    "path": "images/kindnetd/scripts/third_party/gimme/gimme",
    "content": "#!/usr/bin/env bash\n# vim:noexpandtab:ts=2:sw=2:\n#\n#+  Usage: $(basename $0) [flags] [go-version] [version-prefix]\n#+  -\n#+  Version: ${GIMME_VERSION}\n#+  Copyright: ${GIMME_COPYRIGHT}\n#+  License URL: ${GIMME_LICENSE_URL}\n#+  -\n#+  Install go!  There are multiple types of installations available, with 'auto' being the default.\n#+  If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing\n#+  go installation.  This behavior may be disabled by providing '-f/--force/force' as first positional\n#+  argument.\n#+  -\n#+  Option flags:\n#+          -h --help help - show this help text and exit\n#+    -V --version version - show the version only and exit\n#+        -f --force force - remove the existing go installation if present prior to install\n#+          -l --list list - list installed go versions and exit\n#+        -k --known known - list known go versions and exit\n#+    --force-known-update - when used with --known, ignores the cache and updates\n#+    -r --resolve resolve - resolve a version specifier to a version, show that and exit\n#+  -\n#+  Influential env vars:\n#+  -\n#+        GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg)\n#+    GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}',\n#+                           may be given as second positional arg)\n#+              GIMME_ARCH - arch to install (default '${GIMME_ARCH}')\n#+        GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}')\n#+        GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}')\n#+     GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}')\n#+                GIMME_OS - os to install (default '${GIMME_OS}')\n#+               GIMME_TMP - temp directory (default '${GIMME_TMP}')\n#+              GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git')\n#+                           (default '${GIMME_TYPE}')\n#+      GIMME_INSTALL_RACE - install race directory after compile if non-empty.\n#+                           If the install type is 'binary', this option is ignored.\n#+             GIMME_DEBUG - enable tracing if non-empty\n#+      GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host\n#+        GIMME_SILENT_ENV - omit the 'go version' line from env file\n#+       GIMME_CGO_ENABLED - enable build of cgo support\n#+     GIMME_CC_FOR_TARGET - cross compiler for cgo support\n#+     GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}')\n#+        GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}')\n#+   GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}')\n#+  -\n#\nset -e\nshopt -s nullglob\nshopt -s dotglob\nshopt -s extglob\nset -o pipefail\n\n[[ ${GIMME_DEBUG} ]] && set -x\n\nreadonly GIMME_VERSION=\"v1.5.4\"\nreadonly GIMME_COPYRIGHT=\"Copyright (c) 2015-2020 gimme contributors\"\nreadonly GIMME_LICENSE_URL=\"https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE\"\nexport GIMME_VERSION\nexport GIMME_COPYRIGHT\nexport GIMME_LICENSE_URL\n\nprogram_name=\"$(basename \"$0\")\"\n# shellcheck disable=SC1117\nwarn() { printf >&2 \"%s: %s\\n\" \"${program_name}\" \"${*}\"; }\ndie() {\n\twarn \"$@\"\n\texit 1\n}\n\n# We don't want to go around hitting Google's servers with requests for\n# files named HEAD@{date}.tar so we only try binary/source downloads if\n# it looks like a plausible name to us.\n# We don't need to support 0. releases of Go.\n# We don't support 5 digit major-versions of Go (limit back-tracking in RE).\n# We don't support very long versions\n#   (both to avoid annoying download server operators with attacks and\n#    because regexp backtracking can be pathological).\n# Per _assert_version_given we do assume 2.0 not 2\nALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\\.[0-9][0-9a-zA-Z_-]{0,9})+$'\n#\n# The main path which allowed these to leak upstream before has been closed\n# but a valid git repo tag or branch-name will still reach the point of\n# being _tried_ upstream.\n\n# _do_curl \"url\" \"file\"\n_do_curl() {\n\tmkdir -p \"$(dirname \"${2}\")\"\n\n\tif command -v curl >/dev/null; then\n\t\tcurl -sSLf \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v wget >/dev/null; then\n\t\twget -q \"${1}\" -O \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v fetch >/dev/null; then\n\t\tfetch -q \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\techo >&2 'error: no curl, wget, or fetch found'\n\texit 1\n}\n\n# _sha256sum \"file\"\n_sha256sum() {\n\tif command -v sha256sum &>/dev/null; then\n\t\tsha256sum \"$@\"\n\telif command -v gsha256sum &>/dev/null; then\n\t\tgsha256sum \"$@\"\n\telse\n\t\tshasum -a 256 \"$@\"\n\tfi\n}\n\n# sort versions, handling 1.10 after 1.9, not before 1.2\n# FreeBSD sort has --version-sort, none of the others do\n# Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty\nif sort --version-sort </dev/null &>/dev/null; then\n\t_version_sort() { sort --version-sort; }\nelse\n\t_version_sort() {\n\t\t# If we go to four-digit minor or patch versions, then extend the padding here\n\t\t# (but in such a world, perhaps --version-sort will have become standard by then?)\n\t\tsed -E 's/\\.([0-9](\\.|$))/.00\\1/g; s/\\.([0-9][0-9](\\.|$))/.0\\1/g' |\n\t\t\tsort --general-numeric-sort |\n\t\t\tsed 's/\\.00*/./g'\n\t}\nfi\n\n# _do_curls \"file\" \"url\" [\"url\"...]\n_do_curls() {\n\tf=\"${1}\"\n\tshift\n\tif _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\treturn 0\n\tfi\n\tfor url in \"${@}\"; do\n\t\tif _do_curl \"${url}\" \"${f}\"; then\n\t\t\tif _do_curl \"${url}.sha256\" \"${f}.sha256\"; then\n\t\t\t\techo \"$(cat \"${f}.sha256\")  ${f}\" >\"${f}.sha256.tmp\"\n\t\t\t\tmv \"${f}.sha256.tmp\" \"${f}.sha256\"\n\t\t\t\tif ! _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\t\t\t\twarn \"sha256sum failed for '${f}'\"\n\t\t\t\t\twarn 'continuing to next candidate URL'\n\t\t\t\t\tcontinue\n\t\t\t\tfi\n\t\t\tfi\n\t\t\treturn\n\t\tfi\n\tdone\n\trm -f \"${f}\"\n\treturn 1\n}\n\n# _binary \"version\" \"file.tar.gz\" \"arch\"\n_binary() {\n\tlocal version=${1}\n\tlocal file=${2}\n\tlocal arch=${3}\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz\"\n\t)\n\tif [[ \"${GIMME_OS}\" == 'darwin' && \"${GIMME_BINARY_OSX}\" ]]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz\"\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${arch}\" = 'arm' ]; then\n\t\t# attempt \"armv6l\" vs just \"arm\" first (since that's what's officially published)\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz\" # go1.6beta2 & go1.6rc1\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz\" # go1.6beta1\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip\"\n\t\t)\n\tfi\n\t_do_curls \"${file}\" \"${urls[@]}\"\n}\n\n# _source \"version\" \"file.src.tar.gz\"\n_source() {\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz\"\n\t\t\"https://github.com/golang/go/archive/go${1}.tar.gz\"\n\t)\n\t_do_curls \"${2}\" \"${urls[@]}\"\n}\n\n# _fetch \"dir\"\n_fetch() {\n\tmkdir -p \"$(dirname \"${1}\")\"\n\n\tif [[ -d \"${1}/.git\" ]]; then\n\t\t(\n\t\t\tcd \"${1}\"\n\t\t\tgit remote set-url origin \"${GIMME_GO_GIT_REMOTE}\"\n\t\t\tgit fetch -q --all && git fetch -q --tags\n\t\t)\n\t\treturn\n\tfi\n\n\tgit clone -q \"${GIMME_GO_GIT_REMOTE}\" \"${1}\"\n}\n\n# _checkout \"version\" \"dir\"\n# NB: might emit a \"renamed version\" on stdout\n_checkout() {\n\tlocal spec=\"${1:?}\" godir=\"${2:?}\"\n\t# We are called twice, once during validation that a version was given and\n\t# later during build.  We don't want to fetch twice, so we are fetching\n\t# during the validation only, in the caller.\n\n\tif [[ \"${spec}\" =~ ^[0-9a-f]{6,}$ ]]; then\n\t\t# We always treat this as a commit sha, whether instead of doing\n\t\t# branch tests etc.  It looks like a commit sha and the Go maintainers\n\t\t# aren't daft enough to use pure hex for a tag or branch.\n\t\tgit -C \"$godir\" reset -q --hard \"${spec}\" || return 1\n\t\treturn 0\n\tfi\n\n\t# If spec looks like HEAD^{something} or HEAD^^^ then trying\n\t# origin/$spec would succeed but we'd write junk to the filesystem,\n\t# propagating annoying characters out.\n\tlocal retval probe_named disallow rev\n\n\tprobe_named=1\n\tdisallow='[@^~:{}]'\n\tif [[ \"${spec}\" =~ $disallow ]]; then\n\t\tprobe_named=0\n\t\t[[ \"${spec}\" != \"@\" ]] || spec=\"HEAD\"\n\tfi\n\n\ttry_spec() { git -C \"${godir}\" reset -q --hard \"$@\" -- 2>/dev/null; }\n\n\tretval=1\n\tif ((probe_named)); then\n\t\tretval=0\n\t\ttry_spec \"origin/${spec}\" ||\n\t\t\ttry_spec \"origin/go${spec}\" ||\n\t\t\t{ [[ \"${spec}\" == \"tip\" ]] && try_spec origin/master; } ||\n\t\t\ttry_spec \"refs/tags/${spec}\" ||\n\t\t\ttry_spec \"refs/tags/go${spec}\" ||\n\t\t\tretval=1\n\tfi\n\n\tif ((retval)); then\n\t\tretval=0\n\t\t# We're about to reset anyway, if we succeed, so we should reset to a\n\t\t# known state before parsing what might be relative specs\n\t\ttry_spec origin/master &&\n\t\t\trev=\"$(git -C \"${godir}\" rev-parse --verify -q \"${spec}^{object}\")\" &&\n\t\t\ttry_spec \"${rev}\" &&\n\t\t\tgit -C \"${godir}\" rev-parse --verify -q --short=12 \"${rev}\" ||\n\t\t\tretval=1\n\t\t# that rev-parse prints to stdout, so we can affect the version seen\n\tfi\n\n\tunset -f try_spec\n\treturn $retval\n}\n\n# _extract \"file.tar.gz\" \"dir\"\n_extract() {\n\tmkdir -p \"${2}\"\n\n\tif [[ \"${1}\" == *.tar.gz ]]; then\n\t\ttar -xf \"${1}\" -C \"${2}\" --strip-components 1\n\telse\n\t\tunzip -q \"${1}\" -d \"${2}\"\n\t\tmv \"${2}\"/go/* \"${2}\"\n\t\trmdir \"${2}\"/go\n\tfi\n}\n\n# _setup_bootstrap\n_setup_bootstrap() {\n\tlocal versions=(\"1.18\" \"1.17\" \"1.16\" \"1.15\" \"1.14\" \"1.13\" \"1.12\" \"1.11\" \"1.10\" \"1.9\" \"1.8\" \"1.7\" \"1.6\" \"1.5\" \"1.4\")\n\n\t# try existing\n\tfor v in \"${versions[@]}\"; do\n\t\tfor candidate in \"${GIMME_ENV_PREFIX}/go${v}\"*\".env\"; do\n\t\t\tif [ -s \"${candidate}\" ]; then\n\t\t\t\t# shellcheck source=/dev/null\n\t\t\t\tGOROOT_BOOTSTRAP=\"$(source \"${candidate}\" 2>/dev/null && go env GOROOT)\"\n\t\t\t\texport GOROOT_BOOTSTRAP\n\t\t\t\treturn 0\n\t\t\tfi\n\t\tdone\n\tdone\n\n\t# try binary\n\tfor v in \"${versions[@]}\"; do\n\t\tif [ -n \"$(_try_binary \"${v}\" \"${GIMME_HOSTARCH}\")\" ]; then\n\t\t\texport GOROOT_BOOTSTRAP=\"${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}\"\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\techo >&2 \"Unable to setup go bootstrap from existing or binary\"\n\treturn 1\n}\n\n# _compile \"dir\"\n_compile() {\n\t(\n\t\tif grep -q GOROOT_BOOTSTRAP \"${1}/src/make.bash\" &>/dev/null; then\n\t\t\t_setup_bootstrap || return 1\n\t\tfi\n\t\tcd \"${1}\"\n\t\tif [[ -d .git ]]; then\n\t\t\tgit clean -dfx -q\n\t\tfi\n\t\tcd src\n\t\texport GOOS=\"${GIMME_OS}\" GOARCH=\"${GIMME_ARCH}\"\n\t\texport CGO_ENABLED=\"${GIMME_CGO_ENABLED}\"\n\t\texport CC_FOR_TARGET=\"${GIMME_CC_FOR_TARGET}\"\n\n\t\tlocal make_log=\"${1}/make.${GOOS}.${GOARCH}.log\"\n\t\tif [[ \"${GIMME_DEBUG}\" -ge \"2\" ]]; then\n\t\t\t./make.bash -v 2>&1 | tee \"${make_log}\" 1>&2 || return 1\n\t\telse\n\t\t\t./make.bash &>\"${make_log}\" || return 1\n\t\tfi\n\t)\n}\n\n_try_install_race() {\n\tif [[ ! \"${GIMME_INSTALL_RACE}\" ]]; then\n\t\treturn 0\n\tfi\n\t\"${1}/bin/go\" install -race std\n}\n\n_can_compile() {\n\tcat >\"${GIMME_TMP}/test.go\" <<'EOF'\npackage main\nimport \"os\"\nfunc main() {\n\tos.Exit(0)\n}\nEOF\n\t\"${1}/bin/go\" run \"${GIMME_TMP}/test.go\"\n}\n\n# _env \"dir\"\n_env() {\n\t[[ -d \"${1}/bin\" && -x \"${1}/bin/go\" ]] || return 1\n\n\t# if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source\n\t# automatically\n\tGOROOT=\"${1}\" GOFLAGS=\"\" \"${1}/bin/go\" version &>/dev/null || return 1\n\n\t# https://twitter.com/davecheney/status/431581286918934528\n\t# we have to GOROOT sometimes because we use official release binaries in unofficial locations :(\n\t#\n\t# Issue 87 leads to:\n\t#   No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it.\n\t#   The \"avoid setting it\" is _only_ for people using official releases in official locations.\n\t#   Tools like `gimme` are the reason that GOROOT-in-env exists.\n\n\techo\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" ]]; then\n\t\techo 'unset GOOS;'\n\telse\n\t\techo 'export GOOS=\"'\"${GIMME_OS}\"'\";'\n\tfi\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\techo 'unset GOARCH;'\n\telse\n\t\techo 'export GOARCH=\"'\"${GIMME_ARCH}\"'\";'\n\tfi\n\n\techo \"export GOROOT='${1}';\"\n\n\t# shellcheck disable=SC2016\n\techo 'export PATH=\"'\"${1}/bin\"':${PATH}\";'\n\tif [[ -z \"${GIMME_SILENT_ENV}\" ]]; then\n\t\techo 'go version >&2;'\n\tfi\n\techo\n}\n\n# _env_alias \"dir\" \"env-file\"\n_env_alias() {\n\tif [[ \"${GIMME_NO_ENV_ALIAS}\" ]]; then\n\t\techo \"${2}\"\n\t\treturn\n\tfi\n\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" && \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\t# GIMME_GO_VERSION might be a branch, which can contain '/'\n\t\tlocal dest=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\\//__}.env\"\n\t\tcp \"${2}\" \"${dest}\"\n\t\tln -sf \"${dest}\" \"${GIMME_ENV_PREFIX}/latest.env\"\n\t\techo \"${dest}\"\n\telse\n\t\techo \"${2}\"\n\tfi\n}\n\n_try_existing() {\n\tcase \"${1}\" in\n\tbinary)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\t\t;;\n\tsource)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\t\t;;\n\t*)\n\t\t_try_existing binary || _try_existing source\n\t\treturn $?\n\t\t;;\n\tesac\n\n\tif [[ -x \"${existing_ver}/bin/go\" && -s \"${existing_env}\" ]]; then\n\t\t# newer envs have existing semi-colon at end of line, because newer gimme\n\t\t# puts them there; envs created before that change lack those semi-colons\n\t\t# and should gain them, to make it easier for people using eval without\n\t\t# double-quoting the command substition.\n\t\tsed -e 's/\\([^;]\\)$/\\1;/' <\"${existing_env}\"\n\t\t# gimme is the corner-case where GOROOT _should_ be overriden, since if the\n\t\t# ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is\n\t\t# unset, then it will be used and the wrong golang will be picked up.\n\t\t# Lots of old installs won't have GOROOT; munge it from $PATH\n\t\tif grep -qs '^unset GOROOT' -- \"${existing_env}\"; then\n\t\t\tsed -n -e 's/^export PATH=\"\\(.*\\)\\/bin:.*$/export GOROOT='\"'\"'\\1'\"'\"';/p' <\"${existing_env}\"\n\t\t\techo\n\t\tfi\n\t\t# Export the same variables whether building new or using existing\n\t\techo \"export GIMME_ENV='${existing_env}';\"\n\t\treturn\n\tfi\n\n\treturn 1\n}\n\n# _try_binary \"version\" \"arch\"\n_try_binary() {\n\tlocal version=${1}\n\tlocal arch=${2}\n\tlocal bin_tgz=\"${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz\"\n\tlocal bin_dir=\"${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}\"\n\tlocal bin_env=\"${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env\"\n\n\t[[ \"${version}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\tbin_tgz=${bin_tgz%.tar.gz}.zip\n\tfi\n\n\t_binary \"${version}\" \"${bin_tgz}\" \"${arch}\" || return 1\n\t_extract \"${bin_tgz}\" \"${bin_dir}\" || return 1\n\t_env \"${bin_dir}\" | tee \"${bin_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${bin_dir}\" \"${bin_env}\")\\\"\"\n}\n\n_try_source() {\n\tlocal src_tgz=\"${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz\"\n\tlocal src_dir=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\tlocal src_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\n\t[[ \"${GIMME_GO_VERSION}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\t_source \"${GIMME_GO_VERSION}\" \"${src_tgz}\" || return 1\n\t_extract \"${src_tgz}\" \"${src_dir}\" || return 1\n\t_compile \"${src_dir}\" || return 1\n\t_try_install_race \"${src_dir}\" || return 1\n\t_env \"${src_dir}\" | tee \"${src_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${src_dir}\" \"${src_env}\")\\\"\"\n}\n\n# We do _not_ try to use any version caching with _try_existing(), but instead\n# build afresh each time.  We don't want to deal with someone moving the repo\n# to other-version, doing an install, then resetting it back to\n# last-version-we-saw and thus introducing conflicts.\n#\n# If you want to re-use a built-at-spec version, then avoid moving the repo\n# and source the generated .env manually.\n# Note that the env will just refer to the 'go' directory, so it's not safe\n# to reuse anyway.\n_try_git() {\n\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\tlocal git_env=\"${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env\"\n\tlocal resolved_sha\n\n\t# Any tags should have been resolved when we asserted that we were\n\t# given a version, so no need to handle that here.\n\t_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\" >/dev/null || return 1\n\t_compile \"${git_dir}\" || return 1\n\t_try_install_race \"${git_dir}\" || return 1\n\t_env \"${git_dir}\" | tee \"${git_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${git_dir}\" \"${git_env}\")\\\"\"\n}\n\n_wipe_version() {\n\tlocal env_file=\"${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\n\tif [[ -s \"${env_file}\" ]]; then\n\t\trm -rf \"$(awk -F\\\" '/GOROOT/ { print $2 }' \"${env_file}\")\"\n\t\trm -f \"${env_file}\"\n\tfi\n}\n\n_list_versions() {\n\tif [ ! -d \"${GIMME_VERSION_PREFIX}\" ]; then\n\t\treturn 0\n\tfi\n\n\tlocal current_version\n\tcurrent_version=\"$(go env GOROOT 2>/dev/null)\"\n\tcurrent_version=\"${current_version##*/go}\"\n\tcurrent_version=\"${current_version%%.${GIMME_OS}.*}\"\n\n\t# 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash\n\t# doesn't appear to have anything like that.\n\tfor d in \"${GIMME_VERSION_PREFIX}/go\"*\".${GIMME_OS}.\"*; do\n\t\tlocal cleaned=\"${d##*/go}\"\n\t\tcleaned=\"${cleaned%%.${GIMME_OS}.*}\"\n\t\techo \"${cleaned}\"\n\tdone | _version_sort | while read -r cleaned; do\n\t\techo -en \"${cleaned}\"\n\t\tif [[ \"${cleaned}\" == \"${current_version}\" ]]; then\n\t\t\techo -en ' <= current' >&2\n\t\tfi\n\t\techo\n\tdone\n}\n\n_update_remote_known_list_if_needed() {\n\t# shellcheck disable=SC1117\n\tlocal exp=\"go([[:alnum:]\\.]*)\\.src.*\" # :alnum: catches beta versions too\n\tlocal list=\"${GIMME_VERSION_PREFIX}/known-versions.txt\"\n\tlocal dlfile=\"${GIMME_TMP}/known-dl\"\n\n\tif [[ -e \"${list}\" ]] &&\n\t\t! ((force_known_update)) &&\n\t\t! _file_older_than_secs \"${list}\" \"${GIMME_KNOWN_CACHE_MAX}\"; then\n\t\techo \"${list}\"\n\t\treturn 0\n\tfi\n\n\t[[ -d \"${GIMME_VERSION_PREFIX:?}\" ]] || mkdir -p -- \"${GIMME_VERSION_PREFIX}\"\n\n\t_do_curl \"${GIMME_LIST_KNOWN}\" \"${dlfile}\"\n\n\twhile read -r line; do\n\t\tif [[ \"${line}\" =~ ${exp} ]]; then\n\t\t\techo \"${BASH_REMATCH[1]}\"\n\t\tfi\n\tdone <\"${dlfile}\" | _version_sort | uniq >\"${list}.new\"\n\trm -f \"${list}\" &>/dev/null\n\tmv \"${list}.new\" \"${list}\"\n\n\trm -f \"${dlfile}\"\n\techo \"${list}\"\n\treturn 0\n}\n\n_list_known() {\n\tlocal knownfile\n\tknownfile=\"$(_update_remote_known_list_if_needed)\"\n\n\t(\n\t\t_list_versions 2>/dev/null\n\t\tcat -- \"${knownfile}\"\n\t) | grep . | _version_sort | uniq\n}\n\n# For the \"invoked on commandline\" case, we want to always pass unknown\n# strings through, so that we can be a uniqueness filter, but for unknown\n# names we want to exit with a value other than 1, so we document that\n# we'll exit 2.  For use by other functions, 2 is as good as 1.\n_resolve_version() {\n\tcase \"${1}\" in\n\tstable)\n\t\t_get_curr_stable\n\t\treturn 0\n\t\t;;\n\toldstable)\n\t\t_get_old_stable\n\t\treturn 0\n\t\t;;\n\ttip)\n\t\techo \"tip\"\n\t\treturn 0\n\t\t;;\n\t*.x)\n\t\ttrue\n\t\t;;\n\t*)\n\t\techo \"${1}\"\n\t\tlocal GIMME_GO_VERSION=\"$1\"\n\t\tlocal ASSERT_ABORT='return'\n\t\tif _assert_version_given 2>/dev/null; then\n\t\t\treturn 0\n\t\tfi\n\t\twarn \"version specifier '${1}' unknown\"\n\t\treturn 2\n\t\t;;\n\tesac\n\t# We have a .x suffix\n\tlocal base=\"${1%.x}\"\n\tlocal ver last='' known\n\tknown=\"$(_update_remote_known_list_if_needed)\" # will be version-sorted\n\tif [[ ! \"${base}\" =~ ^[0-9.]+$ ]]; then\n\t\twarn \"resolve pattern '${base}.x' invalid for .x finding\"\n\t\treturn 2\n\tfi\n\t# The `.x` is optional; \"1.10\" matches \"1.10.x\"\n\tlocal search=\"^${base//./\\\\.}(\\\\.[0-9.]+)?\\$\"\n\t# avoid regexp attacks\n\twhile read -r ver; do\n\t\t[[ \"${ver}\" =~ $search ]] || continue\n\t\tlast=\"${ver}\"\n\tdone <\"$known\"\n\tif [[ -n \"${last}\" ]]; then\n\t\techo \"${last}\"\n\t\treturn 0\n\tfi\n\techo \"${1}\"\n\twarn \"given '${1}' but no release for '${base}' found\"\n\treturn 2\n}\n\n_realpath() {\n\t# shellcheck disable=SC2005\n\t[ -d \"$1\" ] && echo \"$(cd \"$1\" && pwd)\" || echo \"$(cd \"$(dirname \"$1\")\" && pwd)/$(basename \"$1\")\"\n}\n\n_get_curr_stable() {\n\tlocal stable=\"${GIMME_VERSION_PREFIX}/stable\"\n\n\tif _file_older_than_secs \"${stable}\" 86400; then\n\t\t_update_stable \"${stable}\"\n\tfi\n\n\tcat \"${stable}\"\n}\n\n_get_old_stable() {\n\tlocal oldstable=\"${GIMME_VERSION_PREFIX}/oldstable\"\n\n\tif _file_older_than_secs \"${oldstable}\" 86400; then\n\t\t_update_oldstable \"${oldstable}\"\n\tfi\n\n\tcat \"${oldstable}\"\n}\n\n_update_stable() {\n\tlocal stable=\"${1}\"\n\tlocal url=\"https://golang.org/VERSION?m=text\"\n\n\t_do_curl \"${url}\" \"${stable}\"\n\tsed -i.old -e 's/^go\\(.*\\)/\\1/' \"${stable}\"\n\trm -f \"${stable}.old\"\n}\n\n_update_oldstable() {\n\tlocal oldstable=\"${1}\"\n\tlocal oldstable_x\n\toldstable_x=$(_get_curr_stable | awk -F. '{\n\t\t$2--;\n\t\tprint $1 \".\" $2 \".\" \"x\"\n\t}')\n\t_resolve_version \"${oldstable_x}\" >\"${oldstable}\"\n}\n\n_last_mod_timestamp() {\n\tlocal filename=\"${1}\"\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin | *bsd)\n\t\tstat -f %m \"${filename}\"\n\t\t;;\n\tlinux)\n\t\tstat -c %Y \"${filename}\"\n\t\t;;\n\tesac\n}\n\n_file_older_than_secs() {\n\tlocal file=\"${1}\"\n\tlocal age_secs=\"${2}\"\n\tlocal ts\n\t# if the file does not exist, we return true, as the cache needs updating\n\tts=\"$(_last_mod_timestamp \"${file}\" 2>/dev/null)\" || return 0\n\t((($(date +%s) - ts) > age_secs))\n}\n\n_assert_version_given() {\n\t# By the time we're called, aliases such as \"stable\" must have been resolved\n\t# but we could be a reference in git.\n\t#\n\t# Versions can include suffices such as in \"1.8beta2\", so our assumption is that\n\t# there will always be a minor present; the first public release was \"1.0\" so\n\t# we assume \"2.0\" not \"2\".\n\n\tif [[ -z \"${GIMME_GO_VERSION}\" ]]; then\n\t\techo >&2 'error: no GIMME_GO_VERSION supplied'\n\t\techo >&2 \"  ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}\"\n\t\techo >&2 \"  ex: ${0} 1.4.1 ${*}\"\n\t\t${ASSERT_ABORT:-exit} 1\n\tfi\n\n\t# Note: _resolve_version calls back to us (_assert_version_given), but\n\t# only for cases where the version does not end with .x, so this should\n\t# be safe.\n\t# This should be untangled.  PRs accepted, good starter project.\n\tif [[ \"${GIMME_GO_VERSION}\" == *.x ]]; then\n\t\tGIMME_GO_VERSION=\"$(_resolve_version \"${GIMME_GO_VERSION}\")\" || ${ASSERT_ABORT:-exit} 1\n\tfi\n\n\tif [[ \"${GIMME_GO_VERSION}\" == +([[:digit:]]).+([[:digit:]])* ]]; then\n\t\treturn 0\n\tfi\n\n\t# Here we resolve symbolic references.  If we don't, then we get some\n\t# random git tag name being accepted as valid and then we try to\n\t# curl garbage from upstream.\n\tif [[ \"${GIMME_TYPE}\" == \"auto\" || \"${GIMME_TYPE}\" == \"git\" ]]; then\n\t\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\t\tlocal resolved_sha\n\t\t_fetch \"${git_dir}\"\n\t\tif resolved_sha=\"$(_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\")\"; then\n\t\t\tif [[ -n \"${resolved_sha}\" ]]; then\n\t\t\t\t# Break our normal silence, this one really needs to be seen on stderr\n\t\t\t\t# always; auditability and knowing what version of Go you got wins.\n\t\t\t\twarn \"resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'\"\n\t\t\t\tGIMME_GO_VERSION=\"${resolved_sha}\"\n\t\t\tfi\n\t\t\treturn 0\n\t\tfi\n\tfi\n\n\techo >&2 'error: GIMME_GO_VERSION not recognized as valid'\n\techo >&2 \"  got: ${GIMME_GO_VERSION}\"\n\t${ASSERT_ABORT:-exit} 1\n}\n\n_exclude_from_backups() {\n\t# Please avoid anything which requires elevated privileges or is obnoxious\n\t# enough to offend the invoker\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin)\n\t\t# Darwin: Time Machine is \"standard\", we can add others.  The default\n\t\t# mechanism is sticky, as an attribute on the dir, requires no\n\t\t# privileges, is idempotent (and doesn't support -- to end flags).\n\t\ttmutil addexclusion \"$@\"\n\t\t;;\n\tesac\n}\n\n_versint() {\n\tIFS=\" \" read -r -a args <<<\"${1//[^0-9]/ }\"\n\tprintf '1%03d%03d%03d%03d' \"${args[@]}\"\n}\n\n_to_goarch() {\n\tcase \"${1}\" in\n\taarch64) echo \"arm64\" ;;\n\t*) echo \"${1}\" ;;\n\tesac\n}\n\n: \"${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_ARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_HOSTARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}\"\n: \"${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}\"\n: \"${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}\"\n: \"${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}\"\n: \"${GIMME_TYPE:=auto}\" # 'auto', 'binary', 'source', or 'git'\n: \"${GIMME_BINARY_OSX:=osx10.8}\"\n: \"${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}\"\n: \"${GIMME_LIST_KNOWN:=https://golang.org/dl}\"\n: \"${GIMME_KNOWN_CACHE_MAX:=10800}\"\n\n# The version prefix must be an absolute path\ncase \"${GIMME_VERSION_PREFIX}\" in\n/*) true ;;\n*)\n\techo >&2 \" Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX\"\n\tGIMME_VERSION_PREFIX=\"$(pwd)/${GIMME_VERSION_PREFIX}\"\n\techo >&2 \" to: $GIMME_VERSION_PREFIX\"\n\t;;\nesac\n\ncase \"${GIMME_OS}\" in mingw* | msys_nt*)\n\t# Minimalist GNU for Windows\n\tGIMME_OS='windows'\n\n\tif [ \"${GIMME_ARCH}\" = 'i686' ]; then\n\t\tGIMME_ARCH=\"386\"\n\telse\n\t\tGIMME_ARCH=\"amd64\"\n\tfi\n\t;;\nesac\n\nforce_install=0\nforce_known_update=0\n\nwhile [[ $# -gt 0 ]]; do\n\tcase \"${1}\" in\n\t-h | --help | help | wat)\n\t\t_old_ifs=\"$IFS\"\n\t\tIFS=';'\n\t\tawk '/^#\\+  / {\n\t\t\t\tsub(/^#\\+  /, \"\", $0) ;\n\t\t\t\tsub(/-$/, \"\", $0) ;\n\t\t\t\tprint $0\n\t\t\t}' \"$0\" | while read -r line; do\n\t\t\teval \"echo \\\"$line\\\"\"\n\t\tdone\n\t\tIFS=\"$_old_ifs\"\n\t\texit 0\n\t\t;;\n\t-V | --version | version)\n\t\techo \"${GIMME_VERSION}\"\n\t\texit 0\n\t\t;;\n\t-r | --resolve | resolve)\n\t\t# The normal mkdir of versions is below; we don't want to move it up\n\t\t# to where we create files just if asked our version; thus\n\t\t# _resolve_version has to mkdir the versions dir itself.\n\t\tif [[ $# -ge 2 ]]; then\n\t\t\t_resolve_version \"${2}\"\n\t\telif [[ -n \"${GIMME_GO_VERSION:-}\" ]]; then\n\t\t\t_resolve_version \"${GIMME_GO_VERSION}\"\n\t\telse\n\t\t\tdie \"resolve must be given a version to resolve\"\n\t\tfi\n\t\texit $?\n\t\t;;\n\t-l | --list | list)\n\t\t_list_versions\n\t\texit 0\n\t\t;;\n\t-k | --known | known)\n\t\t_list_known\n\t\texit 0\n\t\t;;\n\t-f | --force | force)\n\t\tforce_install=1\n\t\t;;\n\t--force-known-update | force-known-update)\n\t\tforce_known_update=1\n\t\t;;\n\t-i | install)\n\t\ttrue # ignore a dummy argument\n\t\t;;\n\t*)\n\t\tbreak\n\t\t;;\n\tesac\n\tshift\ndone\n\nif [[ -n \"${1}\" ]]; then\n\tGIMME_GO_VERSION=\"${1}\"\nfi\nif [[ -n \"${2}\" ]]; then\n\tGIMME_VERSION_PREFIX=\"${2}\"\nfi\n\ncase \"${GIMME_ARCH}\" in\nx86_64) GIMME_ARCH=amd64 ;;\nx86) GIMME_ARCH=386 ;;\narm64)\n\tif [[ \"${GIMME_GO_VERSION}\" != master && \"$(_versint \"${GIMME_GO_VERSION}\")\" < \"$(_versint 1.5)\" ]]; then\n\t\techo >&2 \"error: ${GIMME_ARCH} is not supported by this go version\"\n\t\techo >&2 \"try go1.5 or newer\"\n\t\texit 1\n\tfi\n\tif [[ \"${GIMME_HOSTOS}\" == \"linux\" && \"${GIMME_HOSTARCH}\" != \"${GIMME_ARCH}\" ]]; then\n\t\t: \"${GIMME_CC_FOR_TARGET:=\"aarch64-linux-gnu-gcc\"}\"\n\tfi\n\t;;\narm*) GIMME_ARCH=arm ;;\nesac\n\ncase \"${GIMME_HOSTARCH}\" in\nx86_64) GIMME_HOSTARCH=amd64 ;;\nx86) GIMME_HOSTARCH=386 ;;\narm64) ;;\narm*) GIMME_HOSTARCH=arm ;;\nesac\n\ncase \"${GIMME_GO_VERSION}\" in\nstable) GIMME_GO_VERSION=$(_get_curr_stable) ;;\noldstable) GIMME_GO_VERSION=$(_get_old_stable) ;;\nesac\n\n_assert_version_given \"$@\"\n\n((force_install)) && _wipe_version \"${GIMME_GO_VERSION}\"\n\nunset GOARCH\nunset GOBIN\nunset GOOS\nunset GOPATH\nunset GOROOT\nunset CGO_ENABLED\nunset CC_FOR_TARGET\n# GO111MODULE breaks build of Go itself\nunset GO111MODULE\n\nmkdir -p \"${GIMME_VERSION_PREFIX}\" \"${GIMME_ENV_PREFIX}\"\n# The envs dir stays small and provides a record of what had been installed\n# whereas the versions dir grows by hundreds of MB per version and is not\n# intended to support local modifications (as that subverts the point of gimme)\n# _and_ is a cache, so we're unilaterally declaring that the contents of\n# the versions dir should be excluded from system backups.\n_exclude_from_backups \"${GIMME_VERSION_PREFIX}\"\n\nGIMME_VERSION_PREFIX=\"$(_realpath \"${GIMME_VERSION_PREFIX}\")\"\nGIMME_ENV_PREFIX=\"$(_realpath \"${GIMME_ENV_PREFIX}\")\"\n\nif ! case \"${GIMME_TYPE}\" in\n\tbinary) _try_existing binary || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" ;;\n\tsource) _try_existing source || _try_source || _try_git ;;\n\tgit) _try_git ;;\n\tauto) _try_existing || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" || _try_source || _try_git ;;\n\t*)\n\t\techo >&2 \"I don't know how to '${GIMME_TYPE}'.\"\n\t\techo >&2 \"  Try 'auto', 'binary', 'source', or 'git'.\"\n\t\texit 1\n\t\t;;\n\tesac; then\n\techo >&2 \"I don't have any idea what to do with '${GIMME_GO_VERSION}'.\"\n\techo >&2 \"  (using download type '${GIMME_TYPE}')\"\n\texit 1\nfi\n"
  },
  {
    "path": "images/local-path-helper/Dockerfile",
    "content": "# Copyright 2022 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 image is contains the binaries needed for the local-path-provisioner\n# helper pod. Currently that means: sh, rm, mkdir\n\nARG BASE=\"debian:trixie-slim\"\nFROM ${BASE} AS build\n\n# NOTE: copyrights.tar.gz is a quirk of Kubernetes's debian-base image\n# We extract these here so we can grab the relevant files are easily\n# staged for copying into our final image.\nRUN [ ! -f /usr/share/copyrights.tar.gz ] || tar -C / -xzvf /usr/share/copyrights.tar.gz\n\n# we need bash for stage-binary-and-deps.sh\nRUN apt update && apt install -y --no-install-recommends bash\n# replace sh with bash\nRUN ln -sf /usr/bin/bash /usr/bin/sh\n\n# copy in script for staging distro provided binary to distroless\nCOPY --chmod=0755 stage-binary-and-deps.sh /usr/local/bin/\n\n# local-path-provisioner needs these things for the helper pod\n# TODO: we could probably coerce local-path-provisioner to use a small binary\n# for these instead\nARG STAGE_DIR=\"/opt/stage\"\nRUN mkdir -p \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh sh \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh rm \"${STAGE_DIR}\" && \\\n    stage-binary-and-deps.sh mkdir \"${STAGE_DIR}\" && \\\n    find \"${STAGE_DIR}\"\n\n# copy staged binary + deps + copyright into distroless\nFROM \"gcr.io/distroless/static-debian13\"\nARG STAGE_DIR=\"/opt/stage\"\nCOPY --from=build \"${STAGE_DIR}/\" /\n"
  },
  {
    "path": "images/local-path-helper/Makefile",
    "content": "# Copyright 2022 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ninclude $(CURDIR)/../Makefile.common.in"
  },
  {
    "path": "images/local-path-helper/README.md",
    "content": "# local-path-helper\n\nThis image is the image used for the https://github.com/rancher/local-path-provisioner helper pod.\n\n## Building\n\nYou can `make quick` in this directory to build a test image.\n\nTo push an actual image use `make push`."
  },
  {
    "path": "images/local-path-helper/cloudbuild.yaml",
    "content": "# See https://cloud.google.com/cloud-build/docs/build-config\noptions:\n  substitution_option: ALLOW_LOOSE\n  machineType: E2_HIGHCPU_8\nsteps:\n- name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:latest\n  entrypoint: make\n  args: ['-C', 'images/local-path-helper', 'push']\n"
  },
  {
    "path": "images/local-path-helper/stage-binary-and-deps.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# USAGE: stage-binary-and-deps.sh haproxy /opt/stage\n#\n# Stages $1 and it's dependencies + their copyright files to $2\n#\n# This is intended to be used in a multi-stage docker build with a distroless/base\n# or distroless/cc image.\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# file_to_package identifies the debian package that provided the file $1\nfile_to_package() {\n    # `dpkg-query --search $file-pattern` outputs lines with the format: \"$package: $file-path\"\n    # where $file-path belongs to $package\n    # https://manpages.debian.org/jessie/dpkg/dpkg-query.1.en.html\n    (dpkg-query --search \"$(realpath \"${1}\")\") | cut -d':' -f1\n}\n\n# package_to_copyright gives the path to the copyright file for the package $1\npackage_to_copyright() {\n    echo \"/usr/share/doc/${1}/copyright\"\n}\n\n# stage_file stages the filepath $1 to $2, following symlinks\n# and staging copyrights\nstage_file() {\n    # /lib is a symlink to /usr/lib in debian 12, means we just stick to\n    # /usr/lib for all libraries to avoid separating symlinks with the actual binaries\n    # ditto /lib64\n    from=\"${1}\"\n    if [[ $from = /lib*/* ]]; then\n        from=\"/usr$from\"\n    fi\n    cp -a --parents \"${from}\" \"${2}\"\n\n    # recursively follow symlinks\n    if [[ -L \"${from}\" ]]; then\n        stage_file \"$(cd \"$(dirname \"${from}\")\"; realpath -s \"$(readlink \"${from}\")\")\" \"${2}\"\n    fi\n    # get the package so we can stage package metadata as well\n    package=\"$(file_to_package \"${from}\")\"\n\n    # files like /usr/lib/x86_64-linux-gnu/libc.so.6 will return no package\n    if [[ \"$package\" != \"\" ]]; then\n        # stage the copyright for the file\n        cp -a --parents \"$(package_to_copyright \"${package}\")\" \"${2}\"\n        # stage the package status mimicking bazel\n        # https://github.com/bazelbuild/rules_docker/commit/f5432b813e0a11491cf2bf83ff1a923706b36420\n        # instead of parsing the control file, we can just get the actual package status with dpkg\n        dpkg -s \"${package}\" > \"${2}/var/lib/dpkg/status.d/${package}\"\n    fi\n}\n\n# binary_to_libraries identifies the library files needed by the binary $1 with ldd\nbinary_to_libraries() {\n    # see: https://man7.org/linux/man-pages/man1/ldd.1.html\n    ldd \"${1}\" \\\n    `# strip the leading '${name} => ' if any so only '/lib-foo.so (0xf00)' remains` \\\n    | sed -E 's#.* => /#/#' \\\n    `# we want only the path remaining, not the (0x${LOCATION})` \\\n    | awk '{print $1}' \\\n    `# linux-vdso.so.1 is a special virtual shared object from the kernel` \\\n    `# see: http://man7.org/linux/man-pages/man7/vdso.7.html` \\\n    | grep -v 'linux-vdso.so.1'\n}\n\n# main script logic\nmain(){\n    local BINARY=$1\n    local STAGE_DIR=\"${2}/\"\n\n    # locate the path to the binary\n    local binary_path\n    binary_path=\"$(which \"${BINARY}\")\"\n\n    # ensure package metadata dir\n    mkdir -p \"${STAGE_DIR}\"/var/lib/dpkg/status.d/\n\n    # stage the binary itself\n    stage_file \"${binary_path}\" \"${STAGE_DIR}\"\n\n    # stage the dependencies of the binary\n    while IFS= read -r c_dep; do\n        stage_file \"${c_dep}\" \"${STAGE_DIR}\"\n    done < <(binary_to_libraries \"${binary_path}\")\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "images/local-path-provisioner/Dockerfile",
    "content": "# Copyright 2022 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# NOTE the actual go version will be overridden\nFROM --platform=$BUILDPLATFORM docker.io/library/golang:latest\nCOPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/\nRUN git clone --filter=tree:0 https://github.com/rancher/local-path-provisioner\nARG VERSION\n# set by makefile to .go-version\n# TODO: scripts/build builds for multiple platforms, so we waste a little time here\nARG TARGETARCH GO_VERSION\nRUN eval \"$(gimme \"${GO_VERSION}\")\" \\\n    && export GOTOOLCHAIN=\"go${GO_VERSION}\" \\\n    && cd local-path-provisioner \\\n    && git fetch && git checkout \"${VERSION}\" \\\n    && GOARCH=$TARGETARCH scripts/build \\\n    && mv bin/local-path-provisioner-$TARGETARCH /usr/local/bin/local-path-provisioner \\\n    && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest \\\n    && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES .\n\nFROM gcr.io/distroless/static-debian13\nCOPY --from=0 /usr/local/bin/local-path-provisioner /usr/local/bin/local-path-provisioner\nCOPY --from=0 /_LICENSES/* /LICENSES/\nCOPY --chmod=0644 files/LICENSES/* /LICENSES/*\nENTRYPOINT /usr/local/bin/local-path-provisioner\n"
  },
  {
    "path": "images/local-path-provisioner/Makefile",
    "content": "# Copyright 2022 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nVERSION=v0.0.34\nEXTRA_BUILD_OPT=--build-arg=VERSION=$(VERSION)\ninclude $(CURDIR)/../Makefile.common.in"
  },
  {
    "path": "images/local-path-provisioner/README.md",
    "content": "# local-path-provisioner\n\nThis image packages https://github.com/rancher/local-path-provisioner to meet\nour requirements.\n\n- Not based on alpine (see: https://github.com/kubernetes/kubernetes/issues/109406#issuecomment-1103479928)\n- Control over building with current patched go version\n\n## Building\n\nYou can `make quick` in this directory to build a test image.\n\nTo push an actual image use `make push`.\n"
  },
  {
    "path": "images/local-path-provisioner/cloudbuild.yaml",
    "content": "# See https://cloud.google.com/cloud-build/docs/build-config\noptions:\n  substitution_option: ALLOW_LOOSE\n  machineType: E2_HIGHCPU_8\nsteps:\n- name: gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:latest\n  entrypoint: make\n  args: ['-C', 'images/local-path-provisioner', 'push']\n"
  },
  {
    "path": "images/local-path-provisioner/files/LICENSES/README.txt",
    "content": "This directory contains license files and notices from binaries built for this\nimage and the dependencies of those binaries,\nas collected by https://github.com/google/go-licenses.\n"
  },
  {
    "path": "images/local-path-provisioner/scripts/third_party/gimme/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2018 gimme contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "images/local-path-provisioner/scripts/third_party/gimme/README.md",
    "content": "# gimme\n\nThis is an unmodified copy of [gimme], so we don't have to download it\nfrom the internet.\n\n[gimme]: https://github.com/travis-ci/gimme"
  },
  {
    "path": "images/local-path-provisioner/scripts/third_party/gimme/gimme",
    "content": "#!/usr/bin/env bash\n# vim:noexpandtab:ts=2:sw=2:\n#\n#+  Usage: $(basename $0) [flags] [go-version] [version-prefix]\n#+  -\n#+  Version: ${GIMME_VERSION}\n#+  Copyright: ${GIMME_COPYRIGHT}\n#+  License URL: ${GIMME_LICENSE_URL}\n#+  -\n#+  Install go!  There are multiple types of installations available, with 'auto' being the default.\n#+  If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing\n#+  go installation.  This behavior may be disabled by providing '-f/--force/force' as first positional\n#+  argument.\n#+  -\n#+  Option flags:\n#+          -h --help help - show this help text and exit\n#+    -V --version version - show the version only and exit\n#+        -f --force force - remove the existing go installation if present prior to install\n#+          -l --list list - list installed go versions and exit\n#+        -k --known known - list known go versions and exit\n#+    --force-known-update - when used with --known, ignores the cache and updates\n#+    -r --resolve resolve - resolve a version specifier to a version, show that and exit\n#+  -\n#+  Influential env vars:\n#+  -\n#+        GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg)\n#+    GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}',\n#+                           may be given as second positional arg)\n#+              GIMME_ARCH - arch to install (default '${GIMME_ARCH}')\n#+        GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}')\n#+        GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}')\n#+     GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}')\n#+                GIMME_OS - os to install (default '${GIMME_OS}')\n#+               GIMME_TMP - temp directory (default '${GIMME_TMP}')\n#+              GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git')\n#+                           (default '${GIMME_TYPE}')\n#+      GIMME_INSTALL_RACE - install race directory after compile if non-empty.\n#+                           If the install type is 'binary', this option is ignored.\n#+             GIMME_DEBUG - enable tracing if non-empty\n#+      GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host\n#+        GIMME_SILENT_ENV - omit the 'go version' line from env file\n#+       GIMME_CGO_ENABLED - enable build of cgo support\n#+     GIMME_CC_FOR_TARGET - cross compiler for cgo support\n#+     GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}')\n#+        GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}')\n#+   GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}')\n#+  -\n#\nset -e\nshopt -s nullglob\nshopt -s dotglob\nshopt -s extglob\nset -o pipefail\n\n[[ ${GIMME_DEBUG} ]] && set -x\n\nreadonly GIMME_VERSION=\"v1.5.4\"\nreadonly GIMME_COPYRIGHT=\"Copyright (c) 2015-2020 gimme contributors\"\nreadonly GIMME_LICENSE_URL=\"https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE\"\nexport GIMME_VERSION\nexport GIMME_COPYRIGHT\nexport GIMME_LICENSE_URL\n\nprogram_name=\"$(basename \"$0\")\"\n# shellcheck disable=SC1117\nwarn() { printf >&2 \"%s: %s\\n\" \"${program_name}\" \"${*}\"; }\ndie() {\n\twarn \"$@\"\n\texit 1\n}\n\n# We don't want to go around hitting Google's servers with requests for\n# files named HEAD@{date}.tar so we only try binary/source downloads if\n# it looks like a plausible name to us.\n# We don't need to support 0. releases of Go.\n# We don't support 5 digit major-versions of Go (limit back-tracking in RE).\n# We don't support very long versions\n#   (both to avoid annoying download server operators with attacks and\n#    because regexp backtracking can be pathological).\n# Per _assert_version_given we do assume 2.0 not 2\nALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\\.[0-9][0-9a-zA-Z_-]{0,9})+$'\n#\n# The main path which allowed these to leak upstream before has been closed\n# but a valid git repo tag or branch-name will still reach the point of\n# being _tried_ upstream.\n\n# _do_curl \"url\" \"file\"\n_do_curl() {\n\tmkdir -p \"$(dirname \"${2}\")\"\n\n\tif command -v curl >/dev/null; then\n\t\tcurl -sSLf \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v wget >/dev/null; then\n\t\twget -q \"${1}\" -O \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\tif command -v fetch >/dev/null; then\n\t\tfetch -q \"${1}\" -o \"${2}\" 2>/dev/null\n\t\treturn\n\tfi\n\n\techo >&2 'error: no curl, wget, or fetch found'\n\texit 1\n}\n\n# _sha256sum \"file\"\n_sha256sum() {\n\tif command -v sha256sum &>/dev/null; then\n\t\tsha256sum \"$@\"\n\telif command -v gsha256sum &>/dev/null; then\n\t\tgsha256sum \"$@\"\n\telse\n\t\tshasum -a 256 \"$@\"\n\tfi\n}\n\n# sort versions, handling 1.10 after 1.9, not before 1.2\n# FreeBSD sort has --version-sort, none of the others do\n# Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty\nif sort --version-sort </dev/null &>/dev/null; then\n\t_version_sort() { sort --version-sort; }\nelse\n\t_version_sort() {\n\t\t# If we go to four-digit minor or patch versions, then extend the padding here\n\t\t# (but in such a world, perhaps --version-sort will have become standard by then?)\n\t\tsed -E 's/\\.([0-9](\\.|$))/.00\\1/g; s/\\.([0-9][0-9](\\.|$))/.0\\1/g' |\n\t\t\tsort --general-numeric-sort |\n\t\t\tsed 's/\\.00*/./g'\n\t}\nfi\n\n# _do_curls \"file\" \"url\" [\"url\"...]\n_do_curls() {\n\tf=\"${1}\"\n\tshift\n\tif _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\treturn 0\n\tfi\n\tfor url in \"${@}\"; do\n\t\tif _do_curl \"${url}\" \"${f}\"; then\n\t\t\tif _do_curl \"${url}.sha256\" \"${f}.sha256\"; then\n\t\t\t\techo \"$(cat \"${f}.sha256\")  ${f}\" >\"${f}.sha256.tmp\"\n\t\t\t\tmv \"${f}.sha256.tmp\" \"${f}.sha256\"\n\t\t\t\tif ! _sha256sum -c \"${f}.sha256\" &>/dev/null; then\n\t\t\t\t\twarn \"sha256sum failed for '${f}'\"\n\t\t\t\t\twarn 'continuing to next candidate URL'\n\t\t\t\t\tcontinue\n\t\t\t\tfi\n\t\t\tfi\n\t\t\treturn\n\t\tfi\n\tdone\n\trm -f \"${f}\"\n\treturn 1\n}\n\n# _binary \"version\" \"file.tar.gz\" \"arch\"\n_binary() {\n\tlocal version=${1}\n\tlocal file=${2}\n\tlocal arch=${3}\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz\"\n\t)\n\tif [[ \"${GIMME_OS}\" == 'darwin' && \"${GIMME_BINARY_OSX}\" ]]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz\"\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${arch}\" = 'arm' ]; then\n\t\t# attempt \"armv6l\" vs just \"arm\" first (since that's what's officially published)\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz\" # go1.6beta2 & go1.6rc1\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz\" # go1.6beta1\n\t\t\t\"${urls[@]}\"\n\t\t)\n\tfi\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\turls=(\n\t\t\t\"${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip\"\n\t\t)\n\tfi\n\t_do_curls \"${file}\" \"${urls[@]}\"\n}\n\n# _source \"version\" \"file.src.tar.gz\"\n_source() {\n\turls=(\n\t\t\"${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz\"\n\t\t\"https://github.com/golang/go/archive/go${1}.tar.gz\"\n\t)\n\t_do_curls \"${2}\" \"${urls[@]}\"\n}\n\n# _fetch \"dir\"\n_fetch() {\n\tmkdir -p \"$(dirname \"${1}\")\"\n\n\tif [[ -d \"${1}/.git\" ]]; then\n\t\t(\n\t\t\tcd \"${1}\"\n\t\t\tgit remote set-url origin \"${GIMME_GO_GIT_REMOTE}\"\n\t\t\tgit fetch -q --all && git fetch -q --tags\n\t\t)\n\t\treturn\n\tfi\n\n\tgit clone -q \"${GIMME_GO_GIT_REMOTE}\" \"${1}\"\n}\n\n# _checkout \"version\" \"dir\"\n# NB: might emit a \"renamed version\" on stdout\n_checkout() {\n\tlocal spec=\"${1:?}\" godir=\"${2:?}\"\n\t# We are called twice, once during validation that a version was given and\n\t# later during build.  We don't want to fetch twice, so we are fetching\n\t# during the validation only, in the caller.\n\n\tif [[ \"${spec}\" =~ ^[0-9a-f]{6,}$ ]]; then\n\t\t# We always treat this as a commit sha, whether instead of doing\n\t\t# branch tests etc.  It looks like a commit sha and the Go maintainers\n\t\t# aren't daft enough to use pure hex for a tag or branch.\n\t\tgit -C \"$godir\" reset -q --hard \"${spec}\" || return 1\n\t\treturn 0\n\tfi\n\n\t# If spec looks like HEAD^{something} or HEAD^^^ then trying\n\t# origin/$spec would succeed but we'd write junk to the filesystem,\n\t# propagating annoying characters out.\n\tlocal retval probe_named disallow rev\n\n\tprobe_named=1\n\tdisallow='[@^~:{}]'\n\tif [[ \"${spec}\" =~ $disallow ]]; then\n\t\tprobe_named=0\n\t\t[[ \"${spec}\" != \"@\" ]] || spec=\"HEAD\"\n\tfi\n\n\ttry_spec() { git -C \"${godir}\" reset -q --hard \"$@\" -- 2>/dev/null; }\n\n\tretval=1\n\tif ((probe_named)); then\n\t\tretval=0\n\t\ttry_spec \"origin/${spec}\" ||\n\t\t\ttry_spec \"origin/go${spec}\" ||\n\t\t\t{ [[ \"${spec}\" == \"tip\" ]] && try_spec origin/master; } ||\n\t\t\ttry_spec \"refs/tags/${spec}\" ||\n\t\t\ttry_spec \"refs/tags/go${spec}\" ||\n\t\t\tretval=1\n\tfi\n\n\tif ((retval)); then\n\t\tretval=0\n\t\t# We're about to reset anyway, if we succeed, so we should reset to a\n\t\t# known state before parsing what might be relative specs\n\t\ttry_spec origin/master &&\n\t\t\trev=\"$(git -C \"${godir}\" rev-parse --verify -q \"${spec}^{object}\")\" &&\n\t\t\ttry_spec \"${rev}\" &&\n\t\t\tgit -C \"${godir}\" rev-parse --verify -q --short=12 \"${rev}\" ||\n\t\t\tretval=1\n\t\t# that rev-parse prints to stdout, so we can affect the version seen\n\tfi\n\n\tunset -f try_spec\n\treturn $retval\n}\n\n# _extract \"file.tar.gz\" \"dir\"\n_extract() {\n\tmkdir -p \"${2}\"\n\n\tif [[ \"${1}\" == *.tar.gz ]]; then\n\t\ttar -xf \"${1}\" -C \"${2}\" --strip-components 1\n\telse\n\t\tunzip -q \"${1}\" -d \"${2}\"\n\t\tmv \"${2}\"/go/* \"${2}\"\n\t\trmdir \"${2}\"/go\n\tfi\n}\n\n# _setup_bootstrap\n_setup_bootstrap() {\n\tlocal versions=(\"1.18\" \"1.17\" \"1.16\" \"1.15\" \"1.14\" \"1.13\" \"1.12\" \"1.11\" \"1.10\" \"1.9\" \"1.8\" \"1.7\" \"1.6\" \"1.5\" \"1.4\")\n\n\t# try existing\n\tfor v in \"${versions[@]}\"; do\n\t\tfor candidate in \"${GIMME_ENV_PREFIX}/go${v}\"*\".env\"; do\n\t\t\tif [ -s \"${candidate}\" ]; then\n\t\t\t\t# shellcheck source=/dev/null\n\t\t\t\tGOROOT_BOOTSTRAP=\"$(source \"${candidate}\" 2>/dev/null && go env GOROOT)\"\n\t\t\t\texport GOROOT_BOOTSTRAP\n\t\t\t\treturn 0\n\t\t\tfi\n\t\tdone\n\tdone\n\n\t# try binary\n\tfor v in \"${versions[@]}\"; do\n\t\tif [ -n \"$(_try_binary \"${v}\" \"${GIMME_HOSTARCH}\")\" ]; then\n\t\t\texport GOROOT_BOOTSTRAP=\"${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}\"\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\techo >&2 \"Unable to setup go bootstrap from existing or binary\"\n\treturn 1\n}\n\n# _compile \"dir\"\n_compile() {\n\t(\n\t\tif grep -q GOROOT_BOOTSTRAP \"${1}/src/make.bash\" &>/dev/null; then\n\t\t\t_setup_bootstrap || return 1\n\t\tfi\n\t\tcd \"${1}\"\n\t\tif [[ -d .git ]]; then\n\t\t\tgit clean -dfx -q\n\t\tfi\n\t\tcd src\n\t\texport GOOS=\"${GIMME_OS}\" GOARCH=\"${GIMME_ARCH}\"\n\t\texport CGO_ENABLED=\"${GIMME_CGO_ENABLED}\"\n\t\texport CC_FOR_TARGET=\"${GIMME_CC_FOR_TARGET}\"\n\n\t\tlocal make_log=\"${1}/make.${GOOS}.${GOARCH}.log\"\n\t\tif [[ \"${GIMME_DEBUG}\" -ge \"2\" ]]; then\n\t\t\t./make.bash -v 2>&1 | tee \"${make_log}\" 1>&2 || return 1\n\t\telse\n\t\t\t./make.bash &>\"${make_log}\" || return 1\n\t\tfi\n\t)\n}\n\n_try_install_race() {\n\tif [[ ! \"${GIMME_INSTALL_RACE}\" ]]; then\n\t\treturn 0\n\tfi\n\t\"${1}/bin/go\" install -race std\n}\n\n_can_compile() {\n\tcat >\"${GIMME_TMP}/test.go\" <<'EOF'\npackage main\nimport \"os\"\nfunc main() {\n\tos.Exit(0)\n}\nEOF\n\t\"${1}/bin/go\" run \"${GIMME_TMP}/test.go\"\n}\n\n# _env \"dir\"\n_env() {\n\t[[ -d \"${1}/bin\" && -x \"${1}/bin/go\" ]] || return 1\n\n\t# if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source\n\t# automatically\n\tGOROOT=\"${1}\" GOFLAGS=\"\" \"${1}/bin/go\" version &>/dev/null || return 1\n\n\t# https://twitter.com/davecheney/status/431581286918934528\n\t# we have to GOROOT sometimes because we use official release binaries in unofficial locations :(\n\t#\n\t# Issue 87 leads to:\n\t#   No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it.\n\t#   The \"avoid setting it\" is _only_ for people using official releases in official locations.\n\t#   Tools like `gimme` are the reason that GOROOT-in-env exists.\n\n\techo\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" ]]; then\n\t\techo 'unset GOOS;'\n\telse\n\t\techo 'export GOOS=\"'\"${GIMME_OS}\"'\";'\n\tfi\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\techo 'unset GOARCH;'\n\telse\n\t\techo 'export GOARCH=\"'\"${GIMME_ARCH}\"'\";'\n\tfi\n\n\techo \"export GOROOT='${1}';\"\n\n\t# shellcheck disable=SC2016\n\techo 'export PATH=\"'\"${1}/bin\"':${PATH}\";'\n\tif [[ -z \"${GIMME_SILENT_ENV}\" ]]; then\n\t\techo 'go version >&2;'\n\tfi\n\techo\n}\n\n# _env_alias \"dir\" \"env-file\"\n_env_alias() {\n\tif [[ \"${GIMME_NO_ENV_ALIAS}\" ]]; then\n\t\techo \"${2}\"\n\t\treturn\n\tfi\n\n\tif [[ \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTOS)\" == \"${GIMME_OS}\" && \"$(GOROOT=\"${1}\" \"${1}/bin/go\" env GOHOSTARCH)\" == \"${GIMME_ARCH}\" ]]; then\n\t\t# GIMME_GO_VERSION might be a branch, which can contain '/'\n\t\tlocal dest=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\\//__}.env\"\n\t\tcp \"${2}\" \"${dest}\"\n\t\tln -sf \"${dest}\" \"${GIMME_ENV_PREFIX}/latest.env\"\n\t\techo \"${dest}\"\n\telse\n\t\techo \"${2}\"\n\tfi\n}\n\n_try_existing() {\n\tcase \"${1}\" in\n\tbinary)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\t\t;;\n\tsource)\n\t\tlocal existing_ver=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\t\tlocal existing_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\t\t;;\n\t*)\n\t\t_try_existing binary || _try_existing source\n\t\treturn $?\n\t\t;;\n\tesac\n\n\tif [[ -x \"${existing_ver}/bin/go\" && -s \"${existing_env}\" ]]; then\n\t\t# newer envs have existing semi-colon at end of line, because newer gimme\n\t\t# puts them there; envs created before that change lack those semi-colons\n\t\t# and should gain them, to make it easier for people using eval without\n\t\t# double-quoting the command substition.\n\t\tsed -e 's/\\([^;]\\)$/\\1;/' <\"${existing_env}\"\n\t\t# gimme is the corner-case where GOROOT _should_ be overriden, since if the\n\t\t# ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is\n\t\t# unset, then it will be used and the wrong golang will be picked up.\n\t\t# Lots of old installs won't have GOROOT; munge it from $PATH\n\t\tif grep -qs '^unset GOROOT' -- \"${existing_env}\"; then\n\t\t\tsed -n -e 's/^export PATH=\"\\(.*\\)\\/bin:.*$/export GOROOT='\"'\"'\\1'\"'\"';/p' <\"${existing_env}\"\n\t\t\techo\n\t\tfi\n\t\t# Export the same variables whether building new or using existing\n\t\techo \"export GIMME_ENV='${existing_env}';\"\n\t\treturn\n\tfi\n\n\treturn 1\n}\n\n# _try_binary \"version\" \"arch\"\n_try_binary() {\n\tlocal version=${1}\n\tlocal arch=${2}\n\tlocal bin_tgz=\"${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz\"\n\tlocal bin_dir=\"${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}\"\n\tlocal bin_env=\"${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env\"\n\n\t[[ \"${version}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\tif [ \"${GIMME_OS}\" = 'windows' ]; then\n\t\tbin_tgz=${bin_tgz%.tar.gz}.zip\n\tfi\n\n\t_binary \"${version}\" \"${bin_tgz}\" \"${arch}\" || return 1\n\t_extract \"${bin_tgz}\" \"${bin_dir}\" || return 1\n\t_env \"${bin_dir}\" | tee \"${bin_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${bin_dir}\" \"${bin_env}\")\\\"\"\n}\n\n_try_source() {\n\tlocal src_tgz=\"${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz\"\n\tlocal src_dir=\"${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src\"\n\tlocal src_env=\"${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env\"\n\n\t[[ \"${GIMME_GO_VERSION}\" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1\n\n\t_source \"${GIMME_GO_VERSION}\" \"${src_tgz}\" || return 1\n\t_extract \"${src_tgz}\" \"${src_dir}\" || return 1\n\t_compile \"${src_dir}\" || return 1\n\t_try_install_race \"${src_dir}\" || return 1\n\t_env \"${src_dir}\" | tee \"${src_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${src_dir}\" \"${src_env}\")\\\"\"\n}\n\n# We do _not_ try to use any version caching with _try_existing(), but instead\n# build afresh each time.  We don't want to deal with someone moving the repo\n# to other-version, doing an install, then resetting it back to\n# last-version-we-saw and thus introducing conflicts.\n#\n# If you want to re-use a built-at-spec version, then avoid moving the repo\n# and source the generated .env manually.\n# Note that the env will just refer to the 'go' directory, so it's not safe\n# to reuse anyway.\n_try_git() {\n\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\tlocal git_env=\"${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env\"\n\tlocal resolved_sha\n\n\t# Any tags should have been resolved when we asserted that we were\n\t# given a version, so no need to handle that here.\n\t_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\" >/dev/null || return 1\n\t_compile \"${git_dir}\" || return 1\n\t_try_install_race \"${git_dir}\" || return 1\n\t_env \"${git_dir}\" | tee \"${git_env}\" || return 1\n\techo \"export GIMME_ENV=\\\"$(_env_alias \"${git_dir}\" \"${git_env}\")\\\"\"\n}\n\n_wipe_version() {\n\tlocal env_file=\"${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env\"\n\n\tif [[ -s \"${env_file}\" ]]; then\n\t\trm -rf \"$(awk -F\\\" '/GOROOT/ { print $2 }' \"${env_file}\")\"\n\t\trm -f \"${env_file}\"\n\tfi\n}\n\n_list_versions() {\n\tif [ ! -d \"${GIMME_VERSION_PREFIX}\" ]; then\n\t\treturn 0\n\tfi\n\n\tlocal current_version\n\tcurrent_version=\"$(go env GOROOT 2>/dev/null)\"\n\tcurrent_version=\"${current_version##*/go}\"\n\tcurrent_version=\"${current_version%%.${GIMME_OS}.*}\"\n\n\t# 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash\n\t# doesn't appear to have anything like that.\n\tfor d in \"${GIMME_VERSION_PREFIX}/go\"*\".${GIMME_OS}.\"*; do\n\t\tlocal cleaned=\"${d##*/go}\"\n\t\tcleaned=\"${cleaned%%.${GIMME_OS}.*}\"\n\t\techo \"${cleaned}\"\n\tdone | _version_sort | while read -r cleaned; do\n\t\techo -en \"${cleaned}\"\n\t\tif [[ \"${cleaned}\" == \"${current_version}\" ]]; then\n\t\t\techo -en ' <= current' >&2\n\t\tfi\n\t\techo\n\tdone\n}\n\n_update_remote_known_list_if_needed() {\n\t# shellcheck disable=SC1117\n\tlocal exp=\"go([[:alnum:]\\.]*)\\.src.*\" # :alnum: catches beta versions too\n\tlocal list=\"${GIMME_VERSION_PREFIX}/known-versions.txt\"\n\tlocal dlfile=\"${GIMME_TMP}/known-dl\"\n\n\tif [[ -e \"${list}\" ]] &&\n\t\t! ((force_known_update)) &&\n\t\t! _file_older_than_secs \"${list}\" \"${GIMME_KNOWN_CACHE_MAX}\"; then\n\t\techo \"${list}\"\n\t\treturn 0\n\tfi\n\n\t[[ -d \"${GIMME_VERSION_PREFIX:?}\" ]] || mkdir -p -- \"${GIMME_VERSION_PREFIX}\"\n\n\t_do_curl \"${GIMME_LIST_KNOWN}\" \"${dlfile}\"\n\n\twhile read -r line; do\n\t\tif [[ \"${line}\" =~ ${exp} ]]; then\n\t\t\techo \"${BASH_REMATCH[1]}\"\n\t\tfi\n\tdone <\"${dlfile}\" | _version_sort | uniq >\"${list}.new\"\n\trm -f \"${list}\" &>/dev/null\n\tmv \"${list}.new\" \"${list}\"\n\n\trm -f \"${dlfile}\"\n\techo \"${list}\"\n\treturn 0\n}\n\n_list_known() {\n\tlocal knownfile\n\tknownfile=\"$(_update_remote_known_list_if_needed)\"\n\n\t(\n\t\t_list_versions 2>/dev/null\n\t\tcat -- \"${knownfile}\"\n\t) | grep . | _version_sort | uniq\n}\n\n# For the \"invoked on commandline\" case, we want to always pass unknown\n# strings through, so that we can be a uniqueness filter, but for unknown\n# names we want to exit with a value other than 1, so we document that\n# we'll exit 2.  For use by other functions, 2 is as good as 1.\n_resolve_version() {\n\tcase \"${1}\" in\n\tstable)\n\t\t_get_curr_stable\n\t\treturn 0\n\t\t;;\n\toldstable)\n\t\t_get_old_stable\n\t\treturn 0\n\t\t;;\n\ttip)\n\t\techo \"tip\"\n\t\treturn 0\n\t\t;;\n\t*.x)\n\t\ttrue\n\t\t;;\n\t*)\n\t\techo \"${1}\"\n\t\tlocal GIMME_GO_VERSION=\"$1\"\n\t\tlocal ASSERT_ABORT='return'\n\t\tif _assert_version_given 2>/dev/null; then\n\t\t\treturn 0\n\t\tfi\n\t\twarn \"version specifier '${1}' unknown\"\n\t\treturn 2\n\t\t;;\n\tesac\n\t# We have a .x suffix\n\tlocal base=\"${1%.x}\"\n\tlocal ver last='' known\n\tknown=\"$(_update_remote_known_list_if_needed)\" # will be version-sorted\n\tif [[ ! \"${base}\" =~ ^[0-9.]+$ ]]; then\n\t\twarn \"resolve pattern '${base}.x' invalid for .x finding\"\n\t\treturn 2\n\tfi\n\t# The `.x` is optional; \"1.10\" matches \"1.10.x\"\n\tlocal search=\"^${base//./\\\\.}(\\\\.[0-9.]+)?\\$\"\n\t# avoid regexp attacks\n\twhile read -r ver; do\n\t\t[[ \"${ver}\" =~ $search ]] || continue\n\t\tlast=\"${ver}\"\n\tdone <\"$known\"\n\tif [[ -n \"${last}\" ]]; then\n\t\techo \"${last}\"\n\t\treturn 0\n\tfi\n\techo \"${1}\"\n\twarn \"given '${1}' but no release for '${base}' found\"\n\treturn 2\n}\n\n_realpath() {\n\t# shellcheck disable=SC2005\n\t[ -d \"$1\" ] && echo \"$(cd \"$1\" && pwd)\" || echo \"$(cd \"$(dirname \"$1\")\" && pwd)/$(basename \"$1\")\"\n}\n\n_get_curr_stable() {\n\tlocal stable=\"${GIMME_VERSION_PREFIX}/stable\"\n\n\tif _file_older_than_secs \"${stable}\" 86400; then\n\t\t_update_stable \"${stable}\"\n\tfi\n\n\tcat \"${stable}\"\n}\n\n_get_old_stable() {\n\tlocal oldstable=\"${GIMME_VERSION_PREFIX}/oldstable\"\n\n\tif _file_older_than_secs \"${oldstable}\" 86400; then\n\t\t_update_oldstable \"${oldstable}\"\n\tfi\n\n\tcat \"${oldstable}\"\n}\n\n_update_stable() {\n\tlocal stable=\"${1}\"\n\tlocal url=\"https://golang.org/VERSION?m=text\"\n\n\t_do_curl \"${url}\" \"${stable}\"\n\tsed -i.old -e 's/^go\\(.*\\)/\\1/' \"${stable}\"\n\trm -f \"${stable}.old\"\n}\n\n_update_oldstable() {\n\tlocal oldstable=\"${1}\"\n\tlocal oldstable_x\n\toldstable_x=$(_get_curr_stable | awk -F. '{\n\t\t$2--;\n\t\tprint $1 \".\" $2 \".\" \"x\"\n\t}')\n\t_resolve_version \"${oldstable_x}\" >\"${oldstable}\"\n}\n\n_last_mod_timestamp() {\n\tlocal filename=\"${1}\"\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin | *bsd)\n\t\tstat -f %m \"${filename}\"\n\t\t;;\n\tlinux)\n\t\tstat -c %Y \"${filename}\"\n\t\t;;\n\tesac\n}\n\n_file_older_than_secs() {\n\tlocal file=\"${1}\"\n\tlocal age_secs=\"${2}\"\n\tlocal ts\n\t# if the file does not exist, we return true, as the cache needs updating\n\tts=\"$(_last_mod_timestamp \"${file}\" 2>/dev/null)\" || return 0\n\t((($(date +%s) - ts) > age_secs))\n}\n\n_assert_version_given() {\n\t# By the time we're called, aliases such as \"stable\" must have been resolved\n\t# but we could be a reference in git.\n\t#\n\t# Versions can include suffices such as in \"1.8beta2\", so our assumption is that\n\t# there will always be a minor present; the first public release was \"1.0\" so\n\t# we assume \"2.0\" not \"2\".\n\n\tif [[ -z \"${GIMME_GO_VERSION}\" ]]; then\n\t\techo >&2 'error: no GIMME_GO_VERSION supplied'\n\t\techo >&2 \"  ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}\"\n\t\techo >&2 \"  ex: ${0} 1.4.1 ${*}\"\n\t\t${ASSERT_ABORT:-exit} 1\n\tfi\n\n\t# Note: _resolve_version calls back to us (_assert_version_given), but\n\t# only for cases where the version does not end with .x, so this should\n\t# be safe.\n\t# This should be untangled.  PRs accepted, good starter project.\n\tif [[ \"${GIMME_GO_VERSION}\" == *.x ]]; then\n\t\tGIMME_GO_VERSION=\"$(_resolve_version \"${GIMME_GO_VERSION}\")\" || ${ASSERT_ABORT:-exit} 1\n\tfi\n\n\tif [[ \"${GIMME_GO_VERSION}\" == +([[:digit:]]).+([[:digit:]])* ]]; then\n\t\treturn 0\n\tfi\n\n\t# Here we resolve symbolic references.  If we don't, then we get some\n\t# random git tag name being accepted as valid and then we try to\n\t# curl garbage from upstream.\n\tif [[ \"${GIMME_TYPE}\" == \"auto\" || \"${GIMME_TYPE}\" == \"git\" ]]; then\n\t\tlocal git_dir=\"${GIMME_VERSION_PREFIX}/go\"\n\t\tlocal resolved_sha\n\t\t_fetch \"${git_dir}\"\n\t\tif resolved_sha=\"$(_checkout \"${GIMME_GO_VERSION}\" \"${git_dir}\")\"; then\n\t\t\tif [[ -n \"${resolved_sha}\" ]]; then\n\t\t\t\t# Break our normal silence, this one really needs to be seen on stderr\n\t\t\t\t# always; auditability and knowing what version of Go you got wins.\n\t\t\t\twarn \"resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'\"\n\t\t\t\tGIMME_GO_VERSION=\"${resolved_sha}\"\n\t\t\tfi\n\t\t\treturn 0\n\t\tfi\n\tfi\n\n\techo >&2 'error: GIMME_GO_VERSION not recognized as valid'\n\techo >&2 \"  got: ${GIMME_GO_VERSION}\"\n\t${ASSERT_ABORT:-exit} 1\n}\n\n_exclude_from_backups() {\n\t# Please avoid anything which requires elevated privileges or is obnoxious\n\t# enough to offend the invoker\n\tcase \"${GIMME_HOSTOS}\" in\n\tdarwin)\n\t\t# Darwin: Time Machine is \"standard\", we can add others.  The default\n\t\t# mechanism is sticky, as an attribute on the dir, requires no\n\t\t# privileges, is idempotent (and doesn't support -- to end flags).\n\t\ttmutil addexclusion \"$@\"\n\t\t;;\n\tesac\n}\n\n_versint() {\n\tIFS=\" \" read -r -a args <<<\"${1//[^0-9]/ }\"\n\tprintf '1%03d%03d%03d%03d' \"${args[@]}\"\n}\n\n_to_goarch() {\n\tcase \"${1}\" in\n\taarch64) echo \"arm64\" ;;\n\t*) echo \"${1}\" ;;\n\tesac\n}\n\n: \"${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}\"\n: \"${GIMME_ARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_HOSTARCH:=$(_to_goarch \"$(uname -m)\")}\"\n: \"${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}\"\n: \"${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}\"\n: \"${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}\"\n: \"${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}\"\n: \"${GIMME_TYPE:=auto}\" # 'auto', 'binary', 'source', or 'git'\n: \"${GIMME_BINARY_OSX:=osx10.8}\"\n: \"${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}\"\n: \"${GIMME_LIST_KNOWN:=https://golang.org/dl}\"\n: \"${GIMME_KNOWN_CACHE_MAX:=10800}\"\n\n# The version prefix must be an absolute path\ncase \"${GIMME_VERSION_PREFIX}\" in\n/*) true ;;\n*)\n\techo >&2 \" Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX\"\n\tGIMME_VERSION_PREFIX=\"$(pwd)/${GIMME_VERSION_PREFIX}\"\n\techo >&2 \" to: $GIMME_VERSION_PREFIX\"\n\t;;\nesac\n\ncase \"${GIMME_OS}\" in mingw* | msys_nt*)\n\t# Minimalist GNU for Windows\n\tGIMME_OS='windows'\n\n\tif [ \"${GIMME_ARCH}\" = 'i686' ]; then\n\t\tGIMME_ARCH=\"386\"\n\telse\n\t\tGIMME_ARCH=\"amd64\"\n\tfi\n\t;;\nesac\n\nforce_install=0\nforce_known_update=0\n\nwhile [[ $# -gt 0 ]]; do\n\tcase \"${1}\" in\n\t-h | --help | help | wat)\n\t\t_old_ifs=\"$IFS\"\n\t\tIFS=';'\n\t\tawk '/^#\\+  / {\n\t\t\t\tsub(/^#\\+  /, \"\", $0) ;\n\t\t\t\tsub(/-$/, \"\", $0) ;\n\t\t\t\tprint $0\n\t\t\t}' \"$0\" | while read -r line; do\n\t\t\teval \"echo \\\"$line\\\"\"\n\t\tdone\n\t\tIFS=\"$_old_ifs\"\n\t\texit 0\n\t\t;;\n\t-V | --version | version)\n\t\techo \"${GIMME_VERSION}\"\n\t\texit 0\n\t\t;;\n\t-r | --resolve | resolve)\n\t\t# The normal mkdir of versions is below; we don't want to move it up\n\t\t# to where we create files just if asked our version; thus\n\t\t# _resolve_version has to mkdir the versions dir itself.\n\t\tif [[ $# -ge 2 ]]; then\n\t\t\t_resolve_version \"${2}\"\n\t\telif [[ -n \"${GIMME_GO_VERSION:-}\" ]]; then\n\t\t\t_resolve_version \"${GIMME_GO_VERSION}\"\n\t\telse\n\t\t\tdie \"resolve must be given a version to resolve\"\n\t\tfi\n\t\texit $?\n\t\t;;\n\t-l | --list | list)\n\t\t_list_versions\n\t\texit 0\n\t\t;;\n\t-k | --known | known)\n\t\t_list_known\n\t\texit 0\n\t\t;;\n\t-f | --force | force)\n\t\tforce_install=1\n\t\t;;\n\t--force-known-update | force-known-update)\n\t\tforce_known_update=1\n\t\t;;\n\t-i | install)\n\t\ttrue # ignore a dummy argument\n\t\t;;\n\t*)\n\t\tbreak\n\t\t;;\n\tesac\n\tshift\ndone\n\nif [[ -n \"${1}\" ]]; then\n\tGIMME_GO_VERSION=\"${1}\"\nfi\nif [[ -n \"${2}\" ]]; then\n\tGIMME_VERSION_PREFIX=\"${2}\"\nfi\n\ncase \"${GIMME_ARCH}\" in\nx86_64) GIMME_ARCH=amd64 ;;\nx86) GIMME_ARCH=386 ;;\narm64)\n\tif [[ \"${GIMME_GO_VERSION}\" != master && \"$(_versint \"${GIMME_GO_VERSION}\")\" < \"$(_versint 1.5)\" ]]; then\n\t\techo >&2 \"error: ${GIMME_ARCH} is not supported by this go version\"\n\t\techo >&2 \"try go1.5 or newer\"\n\t\texit 1\n\tfi\n\tif [[ \"${GIMME_HOSTOS}\" == \"linux\" && \"${GIMME_HOSTARCH}\" != \"${GIMME_ARCH}\" ]]; then\n\t\t: \"${GIMME_CC_FOR_TARGET:=\"aarch64-linux-gnu-gcc\"}\"\n\tfi\n\t;;\narm*) GIMME_ARCH=arm ;;\nesac\n\ncase \"${GIMME_HOSTARCH}\" in\nx86_64) GIMME_HOSTARCH=amd64 ;;\nx86) GIMME_HOSTARCH=386 ;;\narm64) ;;\narm*) GIMME_HOSTARCH=arm ;;\nesac\n\ncase \"${GIMME_GO_VERSION}\" in\nstable) GIMME_GO_VERSION=$(_get_curr_stable) ;;\noldstable) GIMME_GO_VERSION=$(_get_old_stable) ;;\nesac\n\n_assert_version_given \"$@\"\n\n((force_install)) && _wipe_version \"${GIMME_GO_VERSION}\"\n\nunset GOARCH\nunset GOBIN\nunset GOOS\nunset GOPATH\nunset GOROOT\nunset CGO_ENABLED\nunset CC_FOR_TARGET\n# GO111MODULE breaks build of Go itself\nunset GO111MODULE\n\nmkdir -p \"${GIMME_VERSION_PREFIX}\" \"${GIMME_ENV_PREFIX}\"\n# The envs dir stays small and provides a record of what had been installed\n# whereas the versions dir grows by hundreds of MB per version and is not\n# intended to support local modifications (as that subverts the point of gimme)\n# _and_ is a cache, so we're unilaterally declaring that the contents of\n# the versions dir should be excluded from system backups.\n_exclude_from_backups \"${GIMME_VERSION_PREFIX}\"\n\nGIMME_VERSION_PREFIX=\"$(_realpath \"${GIMME_VERSION_PREFIX}\")\"\nGIMME_ENV_PREFIX=\"$(_realpath \"${GIMME_ENV_PREFIX}\")\"\n\nif ! case \"${GIMME_TYPE}\" in\n\tbinary) _try_existing binary || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" ;;\n\tsource) _try_existing source || _try_source || _try_git ;;\n\tgit) _try_git ;;\n\tauto) _try_existing || _try_binary \"${GIMME_GO_VERSION}\" \"${GIMME_ARCH}\" || _try_source || _try_git ;;\n\t*)\n\t\techo >&2 \"I don't know how to '${GIMME_TYPE}'.\"\n\t\techo >&2 \"  Try 'auto', 'binary', 'source', or 'git'.\"\n\t\texit 1\n\t\t;;\n\tesac; then\n\techo >&2 \"I don't have any idea what to do with '${GIMME_GO_VERSION}'.\"\n\techo >&2 \"  (using download type '${GIMME_TYPE}')\"\n\texit 1\nfi\n"
  },
  {
    "path": "images/node/README.md",
    "content": "## images/node\n\nSee: [`pkg/build/nodeimage/build.go`][pkg/build/nodeimage/build.go] \nand [`pkg/build/nodeimage/buildcontext.go`][pkg/build/nodeimage/buildcontext.go], this\nimage is built programmatically with docker run / exec / commit for performance\nreasons with large artifacts.\n\nRoughly this image is [the base image](./../base), with the addition of:\n - installing the Kubernetes packages / binaries\n - placing the Kubernetes docker images in `/kind/images/*.tar`\n - placing a file in `/kind/version` containing the Kubernetes semver\n\nSee [`node-image`][node-image.md] for more design details.\n\n[pkg/build/nodeimage/build.go]: ./../../pkg/build/nodeimage/build.go\n[pkg/build/nodeimage/buildcontext.go]: ./../../pkg/build/nodeimage/buildcontext.go\n[node-image.md]: https://kind.sigs.k8s.io/docs/design/node-image\n"
  },
  {
    "path": "logo/LICENSE",
    "content": "# The kind logo files are licensed under a choice of either Apache-2.0 or CC-BY-4.0 (Creative Commons Attribution 4.0 International).\n"
  },
  {
    "path": "main.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// This package is a stub main wrapping cmd/kind.Main()\npackage main\n\nimport (\n\t\"sigs.k8s.io/kind/cmd/kind/app\"\n)\n\nfunc main() {\n\tapp.Main()\n}\n"
  },
  {
    "path": "netlify.toml",
    "content": "# netlify configuration\n[build]\nbase = \"site/\"\npublish = \"site/public/\"\ncommand = \"hugo\"\n\n[build.environment]\nHUGO_VERSION = \"0.111.3\"\n\n[context.production.environment]\n# this controls our robots.txt\nHUGO_ENV = \"production\"\nHUGO_BASEURL = \"https://kind.sigs.k8s.io/\"\n\n[context.deploy-preview]\ncommand = \"hugo --enableGitInfo --buildFuture -b $DEPLOY_PRIME_URL\"\n\n[context.branch-deploy]\ncommand = \"hugo --enableGitInfo --buildFuture -b $DEPLOY_PRIME_URL\"\n"
  },
  {
    "path": "pkg/apis/config/defaults/image.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package defaults contains cross-api-version configuration defaults\npackage defaults\n\n// Image is the default for the Config.Image field, aka the default node image.\nconst Image = \"kindest/node:v1.35.1@sha256:05d7bcdefbda08b4e038f644c4df690cdac3fba8b06f8289f30e10026720a1ab\"\n"
  },
  {
    "path": "pkg/apis/config/v1alpha4/default.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha4\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/apis/config/defaults\"\n)\n\n// SetDefaultsCluster sets uninitialized fields to their default value.\nfunc SetDefaultsCluster(obj *Cluster) {\n\t// default to a one node cluster\n\tif len(obj.Nodes) == 0 {\n\t\tobj.Nodes = []Node{\n\t\t\t{\n\t\t\t\tImage: defaults.Image,\n\t\t\t\tRole:  ControlPlaneRole,\n\t\t\t},\n\t\t}\n\t}\n\t// default the nodes\n\tfor i := range obj.Nodes {\n\t\ta := &obj.Nodes[i]\n\t\tSetDefaultsNode(a)\n\t}\n\tif obj.Networking.IPFamily == \"\" {\n\t\tobj.Networking.IPFamily = IPv4Family\n\t}\n\t// default to listening on 127.0.0.1:randomPort on ipv4\n\t// and [::1]:randomPort on ipv6\n\tif obj.Networking.APIServerAddress == \"\" {\n\t\tobj.Networking.APIServerAddress = \"127.0.0.1\"\n\t\tif obj.Networking.IPFamily == IPv6Family {\n\t\t\tobj.Networking.APIServerAddress = \"::1\"\n\t\t}\n\t}\n\t// default the pod CIDR\n\tif obj.Networking.PodSubnet == \"\" {\n\t\tobj.Networking.PodSubnet = \"10.244.0.0/16\"\n\t\tif obj.Networking.IPFamily == IPv6Family {\n\t\t\t// node-mask cidr default is /64 so we need a larger subnet, we use /56 following best practices\n\t\t\t// xref: https://www.ripe.net/publications/docs/ripe-690#4--size-of-end-user-prefix-assignment---48---56-or-something-else-\n\t\t\tobj.Networking.PodSubnet = \"fd00:10:244::/56\"\n\t\t}\n\t\tif obj.Networking.IPFamily == DualStackFamily {\n\t\t\tobj.Networking.PodSubnet = \"10.244.0.0/16,fd00:10:244::/56\"\n\t\t}\n\t}\n\t// default the service CIDR using a different subnet than kubeadm default\n\t// https://github.com/kubernetes/kubernetes/blob/746404f82a28e55e0b76ffa7e40306fb88eb3317/cmd/kubeadm/app/apis/kubeadm/v1beta2/defaults.go#L32\n\t// Note: kubeadm is using a /12 subnet, that may allocate a 2^20 bitmap in etcd\n\t// we allocate a /16 subnet that allows 65535 services (current Kubernetes tested limit is O(10k) services)\n\tif obj.Networking.ServiceSubnet == \"\" {\n\t\tobj.Networking.ServiceSubnet = \"10.96.0.0/16\"\n\t\tif obj.Networking.IPFamily == IPv6Family {\n\t\t\tobj.Networking.ServiceSubnet = \"fd00:10:96::/112\"\n\t\t}\n\t\tif obj.Networking.IPFamily == DualStackFamily {\n\t\t\tobj.Networking.ServiceSubnet = \"10.96.0.0/16,fd00:10:96::/112\"\n\t\t}\n\t}\n\t// default the KubeProxyMode using iptables as it's already the default\n\tif obj.Networking.KubeProxyMode == \"\" {\n\t\tobj.Networking.KubeProxyMode = IPTablesProxyMode\n\t}\n}\n\n// SetDefaultsNode sets uninitialized fields to their default value.\nfunc SetDefaultsNode(obj *Node) {\n\tif obj.Image == \"\" {\n\t\tobj.Image = defaults.Image\n\t}\n\n\tif obj.Role == \"\" {\n\t\tobj.Role = ControlPlaneRole\n\t}\n}\n"
  },
  {
    "path": "pkg/apis/config/v1alpha4/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1alpha4 implements the v1alpha4 apiVersion of kind's cluster\n// configuration\n//\n// +k8s:deepcopy-gen=package\npackage v1alpha4\n"
  },
  {
    "path": "pkg/apis/config/v1alpha4/types.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha4\n\n// Cluster contains kind cluster configuration\ntype Cluster struct {\n\tTypeMeta `yaml:\",inline\" json:\",inline\"`\n\n\t// The cluster name.\n\t// Optional, this will be overridden by --name / KIND_CLUSTER_NAME\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\"`\n\n\t// Nodes contains the list of nodes defined in the `kind` Cluster\n\t// If unset this will default to a single control-plane node\n\t// Note that if more than one control plane is specified, an external\n\t// control plane load balancer will be provisioned implicitly\n\tNodes []Node `yaml:\"nodes,omitempty\" json:\"nodes,omitempty\"`\n\n\t/* Advanced fields */\n\n\t// Networking contains cluster wide network settings\n\tNetworking Networking `yaml:\"networking,omitempty\" json:\"networking,omitempty\"`\n\n\t// FeatureGates contains a map of Kubernetes feature gates to whether they\n\t// are enabled. The feature gates specified here are passed to all Kubernetes components as flags or in config.\n\t//\n\t// https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/\n\tFeatureGates map[string]bool `yaml:\"featureGates,omitempty\" json:\"featureGates,omitempty\"`\n\n\t// RuntimeConfig Keys and values are translated into --runtime-config values for kube-apiserver, separated by commas.\n\t//\n\t// Use this to enable alpha APIs.\n\tRuntimeConfig map[string]string `yaml:\"runtimeConfig,omitempty\" json:\"runtimeConfig,omitempty\"`\n\n\t// KubeadmConfigPatches are applied to the generated kubeadm config as\n\t// merge patches. The `kind` field must match the target object, and\n\t// if `apiVersion` is specified it will only be applied to matching objects.\n\t//\n\t// This should be an inline yaml blob-string\n\t//\n\t// https://tools.ietf.org/html/rfc7386\n\t//\n\t// The cluster-level patches are applied before the node-level patches.\n\tKubeadmConfigPatches []string `yaml:\"kubeadmConfigPatches,omitempty\" json:\"kubeadmConfigPatches,omitempty\"`\n\n\t// KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config\n\t// as JSON 6902 patches. The `kind` field must match the target object, and\n\t// if group or version are specified it will only be objects matching the\n\t// apiVersion: group+\"/\"+version\n\t//\n\t// Name and Namespace are now ignored, but the fields continue to exist for\n\t// backwards compatibility of parsing the config. The name of the generated\n\t// config was/is always fixed as is the namespace so these fields have\n\t// always been a no-op.\n\t//\n\t// https://tools.ietf.org/html/rfc6902\n\t//\n\t// The cluster-level patches are applied before the node-level patches.\n\tKubeadmConfigPatchesJSON6902 []PatchJSON6902 `yaml:\"kubeadmConfigPatchesJSON6902,omitempty\" json:\"kubeadmConfigPatchesJSON6902,omitempty\"`\n\n\t// ContainerdConfigPatches are applied to every node's containerd config\n\t// in the order listed.\n\t// These should be toml stringsto be applied as merge patches\n\t// If the version field in these patches doesn't match containerd config, it will not be a applied\n\t// This way you can write configurations that work for both by supplying two patches\n\tContainerdConfigPatches []string `yaml:\"containerdConfigPatches,omitempty\" json:\"containerdConfigPatches,omitempty\"`\n\n\t// ContainerdConfigPatchesJSON6902 are applied to every node's containerd config\n\t// in the order listed.\n\t// These should be YAML or JSON formatting RFC 6902 JSON patches\n\t// NOTE: These are not currently version-aware.\n\tContainerdConfigPatchesJSON6902 []string `yaml:\"containerdConfigPatchesJSON6902,omitempty\" json:\"containerdConfigPatchesJSON6902,omitempty\"`\n}\n\n// TypeMeta partially copies apimachinery/pkg/apis/meta/v1.TypeMeta\n// No need for a direct dependence; the fields are stable.\ntype TypeMeta struct {\n\tKind       string `yaml:\"kind,omitempty\" json:\"kind,omitempty\"`\n\tAPIVersion string `yaml:\"apiVersion,omitempty\" json:\"apiVersion,omitempty\"`\n}\n\n// Node contains settings for a node in the `kind` Cluster.\n// A node in kind config represent a container that will be provisioned with all the components\n// required for the assigned role in the Kubernetes cluster\ntype Node struct {\n\t// Role defines the role of the node in the Kubernetes cluster\n\t// created by kind\n\t//\n\t// Defaults to \"control-plane\"\n\tRole NodeRole `yaml:\"role,omitempty\" json:\"role,omitempty\"`\n\n\t// Image is the node image to use when creating this node\n\t// If unset a default image will be used, see defaults.Image\n\tImage string `yaml:\"image,omitempty\" json:\"image,omitempty\"`\n\n\t// Labels are the labels with which the respective node will be labeled\n\tLabels map[string]string `yaml:\"labels,omitempty\" json:\"labels,omitempty\"`\n\n\t/* Advanced fields */\n\n\t// TODO: cri-like types should be inline instead\n\t// ExtraMounts describes additional mount points for the node container\n\t// These may be used to bind a hostPath\n\tExtraMounts []Mount `yaml:\"extraMounts,omitempty\" json:\"extraMounts,omitempty\"`\n\n\t// ExtraPortMappings describes additional port mappings for the node container\n\t// binded to a host Port\n\tExtraPortMappings []PortMapping `yaml:\"extraPortMappings,omitempty\" json:\"extraPortMappings,omitempty\"`\n\n\t// KubeadmConfigPatches are applied to the generated kubeadm config as\n\t// merge patches. The `kind` field must match the target object, and\n\t// if `apiVersion` is specified it will only be applied to matching objects.\n\t//\n\t// This should be an inline yaml blob-string\n\t//\n\t// https://tools.ietf.org/html/rfc7386\n\t//\n\t// The node-level patches will be applied after the cluster-level patches\n\t// have been applied. (See Cluster.KubeadmConfigPatches)\n\tKubeadmConfigPatches []string `yaml:\"kubeadmConfigPatches,omitempty\" json:\"kubeadmConfigPatches,omitempty\"`\n\n\t// KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config\n\t// as JSON 6902 patches. The `kind` field must match the target object, and\n\t// if group or version are specified it will only be objects matching the\n\t// apiVersion: group+\"/\"+version\n\t//\n\t// Name and Namespace are now ignored, but the fields continue to exist for\n\t// backwards compatibility of parsing the config. The name of the generated\n\t// config was/is always fixed as is the namespace so these fields have\n\t// always been a no-op.\n\t//\n\t// https://tools.ietf.org/html/rfc6902\n\t//\n\t// The node-level patches will be applied after the cluster-level patches\n\t// have been applied. (See Cluster.KubeadmConfigPatchesJSON6902)\n\tKubeadmConfigPatchesJSON6902 []PatchJSON6902 `yaml:\"kubeadmConfigPatchesJSON6902,omitempty\" json:\"kubeadmConfigPatchesJSON6902,omitempty\"`\n}\n\n// NodeRole defines possible role for nodes in a Kubernetes cluster managed by `kind`\ntype NodeRole string\n\nconst (\n\t// ControlPlaneRole identifies a node that hosts a Kubernetes control-plane.\n\t// NOTE: in single node clusters, control-plane nodes act also as a worker\n\t// nodes, in which case the taint will be removed. see:\n\t// https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#control-plane-node-isolation\n\tControlPlaneRole NodeRole = \"control-plane\"\n\t// WorkerRole identifies a node that hosts a Kubernetes worker\n\tWorkerRole NodeRole = \"worker\"\n)\n\n// Networking contains cluster wide network settings\ntype Networking struct {\n\t// IPFamily is the network cluster model, currently it can be ipv4 or ipv6\n\tIPFamily ClusterIPFamily `yaml:\"ipFamily,omitempty\" json:\"ipFamily,omitempty\"`\n\t// APIServerPort is the listen port on the host for the Kubernetes API Server\n\t// Defaults to a random port on the host obtained by kind\n\t//\n\t// NOTE: if you set the special value of `-1` then the node backend\n\t// (docker, podman...) will be left to pick the port instead.\n\t// This is potentially useful for remote hosts, BUT it means when the container\n\t// is restarted it will be randomized. Leave this unset to allow kind to pick it.\n\tAPIServerPort int32 `yaml:\"apiServerPort,omitempty\" json:\"apiServerPort,omitempty\"`\n\t// APIServerAddress is the listen address on the host for the Kubernetes\n\t// API Server. This should be an IP address.\n\t//\n\t// Defaults to 127.0.0.1\n\tAPIServerAddress string `yaml:\"apiServerAddress,omitempty\" json:\"apiServerAddress,omitempty\"`\n\t// PodSubnet is the CIDR used for pod IPs\n\t// kind will select a default if unspecified\n\tPodSubnet string `yaml:\"podSubnet,omitempty\" json:\"podSubnet,omitempty\"`\n\t// ServiceSubnet is the CIDR used for services VIPs\n\t// kind will select a default if unspecified for IPv6\n\tServiceSubnet string `yaml:\"serviceSubnet,omitempty\" json:\"serviceSubnet,omitempty\"`\n\t// If DisableDefaultCNI is true, kind will not install the default CNI setup.\n\t// Instead the user should install their own CNI after creating the cluster.\n\tDisableDefaultCNI bool `yaml:\"disableDefaultCNI,omitempty\" json:\"disableDefaultCNI,omitempty\"`\n\t// KubeProxyMode defines if kube-proxy should operate in iptables, ipvs or nftables mode\n\t// Defaults to 'iptables' mode\n\tKubeProxyMode ProxyMode `yaml:\"kubeProxyMode,omitempty\" json:\"kubeProxyMode,omitempty\"`\n\t// DNSSearch defines the DNS search domain to use for nodes. If not set, this will be inherited from the host.\n\tDNSSearch *[]string `yaml:\"dnsSearch,omitempty\" json:\"dnsSearch,omitempty\"`\n}\n\n// ClusterIPFamily defines cluster network IP family\ntype ClusterIPFamily string\n\nconst (\n\t// IPv4Family sets ClusterIPFamily to ipv4\n\tIPv4Family ClusterIPFamily = \"ipv4\"\n\t// IPv6Family sets ClusterIPFamily to ipv6\n\tIPv6Family ClusterIPFamily = \"ipv6\"\n\t// DualStackFamily sets ClusterIPFamily to dual\n\tDualStackFamily ClusterIPFamily = \"dual\"\n)\n\n// ProxyMode defines a proxy mode for kube-proxy\ntype ProxyMode string\n\nconst (\n\t// IPTablesProxyMode sets ProxyMode to iptables\n\tIPTablesProxyMode ProxyMode = \"iptables\"\n\t// IPVSProxyMode sets ProxyMode to ipvs\n\tIPVSProxyMode ProxyMode = \"ipvs\"\n\t// NFTablesProxyMode sets ProxyMode to nftables\n\tNFTablesProxyMode ProxyMode = \"nftables\"\n)\n\n// PatchJSON6902 represents an inline kustomize json 6902 patch\n// https://tools.ietf.org/html/rfc6902\ntype PatchJSON6902 struct {\n\t// these fields specify the patch target resource\n\tGroup   string `yaml:\"group\" json:\"group\"`\n\tVersion string `yaml:\"version\" json:\"version\"`\n\tKind    string `yaml:\"kind\" json:\"kind\"`\n\t// Patch should contain the contents of the json patch as a string\n\tPatch string `yaml:\"patch\" json:\"patch\"`\n}\n\n/*\nThese types are from\nhttps://github.com/kubernetes/kubernetes/blob/063e7ff358fdc8b0916e6f39beedc0d025734cb1/pkg/kubelet/apis/cri/runtime/v1alpha2/api.pb.go#L183\n*/\n\n// Mount specifies a host volume to mount into a container.\n// This is a close copy of the upstream cri Mount type\n// see: k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2\n// It additionally serializes the \"propagation\" field with the string enum\n// names on disk as opposed to the int32 values, and the serialized field names\n// have been made closer to core/v1 VolumeMount field names\n// In yaml this looks like:\n//\n//\tcontainerPath: /foo\n//\thostPath: /bar\n//\treadOnly: true\n//\tselinuxRelabel: false\n//\tpropagation: None\n//\n// Propagation may be one of: None, HostToContainer, Bidirectional\ntype Mount struct {\n\t// Path of the mount within the container.\n\tContainerPath string `yaml:\"containerPath,omitempty\" json:\"containerPath,omitempty\"`\n\t// Path of the mount on the host. If the hostPath doesn't exist, then runtimes\n\t// should report error. If the hostpath is a symbolic link, runtimes should\n\t// follow the symlink and mount the real destination to container.\n\tHostPath string `yaml:\"hostPath,omitempty\" json:\"hostPath,omitempty\"`\n\t// If set, the mount is read-only.\n\tReadonly bool `yaml:\"readOnly,omitempty\" json:\"readOnly,omitempty\"`\n\t// If set, the mount needs SELinux relabeling.\n\tSelinuxRelabel bool `yaml:\"selinuxRelabel,omitempty\" json:\"selinuxRelabel,omitempty\"`\n\t// Requested propagation mode.\n\tPropagation MountPropagation `yaml:\"propagation,omitempty\" json:\"propagation,omitempty\"`\n}\n\n// PortMapping specifies a host port mapped into a container port.\n// In yaml this looks like:\n//\n//\tcontainerPort: 80\n//\thostPort: 8000\n//\tlistenAddress: 127.0.0.1\n//\tprotocol: TCP\ntype PortMapping struct {\n\t// Port within the container.\n\tContainerPort int32 `yaml:\"containerPort,omitempty\" json:\"containerPort,omitempty\"`\n\t// Port on the host.\n\t//\n\t// If unset, a random port will be selected.\n\t//\n\t// NOTE: if you set the special value of `-1` then the node backend\n\t// (docker, podman...) will be left to pick the port instead.\n\t// This is potentially useful for remote hosts, BUT it means when the container\n\t// is restarted it will be randomized. Leave this unset to allow kind to pick it.\n\tHostPort int32 `yaml:\"hostPort,omitempty\" json:\"hostPort,omitempty\"`\n\t// TODO: add protocol (tcp/udp) and port-ranges\n\tListenAddress string `yaml:\"listenAddress,omitempty\" json:\"listenAddress,omitempty\"`\n\t// Protocol (TCP/UDP/SCTP)\n\tProtocol PortMappingProtocol `yaml:\"protocol,omitempty\" json:\"protocol,omitempty\"`\n}\n\n// MountPropagation represents an \"enum\" for mount propagation options,\n// see also Mount.\ntype MountPropagation string\n\nconst (\n\t// MountPropagationNone specifies that no mount propagation\n\t// (\"private\" in Linux terminology).\n\tMountPropagationNone MountPropagation = \"None\"\n\t// MountPropagationHostToContainer specifies that mounts get propagated\n\t// from the host to the container (\"rslave\" in Linux).\n\tMountPropagationHostToContainer MountPropagation = \"HostToContainer\"\n\t// MountPropagationBidirectional specifies that mounts get propagated from\n\t// the host to the container and from the container to the host\n\t// (\"rshared\" in Linux).\n\tMountPropagationBidirectional MountPropagation = \"Bidirectional\"\n)\n\n// PortMappingProtocol represents an \"enum\" for port mapping protocol options,\n// see also PortMapping.\ntype PortMappingProtocol string\n\nconst (\n\t// PortMappingProtocolTCP specifies TCP protocol\n\tPortMappingProtocolTCP PortMappingProtocol = \"TCP\"\n\t// PortMappingProtocolUDP specifies UDP protocol\n\tPortMappingProtocolUDP PortMappingProtocol = \"UDP\"\n\t// PortMappingProtocolSCTP specifies SCTP protocol\n\tPortMappingProtocolSCTP PortMappingProtocol = \"SCTP\"\n)\n"
  },
  {
    "path": "pkg/apis/config/v1alpha4/yaml.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha4\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n/*\nCustom YAML (de)serialization for these types\n*/\n\n// UnmarshalYAML implements custom decoding YAML\n// https://godoc.org/sigs.k8s.io/yaml/goyaml.v3\nfunc (m *Mount) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\t// first unmarshal in the alias type (to avoid a recursion loop on unmarshal)\n\ttype MountAlias Mount\n\tvar a MountAlias\n\tif err := unmarshal(&a); err != nil {\n\t\treturn err\n\t}\n\t// now handle propagation\n\tswitch a.Propagation {\n\tcase \"\": // unset, will be defaulted\n\tcase MountPropagationNone:\n\tcase MountPropagationHostToContainer:\n\tcase MountPropagationBidirectional:\n\tdefault:\n\t\treturn errors.Errorf(\"Unknown MountPropagation: %q\", a.Propagation)\n\t}\n\t// and copy over the fields\n\t*m = Mount(a)\n\treturn nil\n}\n\n// UnmarshalYAML implements custom decoding YAML\n// https://godoc.org/sigs.k8s.io/yaml/goyaml.v3\nfunc (p *PortMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\t// first unmarshal in the alias type (to avoid a recursion loop on unmarshal)\n\ttype PortMappingAlias PortMapping\n\tvar a PortMappingAlias\n\tif err := unmarshal(&a); err != nil {\n\t\treturn err\n\t}\n\t// now handle the protocol field\n\ta.Protocol = PortMappingProtocol(strings.ToUpper(string(a.Protocol)))\n\tswitch a.Protocol {\n\tcase \"\": // unset, will be defaulted\n\tcase PortMappingProtocolTCP:\n\tcase PortMappingProtocolUDP:\n\tcase PortMappingProtocolSCTP:\n\tdefault:\n\t\treturn errors.Errorf(\"Unknown PortMappingProtocol: %q\", a.Protocol)\n\t}\n\t// and copy over the fields\n\t*p = PortMapping(a)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apis/config/v1alpha4/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha4\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Cluster) DeepCopyInto(out *Cluster) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tif in.Nodes != nil {\n\t\tin, out := &in.Nodes, &out.Nodes\n\t\t*out = make([]Node, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tin.Networking.DeepCopyInto(&out.Networking)\n\tif in.FeatureGates != nil {\n\t\tin, out := &in.FeatureGates, &out.FeatureGates\n\t\t*out = make(map[string]bool, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.RuntimeConfig != nil {\n\t\tin, out := &in.RuntimeConfig, &out.RuntimeConfig\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.KubeadmConfigPatches != nil {\n\t\tin, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.KubeadmConfigPatchesJSON6902 != nil {\n\t\tin, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902\n\t\t*out = make([]PatchJSON6902, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ContainerdConfigPatches != nil {\n\t\tin, out := &in.ContainerdConfigPatches, &out.ContainerdConfigPatches\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ContainerdConfigPatchesJSON6902 != nil {\n\t\tin, out := &in.ContainerdConfigPatchesJSON6902, &out.ContainerdConfigPatchesJSON6902\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.\nfunc (in *Cluster) DeepCopy() *Cluster {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Cluster)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Mount) DeepCopyInto(out *Mount) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mount.\nfunc (in *Mount) DeepCopy() *Mount {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Mount)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Networking) DeepCopyInto(out *Networking) {\n\t*out = *in\n\tif in.DNSSearch != nil {\n\t\tin, out := &in.DNSSearch, &out.DNSSearch\n\t\t*out = new([]string)\n\t\tif **in != nil {\n\t\t\tin, out := *in, *out\n\t\t\t*out = make([]string, len(*in))\n\t\t\tcopy(*out, *in)\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Networking.\nfunc (in *Networking) DeepCopy() *Networking {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Networking)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Node) DeepCopyInto(out *Node) {\n\t*out = *in\n\tif in.Labels != nil {\n\t\tin, out := &in.Labels, &out.Labels\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.ExtraMounts != nil {\n\t\tin, out := &in.ExtraMounts, &out.ExtraMounts\n\t\t*out = make([]Mount, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ExtraPortMappings != nil {\n\t\tin, out := &in.ExtraPortMappings, &out.ExtraPortMappings\n\t\t*out = make([]PortMapping, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.KubeadmConfigPatches != nil {\n\t\tin, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.KubeadmConfigPatchesJSON6902 != nil {\n\t\tin, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902\n\t\t*out = make([]PatchJSON6902, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Node.\nfunc (in *Node) DeepCopy() *Node {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Node)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *PatchJSON6902) DeepCopyInto(out *PatchJSON6902) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchJSON6902.\nfunc (in *PatchJSON6902) DeepCopy() *PatchJSON6902 {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(PatchJSON6902)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *PortMapping) DeepCopyInto(out *PortMapping) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortMapping.\nfunc (in *PortMapping) DeepCopy() *PortMapping {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(PortMapping)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *TypeMeta) DeepCopyInto(out *TypeMeta) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeMeta.\nfunc (in *TypeMeta) DeepCopy() *TypeMeta {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TypeMeta)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/build.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"runtime\"\n\n\t\"sigs.k8s.io/kind/pkg/build/nodeimage/internal/kube\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// Build builds a node image using the supplied options\nfunc Build(options ...Option) error {\n\t// default options\n\tctx := &buildContext{\n\t\timage:     DefaultImage,\n\t\tbaseImage: DefaultBaseImage,\n\t\tlogger:    log.NoopLogger{},\n\t\tarch:      runtime.GOARCH,\n\t}\n\n\t// apply user options\n\tfor _, option := range options {\n\t\tif err := option.apply(ctx); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// verify that we're using a supported arch\n\tif !supportedArch(ctx.arch) {\n\t\tctx.logger.Warnf(\"unsupported architecture %q\", ctx.arch)\n\t}\n\n\tif ctx.buildType == \"\" {\n\t\tctx.buildType = detectBuildType(ctx.kubeParam)\n\t\tif ctx.buildType != \"\" {\n\t\t\tctx.logger.V(0).Infof(\"Detected build type: %q\", ctx.buildType)\n\t\t}\n\t}\n\n\tif ctx.buildType == \"url\" {\n\t\tctx.logger.V(0).Infof(\"Building using URL: %q\", ctx.kubeParam)\n\t\tbuilder, err := kube.NewURLBuilder(ctx.logger, ctx.kubeParam)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx.builder = builder\n\t}\n\n\tif ctx.buildType == \"file\" {\n\t\tctx.logger.V(0).Infof(\"Building using local file: %q\", ctx.kubeParam)\n\t\tif info, err := os.Stat(ctx.kubeParam); err == nil && info.Mode().IsRegular() {\n\t\t\tbuilder, err := kube.NewTarballBuilder(ctx.logger, ctx.kubeParam)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tctx.builder = builder\n\t\t}\n\t}\n\n\tif ctx.buildType == \"release\" {\n\t\tctx.logger.V(0).Infof(\"Building using release %q artifacts\", ctx.kubeParam)\n\t\tkubever, err := version.ParseSemantic(ctx.kubeParam)\n\t\tif err == nil {\n\t\t\tbuilder, err := kube.NewReleaseBuilder(ctx.logger, \"v\"+kubever.String(), ctx.arch)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tctx.builder = builder\n\t\t} else {\n\t\t\tif _, err := os.Stat(ctx.kubeParam); err != nil {\n\t\t\t\tctx.logger.V(0).Infof(\"%s is not a valid kubernetes version\", ctx.kubeParam)\n\t\t\t\treturn fmt.Errorf(\"%s is not a valid kubernetes version\", ctx.kubeParam)\n\t\t\t}\n\t\t}\n\t}\n\n\tif ctx.builder == nil {\n\t\t// locate sources if no kubernetes source was specified\n\t\tif ctx.kubeParam == \"\" {\n\t\t\tkubeRoot, err := kube.FindSource()\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"error finding kuberoot\")\n\t\t\t}\n\t\t\tctx.kubeParam = kubeRoot\n\t\t}\n\t\tctx.logger.V(0).Infof(\"Building using source: %q\", ctx.kubeParam)\n\n\t\t// initialize bits\n\t\tbuilder, err := kube.NewDockerBuilder(ctx.logger, ctx.kubeParam, ctx.arch)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx.builder = builder\n\t}\n\n\t// do the actual build\n\treturn ctx.Build()\n}\n\n// detectBuildType detect the type of build required based on the param passed in the following order\n// url: if the param is a valid http or https url\n// file: if the param refers to an existing regular file\n// source: if the param refers to an existing directory\n// release: if the param is a semantic version expression (does this require the v preprended?\nfunc detectBuildType(param string) string {\n\tu, err := url.ParseRequestURI(param)\n\tif err == nil {\n\t\tif u.Scheme == \"http\" || u.Scheme == \"https\" {\n\t\t\treturn \"url\"\n\t\t}\n\t}\n\tif info, err := os.Stat(param); err == nil {\n\t\tif info.Mode().IsRegular() {\n\t\t\treturn \"file\"\n\t\t}\n\t\tif info.Mode().IsDir() {\n\t\t\treturn \"source\"\n\t\t}\n\t}\n\t_, err = version.ParseSemantic(param)\n\tif err == nil {\n\t\treturn \"release\"\n\t}\n\treturn \"\"\n}\n\nfunc supportedArch(arch string) bool {\n\tswitch arch {\n\tdefault:\n\t\treturn false\n\t// currently we nominally support building node images for these\n\tcase \"amd64\":\n\tcase \"arm64\":\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/buildcontext.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/build/nodeimage/internal/container/docker\"\n\t\"sigs.k8s.io/kind/pkg/build/nodeimage/internal/kube\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\nconst (\n\t// httpProxy is the HTTP_PROXY environment variable key\n\thttpProxy = \"HTTP_PROXY\"\n\t// httpsProxy is the HTTPS_PROXY environment variable key\n\thttpsProxy = \"HTTPS_PROXY\"\n\t// noProxy is the NO_PROXY environment variable key\n\tnoProxy = \"NO_PROXY\"\n)\n\n// buildContext is used to build the kind node image, and contains\n// build configuration\ntype buildContext struct {\n\t// option fields\n\timage     string\n\tbaseImage string\n\tlogger    log.Logger\n\tarch      string\n\tbuildType string\n\tkubeParam string\n\t// non-option fields\n\tbuilder kube.Builder\n}\n\n// Build builds the cluster node image, the source dir must be set on\n// the buildContext\nfunc (c *buildContext) Build() (err error) {\n\t// ensure kubernetes build is up-to-date first\n\tc.logger.V(0).Info(\"Starting to build Kubernetes\")\n\tbits, err := c.builder.Build()\n\tif err != nil {\n\t\tc.logger.Errorf(\"Failed to build Kubernetes: %v\", err)\n\t\treturn errors.Wrap(err, \"failed to build kubernetes\")\n\t}\n\tc.logger.V(0).Info(\"Finished building Kubernetes\")\n\n\t// then perform the actual docker image build\n\tc.logger.V(0).Info(\"Building node image ...\")\n\treturn c.buildImage(bits)\n}\n\nfunc (c *buildContext) buildImage(bits kube.Bits) error {\n\t// create build container\n\t// NOTE: we are using docker run + docker commit, so we can install\n\t// debian packages without permanently copying them into the image.\n\t// if docker gets proper squash support, we can rm them instead\n\t// This also allows the KubeBit implementations to programmatically\n\t// install in the image\n\tcontainerID, err := c.createBuildContainer()\n\tcmder := docker.ContainerCmder(containerID)\n\n\t// ensure we will delete it\n\tif containerID != \"\" {\n\t\tdefer func() {\n\t\t\t_ = exec.Command(\"docker\", \"rm\", \"-f\", \"-v\", containerID).Run()\n\t\t}()\n\t}\n\tif err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to create build container: %v\", err)\n\t\treturn err\n\t}\n\n\tc.logger.V(0).Info(\"Building in container: \" + containerID)\n\n\t// copy artifacts in\n\tfor _, binary := range bits.BinaryPaths() {\n\t\t// TODO: probably should be /usr/local/bin, but the existing kubelet\n\t\t// service file expects /usr/bin/kubelet\n\t\tnodePath := \"/usr/bin/\" + path.Base(binary)\n\t\tif err := exec.Command(\"docker\", \"cp\", binary, containerID+\":\"+nodePath).Run(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := cmder.Command(\"chmod\", \"+x\", nodePath).Run(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := cmder.Command(\"chown\", \"root:root\", nodePath).Run(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// write version\n\t// TODO: support grabbing version from a binary instead?\n\t// This may or may not be a good idea ...\n\trawVersion := bits.Version()\n\tparsedVersion, err := version.ParseSemantic(rawVersion)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"invalid Kubernetes version\")\n\t}\n\tif err := createFile(cmder, \"/kind/version\", rawVersion); err != nil {\n\t\treturn err\n\t}\n\n\t// pre-pull images that were not part of the build and write CNI / storage\n\t// manifests\n\tif _, err = c.prePullImagesAndWriteManifests(bits, parsedVersion, containerID); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to pull Images: %v\", err)\n\t\treturn err\n\t}\n\n\t// Save the image changes to a new image\n\tif err = exec.Command(\n\t\t\"docker\", \"commit\",\n\t\t// we need to put this back after changing it when running the image\n\t\t\"--change\", `ENTRYPOINT [ \"/usr/local/bin/entrypoint\", \"/sbin/init\" ]`,\n\t\t// remove proxy settings since they're for the building process\n\t\t// and should not be carried with the built image\n\t\t\"--change\", `ENV HTTP_PROXY=\"\" HTTPS_PROXY=\"\" NO_PROXY=\"\"`,\n\t\tcontainerID, c.image,\n\t).Run(); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to save image: %v\", err)\n\t\treturn err\n\t}\n\n\tc.logger.V(0).Infof(\"Image %q build completed.\", c.image)\n\treturn nil\n}\n\n// returns a set of image tags that will be side-loaded\nfunc (c *buildContext) getBuiltImages(bits kube.Bits) (sets.String, error) {\n\timages := sets.NewString()\n\tfor _, path := range bits.ImagePaths() {\n\t\ttags, err := docker.GetArchiveTags(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\timages.Insert(tags...)\n\t}\n\treturn images, nil\n}\n\n// must be run after kubernetes has been installed on the node\nfunc (c *buildContext) prePullImagesAndWriteManifests(bits kube.Bits, parsedVersion *version.Version, containerID string) ([]string, error) {\n\t// first get the images we actually built\n\tbuiltImages, err := c.getBuiltImages(bits)\n\tif err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to get built images: %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// helpers to run things in the build container\n\tcmder := docker.ContainerCmder(containerID)\n\n\t// For kubernetes v1.15+ (actually 1.16 alpha versions) we may need to\n\t// drop the arch suffix from images to get the expected image\n\tarchSuffix := \"-\" + c.arch\n\tfixRepository := func(repository string) string {\n\t\tif strings.HasSuffix(repository, archSuffix) {\n\t\t\tfixed := strings.TrimSuffix(repository, archSuffix)\n\t\t\tc.logger.V(1).Info(\"fixed: \" + repository + \" -> \" + fixed)\n\t\t\trepository = fixed\n\t\t}\n\t\treturn repository\n\t}\n\n\t// Determine accurate built tags using the logic that will be applied\n\t// when rewriting tags during archive loading\n\tfixedImages := sets.NewString()\n\tfixedImagesMap := make(map[string]string, builtImages.Len()) // key: original images, value: fixed images\n\tfor _, image := range builtImages.List() {\n\t\tregistry, tag, err := docker.SplitImage(image)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tregistry = fixRepository(registry)\n\t\tfixedImage := registry + \":\" + tag\n\t\tfixedImages.Insert(fixedImage)\n\t\tfixedImagesMap[image] = fixedImage\n\t}\n\tbuiltImages = fixedImages\n\tc.logger.V(1).Info(\"Detected built images: \" + strings.Join(builtImages.List(), \", \"))\n\n\t// gets the list of images required by kubeadm\n\trequiredImages, err := exec.OutputLines(cmder.Command(\n\t\t\"kubeadm\", \"config\", \"images\", \"list\", \"--kubernetes-version\", bits.Version(),\n\t))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// replace pause image with our own\n\tcontainerdConfig, err := exec.Output(cmder.Command(\"cat\", containerdConfigPath))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpauseImage, err := findSandboxImage(string(containerdConfig))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tn := 0\n\tfor _, image := range requiredImages {\n\t\tif !strings.Contains(image, \"pause\") {\n\t\t\trequiredImages[n] = image\n\t\t\tn++\n\t\t}\n\t}\n\trequiredImages = append(requiredImages[:n], pauseImage)\n\n\tif parsedVersion.LessThan(version.MustParseSemantic(\"v1.24.0\")) {\n\t\tif err := configureContainerdSystemdCgroupFalse(cmder, string(containerdConfig)); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// write the default CNI manifest\n\tif err := createFile(cmder, defaultCNIManifestLocation, defaultCNIManifest); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed write default CNI Manifest: %v\", err)\n\t\treturn nil, err\n\t}\n\t// all builds should install the default CNI images from the above manifest currently\n\trequiredImages = append(requiredImages, defaultCNIImages...)\n\n\t// write the default Storage manifest\n\tif err := createFile(cmder, defaultStorageManifestLocation, defaultStorageManifest); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed write default Storage Manifest: %v\", err)\n\t\treturn nil, err\n\t}\n\t// all builds should install the default storage driver images currently\n\trequiredImages = append(requiredImages, defaultStorageImages...)\n\n\t// setup image importer\n\timporter := newContainerdImporter(cmder)\n\tif err := importer.Prepare(); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to prepare containerd to load images %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// TODO: return this error?\n\tdefer func() {\n\t\tif err := importer.End(); err != nil {\n\t\t\tc.logger.Errorf(\"Image build Failed! Failed to tear down containerd after loading images %v\", err)\n\t\t}\n\t}()\n\n\tfns := []func() error{}\n\tosArch := dockerBuildOsAndArch(c.arch)\n\tfor _, image := range requiredImages {\n\t\timage := image // https://golang.org/doc/faq#closures_and_goroutines\n\t\tif !builtImages.Has(image) {\n\t\t\tfns = append(fns, func() error {\n\t\t\t\tif err = importer.Pull(image, osArch); err != nil {\n\t\t\t\t\tc.logger.Warnf(\"Failed to pull %s with error: %v\", image, err)\n\t\t\t\t\trunE := exec.RunErrorForError(err)\n\t\t\t\t\tc.logger.Warn(string(runE.Output))\n\t\t\t\t\tc.logger.Warnf(\"Retrying %s pull after 1s ...\", image)\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t\treturn importer.Pull(image, osArch)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\t}\n\t// Wait for containerd socket to be ready, which may take 1s when running under emulation\n\tif err := importer.WaitForReady(); err != nil {\n\t\tc.logger.Errorf(\"Image build failed, containerd did not become ready %v\", err)\n\t\treturn nil, err\n\t}\n\tif err := errors.AggregateConcurrent(fns); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// create a plan of image loading\n\tloadFns := []func() error{}\n\tfor _, image := range bits.ImagePaths() {\n\t\timage := image // capture loop var\n\t\tloadFns = append(loadFns, func() error {\n\t\t\tf, err := os.Open(image)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\treturn importer.LoadCommand().SetStdout(os.Stdout).SetStderr(os.Stderr).SetStdin(f).Run()\n\t\t\t// we will rewrite / correct the tags in tagFns below\n\t\t})\n\t}\n\n\t// run all image loading concurrently until one fails or all succeed\n\tif err := errors.UntilErrorConcurrent(loadFns); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to load images %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// create a plan of image re-tagging\n\ttagFns := []func() error{}\n\tfor unfixed, fixed := range fixedImagesMap {\n\t\tunfixed, fixed := unfixed, fixed // capture loop var\n\t\tif unfixed != fixed {\n\t\t\ttagFns = append(tagFns, func() error {\n\t\t\t\treturn importer.Tag(unfixed, fixed)\n\t\t\t})\n\t\t}\n\t}\n\n\t// run all image re-tagging concurrently until one fails or all succeed\n\tif err := errors.UntilErrorConcurrent(tagFns); err != nil {\n\t\tc.logger.Errorf(\"Image build Failed! Failed to re-tag images %v\", err)\n\t\treturn nil, err\n\t}\n\n\treturn importer.ListImported()\n}\n\nfunc (c *buildContext) createBuildContainer() (id string, err error) {\n\t// attempt to explicitly pull the image if it doesn't exist locally\n\t// errors here are non-critical; we'll proceed with execution, which includes a pull operation\n\t_ = docker.Pull(c.logger, c.baseImage, dockerBuildOsAndArch(c.arch), 4)\n\t// this should be good enough: a specific prefix, the current unix time,\n\t// and a little random bits in case we have multiple builds simultaneously\n\trandom := rand.New(rand.NewSource(time.Now().UnixNano())).Int31()\n\tid = fmt.Sprintf(\"kind-build-%d-%d\", time.Now().UTC().Unix(), random)\n\trunArgs := []string{\n\t\t\"-d\",                 // make the client exit while the container continues to run\n\t\t\"--entrypoint=sleep\", // the container should hang forever, so we can exec in it\n\t\t\"--name=\" + id,\n\t\t\"--platform=\" + dockerBuildOsAndArch(c.arch),\n\t\t\"--security-opt\", \"seccomp=unconfined\",\n\t}\n\t// pass proxy settings from environment variables to the building container\n\t// to make them work during the building process\n\tfor _, name := range []string{httpProxy, httpsProxy, noProxy} {\n\t\tval := os.Getenv(name)\n\t\tif val == \"\" {\n\t\t\tval = os.Getenv(strings.ToLower(name))\n\t\t}\n\t\tif val != \"\" {\n\t\t\trunArgs = append(runArgs, \"--env\", name+\"=\"+val)\n\t\t}\n\t}\n\terr = docker.Run(\n\t\tc.baseImage,\n\t\trunArgs,\n\t\t[]string{\n\t\t\t\"infinity\", // sleep infinitely to keep container running indefinitely\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn id, errors.Wrap(err, \"failed to create build container\")\n\t}\n\treturn id, nil\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/const.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\n// these are well known paths within the node image\nconst (\n\t// TODO: refactor kubernetesVersionLocation to a common internal package\n\tkubernetesVersionLocation      = \"/kind/version\"\n\tdefaultCNIManifestLocation     = \"/kind/manifests/default-cni.yaml\"\n\tdefaultStorageManifestLocation = \"/kind/manifests/default-storage.yaml\"\n)\n"
  },
  {
    "path": "pkg/build/nodeimage/const_cni.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\n/*\nThe default CNI manifest and images are our own tiny kindnet\n*/\n\nconst kindnetdImage = \"docker.io/kindest/kindnetd:v20260213-ea8e5717\"\n\nvar defaultCNIImages = []string{kindnetdImage}\n\n// TODO: migrate to fully patching and deprecate the template\nconst defaultCNIManifest = `\n# kindnetd networking manifest\n# would you kindly template this file\n# would you kindly patch this file\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: kindnet\nrules:\n  - apiGroups:\n    - policy\n    resources:\n    - podsecuritypolicies\n    verbs:\n    - use\n    resourceNames:\n    - kindnet\n  - apiGroups:\n      - \"\"\n    resources:\n      - nodes\n      - pods\n      - namespaces\n    verbs:\n      - list\n      - watch\n  - apiGroups:\n     - \"networking.k8s.io\"\n    resources:\n      - networkpolicies\n    verbs:\n      - list\n      - watch\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: kindnet\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: kindnet\nsubjects:\n- kind: ServiceAccount\n  name: kindnet\n  namespace: kube-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: kindnet\n  namespace: kube-system\n---\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: kindnet\n  namespace: kube-system\n  labels:\n    tier: node\n    app: kindnet\n    k8s-app: kindnet\nspec:\n  selector:\n    matchLabels:\n      app: kindnet\n  template:\n    metadata:\n      labels:\n        tier: node\n        app: kindnet\n        k8s-app: kindnet\n    spec:\n      hostNetwork: true\n      nodeSelector:\n        kubernetes.io/os: linux\n      tolerations:\n      - operator: Exists\n      priorityClassName: system-node-critical\n      serviceAccountName: kindnet\n      containers:\n      - name: kindnet-cni\n        image: ` + kindnetdImage + `\n        env:\n        - name: HOST_IP\n          valueFrom:\n            fieldRef:\n              fieldPath: status.hostIP\n        - name: POD_IP\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: POD_SUBNET\n          value: {{ .PodSubnet }}\n        volumeMounts:\n        - name: cni-cfg\n          mountPath: /etc/cni/net.d\n        - name: xtables-lock\n          mountPath: /run/xtables.lock\n          readOnly: false\n        - name: lib-modules\n          mountPath: /lib/modules\n          readOnly: true\n        - name: nri-plugin\n          mountPath: /var/run/nri\n        resources:\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n          limits:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          privileged: false\n          capabilities:\n            add: [\"NET_RAW\", \"NET_ADMIN\"]\n      volumes:\n      - name: cni-cfg\n        hostPath:\n          path: /etc/cni/net.d\n      - name: xtables-lock\n        hostPath:\n          path: /run/xtables.lock\n          type: FileOrCreate\n      - name: lib-modules\n        hostPath:\n          path: /lib/modules\n      - name: nri-plugin\n        hostPath:\n          path: /var/run/nri\n---\n`\n"
  },
  {
    "path": "pkg/build/nodeimage/const_storage.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\n/*\nThe default PV driver manifest and images are provisionally rancher.io/local-path-provisioner\nNOTE: we have customized it in the following ways:\n- storage is under /var instead of /opt\n- our own image and helper image\n- schedule to linux nodes only\n- install as the default storage class\n- tolerate control plane scheduling taints\n*/\n\nconst storageProvisionerImage = \"docker.io/kindest/local-path-provisioner:v20260213-ea8e5717\"\nconst storageHelperImage = \"docker.io/kindest/local-path-helper:v20260131-7181c60a\"\n\n// image we need to preload\nvar defaultStorageImages = []string{storageProvisionerImage, storageHelperImage}\n\nconst defaultStorageManifest = `\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: local-path-storage\n\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: local-path-provisioner-service-account\n  namespace: local-path-storage\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: local-path-provisioner-role\n  namespace: local-path-storage\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"get\", \"list\", \"watch\", \"create\", \"patch\", \"update\", \"delete\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: local-path-provisioner-role\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"nodes\", \"persistentvolumeclaims\", \"configmaps\", \"pods\", \"pods/log\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n  - apiGroups: [\"\"]\n    resources: [\"persistentvolumes\"]\n    verbs: [\"get\", \"list\", \"watch\", \"create\", \"patch\", \"update\", \"delete\"]\n  - apiGroups: [\"\"]\n    resources: [\"events\"]\n    verbs: [\"create\", \"patch\"]\n  - apiGroups: [\"storage.k8s.io\"]\n    resources: [\"storageclasses\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: local-path-provisioner-bind\n  namespace: local-path-storage\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: local-path-provisioner-role\nsubjects:\n  - kind: ServiceAccount\n    name: local-path-provisioner-service-account\n    namespace: local-path-storage\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: local-path-provisioner-bind\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: local-path-provisioner-role\nsubjects:\n  - kind: ServiceAccount\n    name: local-path-provisioner-service-account\n    namespace: local-path-storage\n\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: local-path-provisioner\n  namespace: local-path-storage\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: local-path-provisioner\n  template:\n    metadata:\n      labels:\n        app: local-path-provisioner\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      # TODO: Remove the \"master\" taint after kubeadm nodes no longer have it.\n      # This can be done once kind no longer supports kubeadm 1.24.\n      # https://github.com/kubernetes-sigs/kind/issues/1699\n      tolerations:\n      - key: node-role.kubernetes.io/control-plane\n        operator: Equal\n        effect: NoSchedule\n      - key: node-role.kubernetes.io/master\n        operator: Equal\n        effect: NoSchedule\n      serviceAccountName: local-path-provisioner-service-account\n      containers:\n        - name: local-path-provisioner\n          image: ` + storageProvisionerImage + `\n          imagePullPolicy: IfNotPresent\n          command:\n            - local-path-provisioner\n            - --debug\n            - start\n            - --helper-image\n            - ` + storageHelperImage + `\n            - --config\n            - /etc/config/config.json\n          volumeMounts:\n            - name: config-volume\n              mountPath: /etc/config/\n          env:\n            - name: POD_NAMESPACE\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.namespace\n            - name: CONFIG_MOUNT_PATH\n              value: /etc/config/\n      volumes:\n        - name: config-volume\n          configMap:\n            name: local-path-config\n\n---\napiVersion: storage.k8s.io/v1\nkind: StorageClass\nmetadata:\n  name: standard\n  namespace: kube-system\n  annotations:\n    storageclass.kubernetes.io/is-default-class: \"true\"\nprovisioner: rancher.io/local-path\nvolumeBindingMode: WaitForFirstConsumer\nreclaimPolicy: Delete\n\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: local-path-config\n  namespace: local-path-storage\ndata:\n  config.json: |-\n    {\n            \"nodePathMap\":[\n            {\n                    \"node\":\"DEFAULT_PATH_FOR_NON_LISTED_NODES\",\n                    \"paths\":[\"/var/local-path-provisioner\"]\n            }\n            ]\n    }\n  setup: |-\n    #!/bin/sh\n    set -eu\n    mkdir -m 0777 -p \"$VOL_DIR\"\n  teardown: |-\n    #!/bin/sh\n    set -eu\n    rm -rf \"$VOL_DIR\"\n  helperPod.yaml: |-\n    apiVersion: v1\n    kind: Pod\n    metadata:\n      name: helper-pod\n    spec:\n      priorityClassName: system-node-critical\n      tolerations:\n        - key: node.kubernetes.io/disk-pressure\n          operator: Exists\n          effect: NoSchedule\n      containers:\n      - name: helper-pod\n        image: ` + storageHelperImage + `\n        imagePullPolicy: IfNotPresent\n`\n"
  },
  {
    "path": "pkg/build/nodeimage/containerd.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/patch\"\n)\n\nconst containerdConfigPath = \"/etc/containerd/config.toml\"\n\nconst containerdConfigPatchSystemdCgroupFalse = `\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n  SystemdCgroup = false\n\n[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.test-handler.options]\n  SystemdCgroup = false\n`\n\nfunc configureContainerdSystemdCgroupFalse(containerCmdr exec.Cmder, config string) error {\n\tpatched, err := patch.ContainerdTOML(config, []string{containerdConfigPatchSystemdCgroupFalse}, []string{})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to configure containerd SystemdCgroup=false\")\n\t}\n\terr = containerCmdr.Command(\n\t\t\"cp\", \"/dev/stdin\", containerdConfigPath,\n\t).SetStdin(strings.NewReader(patched)).Run()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to configure containerd SystemdCgroup=false\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/defaults.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\n// DefaultImage is the default name:tag for the built image\nconst DefaultImage = \"kindest/node:latest\"\n\n// DefaultBaseImage is the default base image used\n// TODO: come up with a reasonable solution to digest pinning\n// https://github.com/moby/moby/issues/43188\nconst DefaultBaseImage = \"docker.io/kindest/base:v20260214-ea8e5717\"\n"
  },
  {
    "path": "pkg/build/nodeimage/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package nodeimage implements functionality to build the kind node image\npackage nodeimage\n"
  },
  {
    "path": "pkg/build/nodeimage/helpers.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\nimport (\n\t\"path\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// createFile creates the file at filePath in the container,\n// ensuring the directory exists and writing contents to the file\nfunc createFile(containerCmder exec.Cmder, filePath, contents string) error {\n\t// NOTE: the paths inside the container should use the path package\n\t// and not filepath (!), we want posixy paths in the linux container, NOT\n\t// whatever path format the host uses. For paths on the host we use filepath\n\tif err := containerCmder.Command(\"mkdir\", \"-p\", path.Dir(filePath)).Run(); err != nil {\n\t\treturn err\n\t}\n\n\treturn containerCmder.Command(\n\t\t\"cp\", \"/dev/stdin\", filePath,\n\t).SetStdin(\n\t\tstrings.NewReader(contents),\n\t).Run()\n}\n\nfunc findSandboxImage(config string) (string, error) {\n\tmatch := regexp.MustCompile(`sandbox_image\\s+=\\s+\"([^\\n]+)\"`).FindStringSubmatch(config)\n\tif len(match) < 2 {\n\t\treturn \"\", errors.New(\"failed to parse sandbox_image from config\")\n\t}\n\treturn match[1], nil\n}\n\nfunc dockerBuildOsAndArch(arch string) string {\n\treturn \"linux/\" + arch\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/imageimporter.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\nimport (\n\t\"io\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\ntype containerdImporter struct {\n\tcontainerCmder exec.Cmder\n}\n\nfunc newContainerdImporter(containerCmder exec.Cmder) *containerdImporter {\n\treturn &containerdImporter{\n\t\tcontainerCmder: containerCmder,\n\t}\n}\n\nfunc (c *containerdImporter) Prepare() error {\n\tif err := c.containerCmder.Command(\n\t\t\"bash\", \"-c\", \"nohup containerd > /dev/null 2>&1 &\",\n\t).Run(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *containerdImporter) WaitForReady() error {\n\t// ctr doesn't respect timeouts when the socket doesn't exist\n\t// so we'll look for the socket to exist ourselves, THEN attempt ctr info\n\t// TODO: we are assuming the socket path, and this is kind of hacky\n\tif err := c.containerCmder.Command(\n\t\t\"bash\", \"-c\", `set -e\n# wait for socket to exist\nfor i in {0..3}; do\n  if [ -S /run/containerd/containerd.sock ]; then\n    break\n  fi\n  sleep \"$i\"\ndone\n# check healthy\nctr info\n`,\n\t).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to wait for containerd to become ready\")\n\t}\n\treturn nil\n}\n\nfunc (c *containerdImporter) End() error {\n\treturn c.containerCmder.Command(\"pkill\", \"containerd\").Run()\n}\n\nfunc (c *containerdImporter) Pull(image, platform string) error {\n\treturn c.containerCmder.Command(\n\t\t\"ctr\", \"--namespace=k8s.io\", \"content\", \"fetch\", \"--platform=\"+platform, image,\n\t).SetStdout(io.Discard).SetStderr(io.Discard).Run()\n}\n\nfunc (c *containerdImporter) LoadCommand() exec.Cmd {\n\treturn c.containerCmder.Command(\n\t\t// TODO: ideally we do not need this in the future. we have fixed at least one image\n\t\t\"ctr\", \"--namespace=k8s.io\", \"images\", \"import\", \"--label=io.cri-containerd.pinned=pinned\", \"--all-platforms\", \"--no-unpack\", \"--digests\", \"-\",\n\t)\n}\n\nfunc (c *containerdImporter) Tag(src, target string) error {\n\treturn c.containerCmder.Command(\n\t\t\"ctr\", \"--namespace=k8s.io\", \"images\", \"tag\", \"--force\", src, target,\n\t).Run()\n}\n\nfunc (c *containerdImporter) ListImported() ([]string, error) {\n\treturn exec.OutputLines(c.containerCmder.Command(\"ctr\", \"--namespace=k8s.io\", \"images\", \"list\", \"-q\"))\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/archive.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package docker contains helpers for working with docker\n// This package has no stability guarantees whatsoever!\npackage docker\n\nimport (\n\t\"archive/tar\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// GetArchiveTags obtains a list of \"repo:tag\" docker image tags from a\n// given docker image archive (tarball) path\n// compatible with all known specs:\n// https://github.com/moby/moby/blob/master/image/spec/v1.md\n// https://github.com/moby/moby/blob/master/image/spec/v1.1.md\n// https://github.com/moby/moby/blob/master/image/spec/v1.2.md\nfunc GetArchiveTags(path string) ([]string, error) {\n\t// open the archive and find the repositories entry\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\ttr := tar.NewReader(f)\n\tvar hdr *tar.Header\n\tfor {\n\t\thdr, err = tr.Next()\n\t\tif err == io.EOF {\n\t\t\treturn nil, errors.New(\"could not find image metadata\")\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif hdr.Name == \"manifest.json\" || hdr.Name == \"repositories\" {\n\t\t\tbreak\n\t\t}\n\t}\n\t// read and parse the tags\n\tb, err := io.ReadAll(tr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres := []string{}\n\t// parse\n\tswitch hdr.Name {\n\tcase \"repositories\":\n\t\trepoTags, err := parseRepositories(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// convert to tags in the docker CLI sense\n\t\tfor repo, tags := range repoTags {\n\t\t\tfor tag := range tags {\n\t\t\t\tres = append(res, fmt.Sprintf(\"%s:%s\", repo, tag))\n\t\t\t}\n\t\t}\n\tcase \"manifest.json\":\n\t\tmanifest, err := parseDockerV1Manifest(b)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres = append(res, manifest[0].RepoTags...)\n\t}\n\treturn res, nil\n}\n\n// archiveRepositories represents repository:tag:ref\n//\n// https://github.com/moby/moby/blob/master/image/spec/v1.md\n// https://github.com/moby/moby/blob/master/image/spec/v1.1.md\n// https://github.com/moby/moby/blob/master/image/spec/v1.2.md\ntype archiveRepositories map[string]map[string]string\n\n// https://github.com/moby/moby/blob/master/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format\ntype metadataEntry struct {\n\tConfig   string   `json:\"Config\"`\n\tRepoTags []string `json:\"RepoTags\"`\n\tLayers   []string `json:\"Layers\"`\n}\n\n// returns repository:tag:ref\nfunc parseRepositories(data []byte) (archiveRepositories, error) {\n\tvar repoTags archiveRepositories\n\tif err := json.Unmarshal(data, &repoTags); err != nil {\n\t\treturn nil, err\n\t}\n\treturn repoTags, nil\n}\n\n// parseDockerV1Manifest parses Docker Image Spec v1 manifest (not OCI Image Spec manifest)\n// https://github.com/moby/moby/blob/v20.10.22/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format\nfunc parseDockerV1Manifest(data []byte) ([]metadataEntry, error) {\n\tvar entries []metadataEntry\n\tif err := json.Unmarshal(data, &entries); err != nil {\n\t\treturn nil, err\n\t}\n\treturn entries, nil\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package docker contains helpers for working with docker\n// This package has no stability guarantees whatsoever!\npackage docker\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/exec.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// containerCmder implements exec.Cmder for docker containers\ntype containerCmder struct {\n\tnameOrID string\n}\n\n// ContainerCmder creates a new exec.Cmder against a docker container\nfunc ContainerCmder(containerNameOrID string) exec.Cmder {\n\treturn &containerCmder{\n\t\tnameOrID: containerNameOrID,\n\t}\n}\n\nfunc (c *containerCmder) Command(command string, args ...string) exec.Cmd {\n\treturn &containerCmd{\n\t\tnameOrID: c.nameOrID,\n\t\tcommand:  command,\n\t\targs:     args,\n\t}\n}\n\nfunc (c *containerCmder) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd {\n\treturn &containerCmd{\n\t\tnameOrID: c.nameOrID,\n\t\tcommand:  command,\n\t\targs:     args,\n\t\tctx:      ctx,\n\t}\n}\n\n// containerCmd implements exec.Cmd for docker containers\ntype containerCmd struct {\n\tnameOrID string // the container name or ID\n\tcommand  string\n\targs     []string\n\tenv      []string\n\tstdin    io.Reader\n\tstdout   io.Writer\n\tstderr   io.Writer\n\tctx      context.Context\n}\n\nfunc (c *containerCmd) Run() error {\n\targs := []string{\n\t\t\"exec\",\n\t\t// run with privileges so we can remount etc..\n\t\t// this might not make sense in the most general sense, but it is\n\t\t// important to many kind commands\n\t\t\"--privileged\",\n\t}\n\tif c.stdin != nil {\n\t\targs = append(args,\n\t\t\t\"-i\", // interactive so we can supply input\n\t\t)\n\t}\n\t// set env\n\tfor _, env := range c.env {\n\t\targs = append(args, \"-e\", env)\n\t}\n\t// specify the container and command, after this everything will be\n\t// args the command in the container rather than to docker\n\targs = append(\n\t\targs,\n\t\tc.nameOrID, // ... against the container\n\t\tc.command,  // with the command specified\n\t)\n\targs = append(\n\t\targs,\n\t\t// finally, with the caller args\n\t\tc.args...,\n\t)\n\tvar cmd exec.Cmd\n\tif c.ctx != nil {\n\t\tcmd = exec.CommandContext(c.ctx, \"docker\", args...)\n\t} else {\n\t\tcmd = exec.Command(\"docker\", args...)\n\t}\n\tif c.stdin != nil {\n\t\tcmd.SetStdin(c.stdin)\n\t}\n\tif c.stderr != nil {\n\t\tcmd.SetStderr(c.stderr)\n\t}\n\tif c.stdout != nil {\n\t\tcmd.SetStdout(c.stdout)\n\t}\n\treturn cmd.Run()\n}\n\nfunc (c *containerCmd) SetEnv(env ...string) exec.Cmd {\n\tc.env = env\n\treturn c\n}\n\nfunc (c *containerCmd) SetStdin(r io.Reader) exec.Cmd {\n\tc.stdin = r\n\treturn c\n}\n\nfunc (c *containerCmd) SetStdout(w io.Writer) exec.Cmd {\n\tc.stdout = w\n\treturn c\n}\n\nfunc (c *containerCmd) SetStderr(w io.Writer) exec.Cmd {\n\tc.stderr = w\n\treturn c\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/image.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// SplitImage splits an image into (registry,tag) following these cases:\n//\n//\talpine -> (alpine, latest)\n//\n//\talpine:latest -> (alpine, latest)\n//\n//\talpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913)\n//\n//\talpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913)\n//\n// NOTE: for our purposes we consider the sha to be part of the tag, and we\n// resolve the implicit :latest\nfunc SplitImage(image string) (registry, tag string, err error) {\n\t// we are looking for ':' and '@'\n\tfirstColon := strings.IndexByte(image, 58)\n\tfirstAt := strings.IndexByte(image, 64)\n\n\t// there should be a registry before the tag, and @/: should not be the last\n\t// character, these cases are assumed not to exist by the rest of the code\n\tif firstColon == 0 || firstAt == 0 || firstColon+1 == len(image) || firstAt+1 == len(image) {\n\t\treturn \"\", \"\", fmt.Errorf(\"unexpected image: %q\", image)\n\t}\n\n\t// NOTE: The order of these cases matters\n\t// case: alpine\n\tif firstColon == -1 && firstAt == -1 {\n\t\treturn image, \"latest\", nil\n\t}\n\n\t// case: alpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\n\tif firstAt != -1 && firstAt < firstColon {\n\t\treturn image[:firstAt], \"latest\" + image[firstAt:], nil\n\t}\n\n\t// case: alpine:latest\n\t// case: alpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\n\treturn image[:firstColon], image[firstColon+1:], nil\n}\n\n// ImageInspect return low-level information on containers images\nfunc ImageInspect(containerNameOrID, format string) ([]string, error) {\n\tcmd := exec.Command(\"docker\", \"image\", \"inspect\",\n\t\t\"-f\", format,\n\t\tcontainerNameOrID, // ... against the container\n\t)\n\n\treturn exec.OutputLines(cmd)\n}\n\n// ImageID return the Id of the container image\nfunc ImageID(containerNameOrID string) (string, error) {\n\tlines, err := ImageInspect(containerNameOrID, \"{{ .Id }}\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"Docker image ID should only be one line, got %d lines\", len(lines))\n\t}\n\treturn lines[0], nil\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/image_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport \"testing\"\n\nfunc TestSplitImage(t *testing.T) {\n\tt.Parallel()\n\t/*\n\t\talpine -> (alpine, latest)\n\n\t\talpine:latest -> (alpine, latest)\n\n\t\talpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913)\n\n\t\talpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913)\n\t*/\n\tcases := []struct {\n\t\tImage            string\n\t\tExpectedRegistry string\n\t\tExpectedTag      string\n\t\tExpectError      bool\n\t}{\n\t\t{\n\t\t\tImage:            \"alpine\",\n\t\t\tExpectedRegistry: \"alpine\",\n\t\t\tExpectedTag:      \"latest\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"alpine:latest\",\n\t\t\tExpectedRegistry: \"alpine\",\n\t\t\tExpectedTag:      \"latest\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"alpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectedRegistry: \"alpine\",\n\t\t\tExpectedTag:      \"latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"alpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectedRegistry: \"alpine\",\n\t\t\tExpectedTag:      \"latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"registry.k8s.io/coredns:1.1.3\",\n\t\t\tExpectedRegistry: \"registry.k8s.io/coredns\",\n\t\t\tExpectedTag:      \"1.1.3\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"registry.k8s.io/coredns:1.1.3@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectedRegistry: \"registry.k8s.io/coredns\",\n\t\t\tExpectedTag:      \"1.1.3@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"registry.k8s.io/coredns:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectedRegistry: \"registry.k8s.io/coredns\",\n\t\t\tExpectedTag:      \"latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \"registry.k8s.io/coredns@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectedRegistry: \"registry.k8s.io/coredns\",\n\t\t\tExpectedTag:      \"latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913\",\n\t\t\tExpectError:      false,\n\t\t},\n\t\t{\n\t\t\tImage:            \":\",\n\t\t\tExpectedRegistry: \"\",\n\t\t\tExpectedTag:      \"\",\n\t\t\tExpectError:      true,\n\t\t},\n\t\t{\n\t\t\tImage:            \"@\",\n\t\t\tExpectedRegistry: \"\",\n\t\t\tExpectedTag:      \"\",\n\t\t\tExpectError:      true,\n\t\t},\n\t\t{\n\t\t\tImage:            \"a@\",\n\t\t\tExpectedRegistry: \"\",\n\t\t\tExpectedTag:      \"\",\n\t\t\tExpectError:      true,\n\t\t},\n\t\t{\n\t\t\tImage:            \"a:\",\n\t\t\tExpectedRegistry: \"\",\n\t\t\tExpectedTag:      \"\",\n\t\t\tExpectError:      true,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc // capture tc\n\t\tt.Run(tc.Image, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tregistry, tag, err := SplitImage(tc.Image)\n\t\t\tif err != nil && !tc.ExpectError {\n\t\t\t\tt.Fatalf(\"Unexpected error: %q\", err)\n\t\t\t} else if err == nil && tc.ExpectError {\n\t\t\t\tt.Fatalf(\"Expected error but got nil\")\n\t\t\t}\n\t\t\tif registry != tc.ExpectedRegistry {\n\t\t\t\tt.Fatalf(\"ExpectedRegistry %q != %q\", tc.ExpectedRegistry, registry)\n\t\t\t}\n\t\t\tif tag != tc.ExpectedTag {\n\t\t\t\tt.Fatalf(\"ExpectedTag %q != %q\", tc.ExpectedTag, tag)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/pull.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// Pull pulls an image, retrying up to retries times\nfunc Pull(logger log.Logger, image string, platform string, retries int) error {\n\tlogger.V(1).Infof(\"Pulling image: %s for platform %s ...\", image, platform)\n\terr := exec.Command(\"docker\", \"pull\", \"--platform=\"+platform, image).Run()\n\t// retry pulling up to retries times if necessary\n\tif err != nil {\n\t\tfor i := 0; i < retries; i++ {\n\t\t\ttime.Sleep(time.Second * time.Duration(i+1))\n\t\t\tlogger.V(1).Infof(\"Trying again to pull image: %q ... %v\", image, err)\n\t\t\t// TODO(bentheelder): add some backoff / sleep?\n\t\t\terr = exec.Command(\"docker\", \"pull\", \"--platform=\"+platform, image).Run()\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/run.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// Run creates a container with \"docker run\", with some error handling\nfunc Run(image string, runArgs []string, containerArgs []string) error {\n\t// construct the actual docker run argv\n\targs := []string{\"run\"}\n\targs = append(args, runArgs...)\n\targs = append(args, image)\n\targs = append(args, containerArgs...)\n\tcmd := exec.Command(\"docker\", args...)\n\treturn cmd.Run()\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/container/docker/save.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// Save saves image to dest, as in `docker save`\nfunc Save(image, dest string) error {\n\treturn exec.Command(\"docker\", \"save\", \"-o\", dest, image).Run()\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/bits.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kube\n\n// Bits provides the locations of Kubernetes Binaries / Images\n// needed on the cluster nodes\n// Implementations should be registered with RegisterNamedBits\ntype Bits interface {\n\t// BinaryPaths returns a list of paths to binaries on the host machine that\n\t// should be added to PATH in the Node image\n\tBinaryPaths() []string\n\t// ImagePaths returns a list of paths to image archives to be loaded into\n\t// the Node\n\tImagePaths() []string\n\t// Version\n\tVersion() string\n}\n\n// shared real bits implementation for now\n\ntype bits struct {\n\t// computed at build time\n\tbinaryPaths []string\n\timagePaths  []string\n\tversion     string\n}\n\nvar _ Bits = &bits{}\n\nfunc (b *bits) BinaryPaths() []string {\n\treturn b.binaryPaths\n}\n\nfunc (b *bits) ImagePaths() []string {\n\treturn b.imagePaths\n}\n\nfunc (b *bits) Version() string {\n\treturn b.version\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/builder.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kube\n\n// Builder represents and implementation of building Kubernetes\n// building may constitute downloading a release\ntype Builder interface {\n\t// Build returns a Bits and any errors encountered while building Kubernetes.\n\t// Some implementations (upstream binaries) may use this step to obtain\n\t// an existing build instead\n\tBuild() (Bits, error)\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/builder_docker.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kube\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\n// TODO(bentheelder): plumb through arch\n\n// dockerBuilder implements Bits for a local docker-ized make / bash build\ntype dockerBuilder struct {\n\tkubeRoot string\n\tarch     string\n\tlogger   log.Logger\n}\n\nvar _ Builder = &dockerBuilder{}\n\n// NewDockerBuilder returns a new Bits backed by the docker-ized build,\n// given kubeRoot, the path to the kubernetes source directory\nfunc NewDockerBuilder(logger log.Logger, kubeRoot, arch string) (Builder, error) {\n\treturn &dockerBuilder{\n\t\tkubeRoot: kubeRoot,\n\t\tarch:     arch,\n\t\tlogger:   logger,\n\t}, nil\n}\n\n// Build implements Bits.Build\nfunc (b *dockerBuilder) Build() (Bits, error) {\n\t// capture version info\n\tsourceVersionRaw, err := sourceVersion(b.kubeRoot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkubeVersion, err := version.ParseSemantic(sourceVersionRaw)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to parse source version\")\n\t}\n\n\tmakeVars := []string{\n\t\t// ensure the build isn't especially noisy..\n\t\t\"KUBE_VERBOSE=0\",\n\t\t// we don't want to build these images as we don't use them ...\n\t\t\"KUBE_BUILD_HYPERKUBE=n\",\n\t\t\"KUBE_BUILD_CONFORMANCE=n\",\n\t\t// build for the host platform\n\t\t\"KUBE_BUILD_PLATFORMS=\" + dockerBuildOsAndArch(b.arch),\n\t}\n\n\t// we will pass through the environment variables, prepending defaults\n\t// NOTE: if env are specified multiple times the last one wins\n\t// NOTE: currently there are no defaults so this is essentially a deep copy\n\tenv := append([]string{}, os.Environ()...)\n\t// binaries we want to build\n\twhat := []string{\n\t\t// binaries we use directly\n\t\t\"cmd/kubeadm\",\n\t\t\"cmd/kubectl\",\n\t\t\"cmd/kubelet\",\n\t}\n\n\t// build images + binaries (binaries only on 1.21+)\n\tcmd := exec.Command(\"make\",\n\t\tappend(\n\t\t\t[]string{\n\t\t\t\t\"-C\",\n\t\t\t\tb.kubeRoot,\n\t\t\t\t\"quick-release-images\",\n\t\t\t\t\"KUBE_EXTRA_WHAT=\" + strings.Join(what, \" \"),\n\t\t\t},\n\t\t\tmakeVars...,\n\t\t)...,\n\t).SetEnv(env...)\n\texec.InheritOutput(cmd)\n\tif err := cmd.Run(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to build images\")\n\t}\n\n\t// KUBE_EXTRA_WHAT added in this commit\n\t// https://github.com/kubernetes/kubernetes/commit/35061acc28a666569fdd4d1c8a7693e3c01e14be\n\tif kubeVersion.LessThan(version.MustParseSemantic(\"v1.21.0-beta.1.153+35061acc28a666\")) {\n\t\t// on older versions we still need to build binaries separately\n\t\tcmd = exec.Command(\n\t\t\t\"build/run.sh\",\n\t\t\tappend(\n\t\t\t\t[]string{\n\t\t\t\t\t\"make\",\n\t\t\t\t\t\"all\",\n\t\t\t\t\t\"WHAT=\" + strings.Join(what, \" \"),\n\t\t\t\t},\n\t\t\t\tmakeVars...,\n\t\t\t)...,\n\t\t).SetEnv(env...)\n\t\texec.InheritOutput(cmd)\n\t\tif err := cmd.Run(); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"failed to build binaries\")\n\t\t}\n\t}\n\n\tbinDir := filepath.Join(b.kubeRoot,\n\t\t\"_output\", \"dockerized\", \"bin\", \"linux\", b.arch,\n\t)\n\timageDir := filepath.Join(b.kubeRoot,\n\t\t\"_output\", \"release-images\", b.arch,\n\t)\n\n\treturn &bits{\n\t\tbinaryPaths: []string{\n\t\t\tfilepath.Join(binDir, \"kubeadm\"),\n\t\t\tfilepath.Join(binDir, \"kubelet\"),\n\t\t\tfilepath.Join(binDir, \"kubectl\"),\n\t\t},\n\t\timagePaths: []string{\n\t\t\tfilepath.Join(imageDir, \"kube-apiserver.tar\"),\n\t\t\tfilepath.Join(imageDir, \"kube-controller-manager.tar\"),\n\t\t\tfilepath.Join(imageDir, \"kube-scheduler.tar\"),\n\t\t\tfilepath.Join(imageDir, \"kube-proxy.tar\"),\n\t\t},\n\t\tversion: sourceVersionRaw,\n\t}, nil\n}\n\nfunc dockerBuildOsAndArch(arch string) string {\n\treturn \"linux/\" + arch\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/builder_remote.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kube\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\ntype remoteBuilder struct {\n\tversion string\n\tlogger  log.Logger\n\turl     string\n}\n\nvar _ Builder = &remoteBuilder{}\n\n// NewURLBuilder used to specify a complete url to a gzipped tarball\nfunc NewURLBuilder(logger log.Logger, url string) (Builder, error) {\n\treturn &remoteBuilder{\n\t\tversion: \"\",\n\t\tlogger:  logger,\n\t\turl:     url,\n\t}, nil\n}\n\n// NewReleaseBuilder used to specify a release semver and constructs a url to release artifacts\nfunc NewReleaseBuilder(logger log.Logger, version, arch string) (Builder, error) {\n\turl := \"https://dl.k8s.io/\" + version + \"/kubernetes-server-linux-\" + arch + \".tar.gz\"\n\treturn &remoteBuilder{\n\t\tversion: version,\n\t\tlogger:  logger,\n\t\turl:     url,\n\t}, nil\n}\n\n// Build implements Bits.Build\nfunc (b *remoteBuilder) Build() (Bits, error) {\n\n\ttmpDir, err := os.MkdirTemp(os.TempDir(), \"k8s-tar-extract-\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating temporary directory for tar extraction: %w\", err)\n\t}\n\n\ttgzFile := filepath.Join(tmpDir, \"kubernetes-\"+b.version+\"-server-linux-amd64.tar.gz\")\n\terr = b.downloadURL(b.url, tgzFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error downloading file: %w\", err)\n\t}\n\n\terr = extractTarball(tgzFile, tmpDir, b.logger)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error extracting tgz file: %w\", err)\n\t}\n\n\tbinDir := filepath.Join(tmpDir, \"kubernetes/server/bin\")\n\tcontents, err := os.ReadFile(filepath.Join(tmpDir, \"kubernetes/version\"))\n\t// fallback for Kubernetes < v1.31 which doesn't have the version file\n\t// this approach only works for release tags as the format happens to match\n\t// for pre-release builds the docker tag is mangled and not valid semver\n\tif err != nil && os.IsNotExist(err) {\n\t\tb.logger.Warn(\"WARNING: Using fallback version detection due to missing version file (This command works best with Kubernetes v1.31+)\")\n\t\tcontents, err = os.ReadFile(filepath.Join(binDir, \"kube-apiserver.docker_tag\"))\n\t}\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get version\")\n\t}\n\tsourceVersionRaw := strings.TrimSpace(string(contents))\n\treturn &bits{\n\t\tbinaryPaths: []string{\n\t\t\tfilepath.Join(binDir, \"kubeadm\"),\n\t\t\tfilepath.Join(binDir, \"kubelet\"),\n\t\t\tfilepath.Join(binDir, \"kubectl\"),\n\t\t},\n\t\timagePaths: []string{\n\t\t\tfilepath.Join(binDir, \"kube-apiserver.tar\"),\n\t\t\tfilepath.Join(binDir, \"kube-controller-manager.tar\"),\n\t\t\tfilepath.Join(binDir, \"kube-scheduler.tar\"),\n\t\t\tfilepath.Join(binDir, \"kube-proxy.tar\"),\n\t\t},\n\t\tversion: sourceVersionRaw,\n\t}, nil\n}\n\nfunc (b *remoteBuilder) downloadURL(url string, destPath string) error {\n\toutput, err := os.Create(destPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating file for download %q: %v\", destPath, err)\n\t}\n\tdefer output.Close()\n\n\tb.logger.V(0).Infof(\"Downloading %q\", url)\n\n\t// Create a client with custom timeouts\n\t// to avoid idle downloads to hang the program\n\thttpClient := &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy: http.ProxyFromEnvironment,\n\t\t\tDialContext: (&net.Dialer{\n\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\tKeepAlive: 30 * time.Second,\n\t\t\t}).DialContext,\n\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\tResponseHeaderTimeout: 30 * time.Second,\n\t\t\tIdleConnTimeout:       30 * time.Second,\n\t\t},\n\t}\n\n\t// this will stop slow downloads after 10 minutes\n\t// and interrupt reading of the Response.Body\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot create request: %v\", err)\n\t}\n\n\tresponse, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error doing HTTP fetch of %q: %v\", url, err)\n\t}\n\tdefer response.Body.Close()\n\n\tif response.StatusCode >= 400 {\n\t\treturn fmt.Errorf(\"error response from %q: HTTP %v\", url, response.StatusCode)\n\t}\n\n\tstart := time.Now()\n\tdefer func() {\n\t\tb.logger.V(2).Infof(\"Copying %q to %q took %q\", url, destPath, time.Since(start))\n\t}()\n\n\t// TODO: we should add some sort of progress indicator\n\t_, err = io.Copy(output, response.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error downloading HTTP content from %q: %v\", url, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/builder_tarball.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kube\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// TODO(bentheelder): plumb through arch\n\n// directoryBuilder implements Bits for a local docker-ized make / bash build\ntype directoryBuilder struct {\n\ttarballPath string\n\tlogger      log.Logger\n}\n\nvar _ Builder = &directoryBuilder{}\n\n// NewTarballBuilder returns a new Bits backed by the docker-ized build,\n// given kubeRoot, the path to the kubernetes source directory\nfunc NewTarballBuilder(logger log.Logger, tarballPath string) (Builder, error) {\n\treturn &directoryBuilder{\n\t\ttarballPath: tarballPath,\n\t\tlogger:      logger,\n\t}, nil\n}\n\n// Build implements Bits.Build\nfunc (b *directoryBuilder) Build() (Bits, error) {\n\ttmpDir, err := os.MkdirTemp(os.TempDir(), \"k8s-tar-extract-\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating temporary directory for tar extraction: %w\", err)\n\t}\n\n\tb.logger.V(0).Infof(\"Extracting %q\", b.tarballPath)\n\terr = extractTarball(b.tarballPath, tmpDir, b.logger)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error extracting tar file: %w\", err)\n\t}\n\n\tbinDir := filepath.Join(tmpDir, \"kubernetes/server/bin\")\n\tcontents, err := os.ReadFile(filepath.Join(tmpDir, \"kubernetes/version\"))\n\t// fallback for Kubernetes < v1.31 which doesn't have the version file\n\t// this approach only works for release tags as the format happens to match\n\t// for pre-release builds the docker tag is mangled and not valid semver\n\tif err != nil && os.IsNotExist(err) {\n\t\tb.logger.Warn(\"WARNING: Using fallback version detection due to missing version file (This command works best with Kubernetes v1.31+)\")\n\t\tcontents, err = os.ReadFile(filepath.Join(binDir, \"kube-apiserver.docker_tag\"))\n\t}\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get version\")\n\t}\n\tsourceVersionRaw := strings.TrimSpace(string(contents))\n\treturn &bits{\n\t\tbinaryPaths: []string{\n\t\t\tfilepath.Join(binDir, \"kubeadm\"),\n\t\t\tfilepath.Join(binDir, \"kubelet\"),\n\t\t\tfilepath.Join(binDir, \"kubectl\"),\n\t\t},\n\t\timagePaths: []string{\n\t\t\tfilepath.Join(binDir, \"kube-apiserver.tar\"),\n\t\t\tfilepath.Join(binDir, \"kube-controller-manager.tar\"),\n\t\t\tfilepath.Join(binDir, \"kube-scheduler.tar\"),\n\t\t\tfilepath.Join(binDir, \"kube-proxy.tar\"),\n\t\t},\n\t\tversion: sourceVersionRaw,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kube implements functionality to build Kubernetes for the purposes\n// of installing into the kind node image\npackage kube\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/source.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kube\n\nimport (\n\t\"fmt\"\n\t\"go/build\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"al.essio.dev/pkg/shellescape\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// FindSource attempts to locate a kubernetes checkout using go's build package\nfunc FindSource() (root string, err error) {\n\t// check current working directory first\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to locate kubernetes source, could not current working directory: %w\", err)\n\t}\n\tif probablyKubeDir(wd) {\n\t\treturn wd, nil\n\t}\n\t// then look under GOPATH\n\tgopath := build.Default.GOPATH\n\tif gopath == \"\" {\n\t\treturn \"\", errors.New(\"could not find Kubernetes source under current working directory and GOPATH is not set\")\n\t}\n\t// try k8s.io/kubernetes first (old canonical GOPATH locaation)\n\tif dir := filepath.Join(gopath, \"src\", \"k8s.io\", \"kubernetes\"); probablyKubeDir(dir) {\n\t\treturn dir, nil\n\t}\n\t// then try github.com/kubernetes/kubernetes (CI without path_alias set)\n\tif dir := filepath.Join(gopath, \"src\", \"github.com\", \"kubernetes\", \"kubernetes\"); probablyKubeDir(dir) {\n\t\treturn dir, nil\n\t}\n\treturn \"\", fmt.Errorf(\"could not find Kubernetes source under current working directory or GOPATH=%s\", build.Default.GOPATH)\n}\n\n// probablyKubeDir returns true if the dir looks plausibly like a kubernetes\n// source directory\nfunc probablyKubeDir(dir string) bool {\n\t// TODO: should we do more checks?\n\t// NOTE: go.mod with this line has existed since Kubernetes 1.15\n\tconst sentinelLine = \"module k8s.io/kubernetes\"\n\tcontents, err := os.ReadFile(filepath.Join(dir, \"go.mod\"))\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn strings.Contains(string(contents), sentinelLine)\n}\n\n// sourceVersion the kubernetes git version based on hack/print-workspace-status.sh\n// the raw version is also returned\nfunc sourceVersion(kubeRoot string) (string, error) {\n\t// get the version output\n\tcmd := exec.Command(\n\t\t\"sh\", \"-c\",\n\t\tfmt.Sprintf(\n\t\t\t\"cd %s && hack/print-workspace-status.sh\",\n\t\t\tshellescape.Quote(kubeRoot),\n\t\t),\n\t)\n\toutput, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// parse it, and populate it into _output/git_version\n\tversion := \"\"\n\tfor _, line := range output {\n\t\tparts := strings.SplitN(line, \" \", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn \"\", errors.Errorf(\"could not parse kubernetes version: %q\", strings.Join(output, \"\\n\"))\n\t\t}\n\t\tif parts[0] == \"gitVersion\" {\n\t\t\tversion = parts[1]\n\t\t\treturn version, nil\n\t\t}\n\t}\n\tif version == \"\" {\n\t\treturn \"\", errors.Errorf(\"could not obtain kubernetes version: %q\", strings.Join(output, \"\\n\"))\n\n\t}\n\treturn \"\", errors.Errorf(\"could not find kubernetes version in output: %q\", strings.Join(output, \"\\n\"))\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/internal/kube/tar.go",
    "content": "package kube\n\nimport (\n\t\"archive/tar\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// extractTarball takes a gzipped-tarball and extracts the contents into a specified directory\nfunc extractTarball(tarPath, destDirectory string, logger log.Logger) (err error) {\n\t// Open the tar file\n\tf, err := os.Open(tarPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"opening tarball: %w\", err)\n\t}\n\tdefer f.Close()\n\n\tgzipReader, err := gzip.NewReader(f)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttr := tar.NewReader(gzipReader)\n\n\tnumFiles := 0\n\tfor {\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 fmt.Errorf(\"reading tarfile %s: %w\", tarPath, err)\n\t\t}\n\n\t\tif hdr.FileInfo().IsDir() {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := os.MkdirAll(\n\t\t\tfilepath.Join(destDirectory, filepath.Dir(hdr.Name)), os.FileMode(0o755),\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"creating image directory structure: %w\", err)\n\t\t}\n\n\t\tf, err := os.Create(filepath.Join(destDirectory, hdr.Name))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"creating image layer file: %w\", err)\n\t\t}\n\n\t\tif _, err := io.CopyN(f, tr, hdr.Size); err != nil {\n\t\t\tf.Close()\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\treturn fmt.Errorf(\"extracting image data: %w\", err)\n\t\t}\n\t\tf.Close()\n\n\t\tnumFiles++\n\t}\n\n\tlogger.V(2).Infof(\"Successfully extracted %d files from image tarball %s\", numFiles, tarPath)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/build/nodeimage/options.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeimage\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// Option is a configuration option supplied to Build\ntype Option interface {\n\tapply(*buildContext) error\n}\n\ntype optionAdapter func(*buildContext) error\n\nfunc (c optionAdapter) apply(o *buildContext) error {\n\treturn c(o)\n}\n\n// WithImage configures a build to tag the built image with `image`\nfunc WithImage(image string) Option {\n\treturn optionAdapter(func(b *buildContext) error {\n\t\tb.image = image\n\t\treturn nil\n\t})\n}\n\n// WithBaseImage configures a build to use `image` as the base image\nfunc WithBaseImage(image string) Option {\n\treturn optionAdapter(func(b *buildContext) error {\n\t\tb.baseImage = image\n\t\treturn nil\n\t})\n}\n\n// WithKubeParam sets the path to the Kubernetes source directory (if empty, the path will be autodetected)\nfunc WithKubeParam(root string) Option {\n\treturn optionAdapter(func(b *buildContext) error {\n\t\tb.kubeParam = root\n\t\treturn nil\n\t})\n}\n\n// WithLogger sets the logger\nfunc WithLogger(logger log.Logger) Option {\n\treturn optionAdapter(func(b *buildContext) error {\n\t\tb.logger = logger\n\t\treturn nil\n\t})\n}\n\n// WithArch sets the architecture to build for\nfunc WithArch(arch string) Option {\n\treturn optionAdapter(func(b *buildContext) error {\n\t\tif arch != \"\" {\n\t\t\tb.arch = arch\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// WithBuildType sets the build type to perform\nfunc WithBuildType(buildType string) Option {\n\treturn optionAdapter(func(b *buildContext) error {\n\t\tif buildType != \"\" {\n\t\t\tb.buildType = buildType\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "pkg/cluster/constants/constants.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package constants contains well known constants for kind clusters\npackage constants\n\n// DefaultClusterName is the default cluster Context name\nconst DefaultClusterName = \"kind\"\n\n/* node role value constants */\nconst (\n\t// ControlPlaneNodeRoleValue identifies a node that hosts a Kubernetes\n\t// control-plane.\n\t//\n\t// NOTE: in single node clusters, control-plane nodes act as worker nodes\n\tControlPlaneNodeRoleValue string = \"control-plane\"\n\n\t// WorkerNodeRoleValue identifies a node that hosts a Kubernetes worker\n\tWorkerNodeRoleValue string = \"worker\"\n\n\t// ExternalLoadBalancerNodeRoleValue identifies a node that hosts an\n\t// external load balancer for the API server in HA configurations.\n\t//\n\t// Please note that `kind` nodes hosting external load balancer are not\n\t// kubernetes nodes\n\tExternalLoadBalancerNodeRoleValue string = \"external-load-balancer\"\n\n\t// ExternalEtcdNodeRoleValue identifies a node that hosts an external-etcd\n\t// instance.\n\t//\n\t// WARNING: this node type is not yet implemented!\n\t//\n\t// Please note that `kind` nodes hosting external etcd are not\n\t// kubernetes nodes\n\tExternalEtcdNodeRoleValue string = \"external-etcd\"\n)\n"
  },
  {
    "path": "pkg/cluster/createoption.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/apis/config/v1alpha4\"\n\tinternalcreate \"sigs.k8s.io/kind/pkg/cluster/internal/create\"\n\tinternalencoding \"sigs.k8s.io/kind/pkg/internal/apis/config/encoding\"\n)\n\n// CreateOption is a Provider.Create option\ntype CreateOption interface {\n\tapply(*internalcreate.ClusterOptions) error\n}\n\ntype createOptionAdapter func(*internalcreate.ClusterOptions) error\n\nfunc (c createOptionAdapter) apply(o *internalcreate.ClusterOptions) error {\n\treturn c(o)\n}\n\n// CreateWithConfigFile configures the config file path to use\nfunc CreateWithConfigFile(path string) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\tvar err error\n\t\to.Config, err = internalencoding.Load(path)\n\t\treturn err\n\t})\n}\n\n// CreateWithRawConfig configures the config to use from raw (yaml) bytes\nfunc CreateWithRawConfig(raw []byte) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\tvar err error\n\t\to.Config, err = internalencoding.Parse(raw)\n\t\treturn err\n\t})\n}\n\n// CreateWithV1Alpha4Config configures the cluster with a v1alpha4 config\nfunc CreateWithV1Alpha4Config(config *v1alpha4.Cluster) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.Config = internalencoding.V1Alpha4ToInternal(config)\n\t\treturn nil\n\t})\n}\n\n// CreateWithNodeImage overrides the image on all nodes in config\n// as an easy way to change the Kubernetes version\nfunc CreateWithNodeImage(nodeImage string) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.NodeImage = nodeImage\n\t\treturn nil\n\t})\n}\n\n// CreateWithRetain disables deletion of nodes and any other cleanup\n// that would normally occur after a failure to create\n// This is mainly used for debugging purposes\nfunc CreateWithRetain(retain bool) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.Retain = retain\n\t\treturn nil\n\t})\n}\n\n// CreateWithWaitForReady configures a maximum wait time for the control plane\n// node(s) to be ready. By default no waiting is performed\nfunc CreateWithWaitForReady(waitTime time.Duration) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.WaitForReady = waitTime\n\t\treturn nil\n\t})\n}\n\n// CreateWithKubeconfigPath sets the explicit --kubeconfig path\nfunc CreateWithKubeconfigPath(explicitPath string) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.KubeconfigPath = explicitPath\n\t\treturn nil\n\t})\n}\n\n// CreateWithStopBeforeSettingUpKubernetes enables skipping setting up\n// kubernetes (kubeadm init etc.) after creating node containers\n// This generally shouldn't be used and is only lightly supported, but allows\n// provisioning node containers for experimentation\nfunc CreateWithStopBeforeSettingUpKubernetes(stopBeforeSettingUpKubernetes bool) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.StopBeforeSettingUpKubernetes = stopBeforeSettingUpKubernetes\n\t\treturn nil\n\t})\n}\n\n// CreateWithDisplayUsage enables displaying usage if displayUsage is true\nfunc CreateWithDisplayUsage(displayUsage bool) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.DisplayUsage = displayUsage\n\t\treturn nil\n\t})\n}\n\n// CreateWithDisplaySalutation enables display a salutation at the end of create\n// cluster if displaySalutation is true\nfunc CreateWithDisplaySalutation(displaySalutation bool) CreateOption {\n\treturn createOptionAdapter(func(o *internalcreate.ClusterOptions) error {\n\t\to.DisplaySalutation = displaySalutation\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "pkg/cluster/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cluster implements kind kubernetes-in-docker cluster management\npackage cluster\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/action.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package actions contains logic for steps to perform once an initial node\n// container has been created.\npackage actions\n\nimport (\n\t\"sync\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n)\n\n// Action defines a step of bringing up a kind cluster after initial node\n// container creation\ntype Action interface {\n\tExecute(ctx *ActionContext) error\n}\n\n// ActionContext is data supplied to all actions\ntype ActionContext struct {\n\tLogger   log.Logger\n\tStatus   *cli.Status\n\tConfig   *config.Cluster\n\tProvider providers.Provider\n\tcache    *cachedData\n}\n\n// NewActionContext returns a new ActionContext\nfunc NewActionContext(\n\tlogger log.Logger,\n\tstatus *cli.Status,\n\tprovider providers.Provider,\n\tcfg *config.Cluster,\n) *ActionContext {\n\treturn &ActionContext{\n\t\tLogger:   logger,\n\t\tStatus:   status,\n\t\tProvider: provider,\n\t\tConfig:   cfg,\n\t\tcache:    &cachedData{},\n\t}\n}\n\ntype cachedData struct {\n\tmu    sync.RWMutex\n\tnodes []nodes.Node\n}\n\nfunc (cd *cachedData) getNodes() []nodes.Node {\n\tcd.mu.RLock()\n\tdefer cd.mu.RUnlock()\n\treturn cd.nodes\n}\n\nfunc (cd *cachedData) setNodes(n []nodes.Node) {\n\tcd.mu.Lock()\n\tdefer cd.mu.Unlock()\n\tcd.nodes = n\n}\n\n// Nodes returns the list of cluster nodes, this is a cached call\nfunc (ac *ActionContext) Nodes() ([]nodes.Node, error) {\n\tcachedNodes := ac.cache.getNodes()\n\tif cachedNodes != nil {\n\t\treturn cachedNodes, nil\n\t}\n\tn, err := ac.Provider.ListNodes(ac.Config.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tac.cache.setNodes(n)\n\treturn n, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/config/config.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package config implements the kubeadm config action\npackage config\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/kubeadm\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/patch\"\n)\n\n// Action implements action for creating the node config files\ntype Action struct{}\n\n// NewAction returns a new action for creating the config files\nfunc NewAction() actions.Action {\n\treturn &Action{}\n}\n\n// Execute runs the action\nfunc (a *Action) Execute(ctx *actions.ActionContext) error {\n\tctx.Status.Start(\"Writing configuration 📜\")\n\tdefer ctx.Status.End(false)\n\n\tproviderInfo, err := ctx.Provider.Info()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcontrolPlaneEndpoint, err := ctx.Provider.GetAPIServerInternalEndpoint(ctx.Config.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// create kubeadm init config\n\tfns := []func() error{}\n\n\tprovider := fmt.Sprintf(\"%s\", ctx.Provider)\n\tconfigData := kubeadm.ConfigData{\n\t\tNodeProvider:         provider,\n\t\tClusterName:          ctx.Config.Name,\n\t\tControlPlaneEndpoint: controlPlaneEndpoint,\n\t\tAPIBindPort:          common.APIServerInternalPort,\n\t\tAPIServerAddress:     ctx.Config.Networking.APIServerAddress,\n\t\tToken:                kubeadm.Token,\n\t\tPodSubnet:            ctx.Config.Networking.PodSubnet,\n\t\tKubeProxyMode:        string(ctx.Config.Networking.KubeProxyMode),\n\t\tServiceSubnet:        ctx.Config.Networking.ServiceSubnet,\n\t\tControlPlane:         true,\n\t\tIPFamily:             ctx.Config.Networking.IPFamily,\n\t\tFeatureGates:         ctx.Config.FeatureGates,\n\t\tRuntimeConfig:        ctx.Config.RuntimeConfig,\n\t\tRootlessProvider:     providerInfo.Rootless,\n\t}\n\n\tkubeadmConfigPlusPatches := func(node nodes.Node, data kubeadm.ConfigData) func() error {\n\t\treturn func() error {\n\t\t\tdata.NodeName = node.String()\n\t\t\tkubeadmConfig, err := getKubeadmConfig(ctx.Config, data, node, provider)\n\t\t\tif err != nil {\n\t\t\t\t// TODO(bentheelder): logging here\n\t\t\t\treturn errors.Wrap(err, \"failed to generate kubeadm config content\")\n\t\t\t}\n\n\t\t\tctx.Logger.V(2).Infof(\"Using the following kubeadm config for node %s:\\n%s\", node.String(), kubeadmConfig)\n\t\t\treturn writeKubeadmConfig(kubeadmConfig, node)\n\t\t}\n\t}\n\n\t// create the kubeadm join configuration for the kubernetes cluster nodes only\n\tkubeNodes, err := nodeutils.InternalNodes(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, node := range kubeNodes {\n\t\tnode := node             // capture loop variable\n\t\tconfigData := configData // copy config data\n\t\tfns = append(fns, kubeadmConfigPlusPatches(node, configData))\n\t}\n\n\t// Create the kubeadm config in all nodes concurrently\n\tif err := errors.UntilErrorConcurrent(fns); err != nil {\n\t\treturn err\n\t}\n\n\t// if we have containerd config, patch all the nodes concurrently\n\tif len(ctx.Config.ContainerdConfigPatches) > 0 || len(ctx.Config.ContainerdConfigPatchesJSON6902) > 0 {\n\t\tfns := make([]func() error, len(kubeNodes))\n\t\tfor i, node := range kubeNodes {\n\t\t\tnode := node // capture loop variable\n\t\t\tfns[i] = func() error {\n\t\t\t\t// read and patch the config\n\t\t\t\tconst containerdConfigPath = \"/etc/containerd/config.toml\"\n\t\t\t\tvar buff bytes.Buffer\n\t\t\t\tif err := node.Command(\"cat\", containerdConfigPath).SetStdout(&buff).Run(); err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"failed to read containerd config from node\")\n\t\t\t\t}\n\t\t\t\tpatched, err := patch.ContainerdTOML(buff.String(), ctx.Config.ContainerdConfigPatches, ctx.Config.ContainerdConfigPatchesJSON6902)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"failed to patch containerd config\")\n\t\t\t\t}\n\t\t\t\tif err := nodeutils.WriteFile(node, containerdConfigPath, patched); err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"failed to write patched containerd config\")\n\t\t\t\t}\n\t\t\t\t// restart containerd now that we've re-configured it\n\t\t\t\t// skip if containerd is not running\n\t\t\t\tif err := node.Command(\"bash\", \"-c\", `! pgrep --exact containerd || systemctl restart containerd`).Run(); err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"failed to restart containerd after patching config\")\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tif err := errors.UntilErrorConcurrent(fns); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// mark success\n\tctx.Status.End(true)\n\treturn nil\n}\n\n// getKubeadmConfig generates the kubeadm config contents for the cluster\n// by running data through the template and applying patches as needed.\nfunc getKubeadmConfig(cfg *config.Cluster, data kubeadm.ConfigData, node nodes.Node, provider string) (path string, err error) {\n\tkubeVersion, err := nodeutils.KubeVersion(node)\n\tif err != nil {\n\t\t// TODO(bentheelder): logging here\n\t\treturn \"\", errors.Wrap(err, \"failed to get kubernetes version from node\")\n\t}\n\tdata.KubernetesVersion = kubeVersion\n\n\t// TODO: gross hack!\n\t// identify node in config by matching name (since these are named in order)\n\t// we should really just streamline the bootstrap code and maintain\n\t// this mapping ... something for the next major refactor\n\tvar configNode *config.Node\n\tnamer := common.MakeNodeNamer(\"\")\n\tfor i := range cfg.Nodes {\n\t\tn := &cfg.Nodes[i]\n\t\tnodeSuffix := namer(string(n.Role))\n\t\tif strings.HasSuffix(node.String(), nodeSuffix) {\n\t\t\tconfigNode = n\n\t\t}\n\t}\n\tif configNode == nil {\n\t\treturn \"\", errors.Errorf(\"failed to match node %q to config\", node.String())\n\t}\n\n\t// get the node ip address\n\tnodeAddress, nodeAddressIPv6, err := node.IP()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get IP for node\")\n\t}\n\n\tdata.NodeAddress = nodeAddress\n\t// configure the right protocol addresses\n\tif cfg.Networking.IPFamily == config.IPv6Family || cfg.Networking.IPFamily == config.DualStackFamily {\n\t\tif ip := net.ParseIP(nodeAddressIPv6); ip.To16() == nil {\n\t\t\treturn \"\", errors.Errorf(\"failed to get IPv6 address for node %s; is %s configured to use IPv6 correctly?\", node.String(), provider)\n\t\t}\n\t\tdata.NodeAddress = nodeAddressIPv6\n\t\tif cfg.Networking.IPFamily == config.DualStackFamily {\n\t\t\t// order matters since the nodeAddress will be used later to configure the apiserver advertise address\n\t\t\t// Ref: #2484\n\t\t\tprimaryServiceSubnet := strings.Split(cfg.Networking.ServiceSubnet, \",\")[0]\n\t\t\tip, _, err := net.ParseCIDR(primaryServiceSubnet)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"failed to parse primary Service Subnet %s (%s): %w\", primaryServiceSubnet, cfg.Networking.ServiceSubnet, err)\n\t\t\t}\n\t\t\tif ip.To4() != nil {\n\t\t\t\tdata.NodeAddress = fmt.Sprintf(\"%s,%s\", nodeAddress, nodeAddressIPv6)\n\t\t\t} else {\n\t\t\t\tdata.NodeAddress = fmt.Sprintf(\"%s,%s\", nodeAddressIPv6, nodeAddress)\n\t\t\t}\n\t\t}\n\t}\n\n\t// configure the node labels\n\tif len(configNode.Labels) > 0 {\n\t\tdata.NodeLabels = hashMapLabelsToCommaSeparatedLabels(configNode.Labels)\n\t}\n\n\t// set the node role\n\tdata.ControlPlane = string(configNode.Role) == constants.ControlPlaneNodeRoleValue\n\n\t// generate the config contents\n\tcf, err := kubeadm.Config(data)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tclusterPatches, clusterJSONPatches := allPatchesFromConfig(cfg)\n\t// apply cluster-level patches first\n\tpatchedConfig, err := patch.KubeYAML(cf, clusterPatches, clusterJSONPatches)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// if needed, apply current node's patches\n\tif len(configNode.KubeadmConfigPatches) > 0 || len(configNode.KubeadmConfigPatchesJSON6902) > 0 {\n\t\tpatchedConfig, err = patch.KubeYAML(patchedConfig, configNode.KubeadmConfigPatches, configNode.KubeadmConfigPatchesJSON6902)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\t// fix all the patches to have name metadata matching the generated config\n\treturn removeMetadata(patchedConfig), nil\n}\n\n// trims out the metadata.name we put in the config for kustomize matching,\n// kubeadm will complain about this otherwise\nfunc removeMetadata(kustomized string) string {\n\treturn strings.ReplaceAll(\n\t\tkustomized,\n\t\t`metadata:\n  name: config\n`,\n\t\t\"\",\n\t)\n}\n\nfunc allPatchesFromConfig(cfg *config.Cluster) (patches []string, jsonPatches []config.PatchJSON6902) {\n\treturn cfg.KubeadmConfigPatches, cfg.KubeadmConfigPatchesJSON6902\n}\n\n// writeKubeadmConfig writes the kubeadm configuration in the specified node\nfunc writeKubeadmConfig(kubeadmConfig string, node nodes.Node) error {\n\t// copy the config to the node\n\tif err := nodeutils.WriteFile(node, \"/kind/kubeadm.conf\", kubeadmConfig); err != nil {\n\t\t// TODO(bentheelder): logging here\n\t\treturn errors.Wrap(err, \"failed to copy kubeadm config to node\")\n\t}\n\n\treturn nil\n}\n\n// hashMapLabelsToCommaSeparatedLabels converts labels in hashmap form to labels in a comma-separated string form like \"key1=value1,key2=value2\"\nfunc hashMapLabelsToCommaSeparatedLabels(labels map[string]string) string {\n\toutput := \"\"\n\tfor key, value := range labels {\n\t\toutput += fmt.Sprintf(\"%s=%s,\", key, value)\n\t}\n\treturn strings.TrimSuffix(output, \",\") // remove the last character (comma) in the output string\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/installcni/cni.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package installcni implements the install CNI action\npackage installcni\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/internal/patch\"\n)\n\ntype action struct{}\n\n// NewAction returns a new action for installing default CNI\nfunc NewAction() actions.Action {\n\treturn &action{}\n}\n\n// Execute runs the action\nfunc (a *action) Execute(ctx *actions.ActionContext) error {\n\tctx.Status.Start(\"Installing CNI 🔌\")\n\tdefer ctx.Status.End(false)\n\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// get the target node for this task\n\tcontrolPlanes, err := nodeutils.ControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnode := controlPlanes[0] // kind expects at least one always\n\n\t// read the manifest from the node\n\tvar raw bytes.Buffer\n\tif err := node.Command(\"cat\", \"/kind/manifests/default-cni.yaml\").SetStdout(&raw).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to read CNI manifest\")\n\t}\n\tmanifest := raw.String()\n\n\t// TODO: remove this check?\n\t// backwards compatibility for mounting your own manifest file to the default\n\t// location\n\t// NOTE: this is intentionally undocumented, as an internal implementation\n\t// detail. Going forward users should disable the default CNI and install\n\t// their own, or use the default. The internal templating mechanism is\n\t// not intended for external usage and is unstable.\n\tif strings.Contains(manifest, \"would you kindly template this file\") {\n\t\tt, err := template.New(\"cni-manifest\").Parse(manifest)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to parse CNI manifest template\")\n\t\t}\n\t\tvar out bytes.Buffer\n\t\terr = t.Execute(&out, &struct {\n\t\t\tPodSubnet string\n\t\t}{\n\t\t\tPodSubnet: ctx.Config.Networking.PodSubnet,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to execute CNI manifest template\")\n\t\t}\n\t\tmanifest = out.String()\n\t}\n\n\t// NOTE: this is intentionally undocumented, as an internal implementation\n\t// detail. Going forward users should disable the default CNI and install\n\t// their own, or use the default. The internal templating mechanism is\n\t// not intended for external usage and is unstable.\n\tif strings.Contains(manifest, \"would you kindly patch this file\") {\n\t\t// Add the controlplane endpoint so kindnet doesn´t have to wait for kube-proxy\n\t\tcontrolPlaneEndpoint, err := ctx.Provider.GetAPIServerInternalEndpoint(ctx.Config.Name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpatchValue := `\n- op: add\n  path: /spec/template/spec/containers/0/env/-\n  value:\n    name: CONTROL_PLANE_ENDPOINT\n    value: ` + controlPlaneEndpoint\n\n\t\tcontrolPlanePatch6902 := config.PatchJSON6902{\n\t\t\tGroup:   \"apps\",\n\t\t\tVersion: \"v1\",\n\t\t\tKind:    \"DaemonSet\",\n\t\t\tPatch:   patchValue,\n\t\t}\n\n\t\tpatchedConfig, err := patch.KubeYAML(manifest, nil, []config.PatchJSON6902{controlPlanePatch6902})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmanifest = patchedConfig\n\t}\n\n\tctx.Logger.V(5).Infof(\"Using the following Kindnetd config:\\n%s\", manifest)\n\n\t// install the manifest\n\tif err := node.Command(\n\t\t\"kubectl\", \"create\", \"--kubeconfig=/etc/kubernetes/admin.conf\",\n\t\t\"-f\", \"-\",\n\t).SetStdin(strings.NewReader(manifest)).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to apply overlay network\")\n\t}\n\n\t// mark success\n\tctx.Status.End(true)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/installstorage/storage.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package installstorage implements the an action to install a default\n// storageclass\npackage installstorage\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n)\n\ntype action struct{}\n\n// NewAction returns a new action for installing storage\nfunc NewAction() actions.Action {\n\treturn &action{}\n}\n\n// Execute runs the action\nfunc (a *action) Execute(ctx *actions.ActionContext) error {\n\tctx.Status.Start(\"Installing StorageClass 💾\")\n\tdefer ctx.Status.End(false)\n\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// get the target node for this task\n\tcontrolPlanes, err := nodeutils.ControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnode := controlPlanes[0] // kind expects at least one always\n\n\t// add the default storage class\n\tif err := addDefaultStorage(ctx.Logger, node); err != nil {\n\t\treturn errors.Wrap(err, \"failed to add default storage class\")\n\t}\n\n\t// mark success\n\tctx.Status.End(true)\n\treturn nil\n}\n\n// legacy default storage class\n// we need this for e2es (StatefulSet)\n// newer kind images ship a storage driver manifest\nconst defaultStorageManifest = `# host-path based default storage class\napiVersion: storage.k8s.io/v1\nkind: StorageClass\nmetadata:\n  namespace: kube-system\n  name: standard\n  annotations:\n    storageclass.kubernetes.io/is-default-class: \"true\"\nprovisioner: kubernetes.io/host-path`\n\nfunc addDefaultStorage(logger log.Logger, controlPlane nodes.Node) error {\n\t// start with fallback default, and then try to get the newer kind node\n\t// storage manifest if present\n\tmanifest := defaultStorageManifest\n\tvar raw bytes.Buffer\n\tif err := controlPlane.Command(\"cat\", \"/kind/manifests/default-storage.yaml\").SetStdout(&raw).Run(); err != nil {\n\t\tlogger.Warn(\"Could not read storage manifest, falling back on old k8s.io/host-path default ...\")\n\t} else {\n\t\tmanifest = raw.String()\n\t}\n\n\t// apply the manifest\n\tin := strings.NewReader(manifest)\n\tcmd := controlPlane.Command(\n\t\t\"kubectl\",\n\t\t\"--kubeconfig=/etc/kubernetes/admin.conf\", \"apply\", \"-f\", \"-\",\n\t)\n\tcmd.SetStdin(in)\n\treturn cmd.Run()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/kubeadminit/init.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeadminit implements the kubeadm init action\npackage kubeadminit\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\n// kubeadmInitAction implements action for executing the kubeadm init\n// and a set of default post init operations like e.g. install the\n// CNI network plugin.\ntype action struct {\n\tskipKubeProxy bool\n}\n\n// NewAction returns a new action for kubeadm init\nfunc NewAction(cfg *config.Cluster) actions.Action {\n\treturn &action{skipKubeProxy: cfg.Networking.KubeProxyMode == config.NoneProxyMode}\n}\n\n// Execute runs the action\nfunc (a *action) Execute(ctx *actions.ActionContext) error {\n\tctx.Status.Start(\"Starting control-plane 🕹️\")\n\tdefer ctx.Status.End(false)\n\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// get the target node for this task\n\t// TODO: eliminate the concept of bootstrapcontrolplane node entirely\n\t// outside this method\n\tnode, err := nodeutils.BootstrapControlPlaneNode(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkubeVersionStr, err := nodeutils.KubeVersion(node)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to get kubernetes version from node\")\n\t}\n\tkubeVersion, err := version.ParseGeneric(kubeVersionStr)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to parse kubernetes version %q\", kubeVersionStr)\n\t}\n\n\targs := []string{\n\t\t// init because this is the control plane node\n\t\t\"init\",\n\t\t// specify our generated config file\n\t\t\"--config=/kind/kubeadm.conf\",\n\t\t\"--skip-token-print\",\n\t\t// increase verbosity for debugging\n\t\t\"--v=6\",\n\t}\n\n\t// Newer versions set this in the config file.\n\tif kubeVersion.LessThan(version.MustParseSemantic(\"v1.23.0\")) {\n\t\t// Skip preflight to avoid pulling images.\n\t\t// Kind pre-pulls images and preflight may conflict with that.\n\t\tskipPhases := \"preflight\"\n\t\tif a.skipKubeProxy {\n\t\t\tskipPhases += \",addon/kube-proxy\"\n\t\t}\n\t\targs = append(args, \"--skip-phases=\"+skipPhases)\n\t}\n\n\t// run kubeadm\n\tcmd := node.Command(\"kubeadm\", args...)\n\tlines, err := exec.CombinedOutputLines(cmd)\n\tctx.Logger.V(3).Info(strings.Join(lines, \"\\n\"))\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to init node with kubeadm\")\n\t}\n\n\t// copy some files to the other control plane nodes\n\totherControlPlanes, err := nodeutils.SecondaryControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, otherNode := range otherControlPlanes {\n\t\tfor _, file := range []string{\n\t\t\t// copy over admin config so we can use any control plane to get it later\n\t\t\t\"/etc/kubernetes/admin.conf\",\n\t\t\t// copy over certs\n\t\t\t\"/etc/kubernetes/pki/ca.crt\", \"/etc/kubernetes/pki/ca.key\",\n\t\t\t\"/etc/kubernetes/pki/front-proxy-ca.crt\", \"/etc/kubernetes/pki/front-proxy-ca.key\",\n\t\t\t\"/etc/kubernetes/pki/sa.pub\", \"/etc/kubernetes/pki/sa.key\",\n\t\t\t// TODO: if we gain external etcd support these will be\n\t\t\t// handled differently\n\t\t\t\"/etc/kubernetes/pki/etcd/ca.crt\", \"/etc/kubernetes/pki/etcd/ca.key\",\n\t\t} {\n\t\t\tif err := nodeutils.CopyNodeToNode(node, otherNode, file); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"failed to copy admin kubeconfig\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// if we are only provisioning one node, remove the control plane taint\n\t// https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#master-isolation\n\tif len(allNodes) == 1 {\n\t\t// TODO: Once kubeadm 1.23 is no longer supported remove the <1.24 handling.\n\t\t// TODO: Once kubeadm 1.24 is no longer supported remove the <1.25 handling.\n\t\t// https://github.com/kubernetes-sigs/kind/issues/1699\n\t\trawVersion, err := nodeutils.KubeVersion(node)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to get Kubernetes version from node\")\n\t\t}\n\t\tkubeVersion, err := version.ParseSemantic(rawVersion)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse Kubernetes version\")\n\t\t}\n\t\tvar taints []string\n\t\tif kubeVersion.LessThan(version.MustParseSemantic(\"v1.24.0-alpha.1.592+370031cadac624\")) {\n\t\t\t// for versions older than 1.24 prerelease remove only the old taint\n\t\t\ttaints = []string{\"node-role.kubernetes.io/master-\"}\n\t\t} else if kubeVersion.LessThan(version.MustParseSemantic(\"v1.25.0-alpha.0.557+84c8afeba39ec9\")) {\n\t\t\t// for versions between 1.24 and 1.25 prerelease remove both the old and new taint\n\t\t\ttaints = []string{\"node-role.kubernetes.io/control-plane-\", \"node-role.kubernetes.io/master-\"}\n\t\t} else {\n\t\t\t// for any newer version only remove the new taint\n\t\t\ttaints = []string{\"node-role.kubernetes.io/control-plane-\"}\n\t\t}\n\t\ttaintArgs := []string{\"--kubeconfig=/etc/kubernetes/admin.conf\", \"taint\", \"nodes\", \"--all\"}\n\t\ttaintArgs = append(taintArgs, taints...)\n\n\t\tif err := node.Command(\n\t\t\t\"kubectl\", taintArgs...,\n\t\t).Run(); err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to remove control plane taint\")\n\t\t}\n\t}\n\n\t// Kubeadm will add `node.kubernetes.io/exclude-from-external-load-balancers` on control plane nodes.\n\t// For single node clusters, this means we cannot have a load balancer at all (MetalLB, etc), so remove the label.\n\tif len(allNodes) == 1 {\n\t\tlabelArgs := []string{\"--kubeconfig=/etc/kubernetes/admin.conf\", \"label\", \"nodes\", \"--all\", \"node.kubernetes.io/exclude-from-external-load-balancers-\"}\n\t\tif err := node.Command(\n\t\t\t\"kubectl\", labelArgs...,\n\t\t).Run(); err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to remove control plane load balancer label\")\n\t\t}\n\t}\n\n\t// mark success\n\tctx.Status.End(true)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/kubeadmjoin/join.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeadmjoin implements the kubeadm join action\npackage kubeadmjoin\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n)\n\n// Action implements action for creating the kubeadm join\n// and deploying it on the bootstrap control-plane node.\ntype Action struct{}\n\n// NewAction returns a new action for creating the kubeadm jion\nfunc NewAction() actions.Action {\n\treturn &Action{}\n}\n\n// Execute runs the action\nfunc (a *Action) Execute(ctx *actions.ActionContext) error {\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// join secondary control plane nodes if any\n\tsecondaryControlPlanes, err := nodeutils.SecondaryControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(secondaryControlPlanes) > 0 {\n\t\tif err := joinSecondaryControlPlanes(ctx, secondaryControlPlanes); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// then join worker nodes if any\n\tworkers, err := nodeutils.SelectNodesByRole(allNodes, constants.WorkerNodeRoleValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(workers) > 0 {\n\t\tif err := joinWorkers(ctx, workers); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc joinSecondaryControlPlanes(\n\tctx *actions.ActionContext,\n\tsecondaryControlPlanes []nodes.Node,\n) error {\n\tctx.Status.Start(\"Joining more control-plane nodes 🎮\")\n\tdefer ctx.Status.End(false)\n\n\t// TODO(bentheelder): it's too bad we can't do this concurrently\n\t// (this is not safe currently)\n\tfor _, node := range secondaryControlPlanes {\n\t\tnode := node // capture loop variable\n\t\tif err := runKubeadmJoin(ctx.Logger, node); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tctx.Status.End(true)\n\treturn nil\n}\n\nfunc joinWorkers(\n\tctx *actions.ActionContext,\n\tworkers []nodes.Node,\n) error {\n\tctx.Status.Start(\"Joining worker nodes 🚜\")\n\tdefer ctx.Status.End(false)\n\n\t// create the workers concurrently\n\tfns := []func() error{}\n\tfor _, node := range workers {\n\t\tnode := node // capture loop variable\n\t\tfns = append(fns, func() error {\n\t\t\treturn runKubeadmJoin(ctx.Logger, node)\n\t\t})\n\t}\n\tif err := errors.UntilErrorConcurrent(fns); err != nil {\n\t\treturn err\n\t}\n\n\tctx.Status.End(true)\n\treturn nil\n}\n\n// runKubeadmJoin executes kubeadm join command\nfunc runKubeadmJoin(logger log.Logger, node nodes.Node) error {\n\tkubeVersionStr, err := nodeutils.KubeVersion(node)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to get kubernetes version from node\")\n\t}\n\tkubeVersion, err := version.ParseGeneric(kubeVersionStr)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to parse kubernetes version %q\", kubeVersionStr)\n\t}\n\n\targs := []string{\n\t\t\"join\",\n\t\t// the join command uses the config file generated in a well known location\n\t\t\"--config\", \"/kind/kubeadm.conf\",\n\t\t// increase verbosity for debugging\n\t\t\"--v=6\",\n\t}\n\t// Newer versions set this in the config file.\n\tif kubeVersion.LessThan(version.MustParseSemantic(\"v1.23.0\")) {\n\t\t// Skip preflight to avoid pulling images.\n\t\t// Kind pre-pulls images and preflight may conflict with that.\n\t\targs = append(args, \"--skip-phases=preflight\")\n\t}\n\n\t// run kubeadm join\n\tcmd := node.Command(\"kubeadm\", args...)\n\tlines, err := exec.CombinedOutputLines(cmd)\n\tlogger.V(3).Info(strings.Join(lines, \"\\n\"))\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to join node with kubeadm\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/loadbalancer/loadbalancer.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package loadbalancer implements the load balancer configuration action\npackage loadbalancer\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n)\n\n// Action implements and action for configuring and starting the\n// external load balancer in front of the control-plane nodes.\ntype Action struct{}\n\n// NewAction returns a new Action for configuring the load balancer\nfunc NewAction() actions.Action {\n\treturn &Action{}\n}\n\n// Execute runs the action\nfunc (a *Action) Execute(ctx *actions.ActionContext) error {\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// identify external load balancer node\n\tloadBalancerNode, err := nodeutils.ExternalLoadBalancerNode(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// if there's no loadbalancer we're done\n\tif loadBalancerNode == nil {\n\t\treturn nil\n\t}\n\n\t// otherwise notify the user\n\tctx.Status.Start(\"Configuring the external load balancer ⚖️\")\n\tdefer ctx.Status.End(false)\n\n\t// collect info about the existing controlplane nodes\n\tvar backendServers = map[string]string{}\n\tcontrolPlaneNodes, err := nodeutils.SelectNodesByRole(\n\t\tallNodes,\n\t\tconstants.ControlPlaneNodeRoleValue,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, n := range controlPlaneNodes {\n\t\tbackendServers[n.String()] = fmt.Sprintf(\"%s:%d\", n.String(), common.APIServerInternalPort)\n\t}\n\n\t// create loadbalancer config data\n\tloadbalancerConfig, err := loadbalancer.Config(&loadbalancer.ConfigData{\n\t\tControlPlanePort: common.APIServerInternalPort,\n\t\tBackendServers:   backendServers,\n\t\tIPv6:             ctx.Config.Networking.IPFamily == config.IPv6Family,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to generate loadbalancer config data\")\n\t}\n\n\t// create loadbalancer config on the node\n\tif err := nodeutils.WriteFile(loadBalancerNode, loadbalancer.ConfigPath, loadbalancerConfig); err != nil {\n\t\t// TODO: logging here\n\t\treturn errors.Wrap(err, \"failed to copy loadbalancer config to node\")\n\t}\n\n\t// reload the config. haproxy will reload on SIGHUP\n\tif err := loadBalancerNode.Command(\"kill\", \"-s\", \"HUP\", \"1\").Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to reload loadbalancer\")\n\t}\n\n\tctx.Status.End(true)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/actions/waitforready/waitforready.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package waitforready implements the wait for ready action\npackage waitforready\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\n// Action implements an action for waiting for the cluster to be ready\ntype Action struct {\n\twaitTime time.Duration\n}\n\n// NewAction returns a new action for waiting for the cluster to be ready\nfunc NewAction(waitTime time.Duration) actions.Action {\n\treturn &Action{\n\t\twaitTime: waitTime,\n\t}\n}\n\n// Execute runs the action\nfunc (a *Action) Execute(ctx *actions.ActionContext) error {\n\t// skip entirely if the wait time is 0\n\tif a.waitTime == time.Duration(0) {\n\t\treturn nil\n\t}\n\tctx.Status.Start(\n\t\tfmt.Sprintf(\n\t\t\t\"Waiting ≤ %s for control-plane = Ready ⏳\",\n\t\t\tformatDuration(a.waitTime),\n\t\t),\n\t)\n\n\tallNodes, err := ctx.Nodes()\n\tif err != nil {\n\t\treturn err\n\t}\n\t// get a control plane node to use to check cluster status\n\tcontrolPlanes, err := nodeutils.ControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnode := controlPlanes[0] // kind expects at least one always\n\n\t// Wait for the nodes to reach Ready status.\n\tstartTime := time.Now()\n\n\t// TODO: Remove the below handling once kubeadm 1.23 is no longer supported.\n\t// https://github.com/kubernetes-sigs/kind/issues/1699\n\trawVersion, err := nodeutils.KubeVersion(node)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to get Kubernetes version from node\")\n\t}\n\tkubeVersion, err := version.ParseSemantic(rawVersion)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse Kubernetes version\")\n\t}\n\tselectorLabel := \"node-role.kubernetes.io/control-plane\"\n\tif kubeVersion.LessThan(version.MustParseSemantic(\"v1.24.0-alpha.1.591+a3d5e5598290df\")) {\n\t\tselectorLabel = \"node-role.kubernetes.io/master\"\n\t}\n\n\tisReady := waitForReady(node, startTime.Add(a.waitTime), selectorLabel)\n\tif !isReady {\n\t\tctx.Status.End(false)\n\t\tctx.Logger.V(0).Info(\" • WARNING: Timed out waiting for Ready ⚠️\")\n\t\treturn nil\n\t}\n\n\t// mark success\n\tctx.Status.End(true)\n\tctx.Logger.V(0).Infof(\" • Ready after %s 💚\", formatDuration(time.Since(startTime)))\n\treturn nil\n}\n\n// WaitForReady uses kubectl inside the \"node\" container to check if the\n// control plane nodes are \"Ready\".\nfunc waitForReady(node nodes.Node, until time.Time, selectorLabel string) bool {\n\treturn tryUntil(until, func() bool {\n\t\tcmd := node.Command(\n\t\t\t\"kubectl\",\n\t\t\t\"--kubeconfig=/etc/kubernetes/admin.conf\",\n\t\t\t\"get\",\n\t\t\t\"nodes\",\n\t\t\t\"--selector=\"+selectorLabel,\n\t\t\t// When the node reaches status ready, the status field will be set\n\t\t\t// to true.\n\t\t\t\"-o=jsonpath='{.items..status.conditions[-1:].status}'\",\n\t\t)\n\t\tlines, err := exec.OutputLines(cmd)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\t// 'lines' will return the status of all nodes labeled as master. For\n\t\t// example, if we have three control plane nodes, and all are ready,\n\t\t// then the status will have the following format: `True True True'.\n\t\tstatus := strings.Fields(lines[0])\n\t\tfor _, s := range status {\n\t\t\t// Check node status. If node is ready then this will be 'True',\n\t\t\t// 'False' or 'Unknown' otherwise.\n\t\t\tif !strings.Contains(s, \"True\") {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n}\n\n// helper that calls `try()“ in a loop until the deadline `until`\n// has passed or `try()`returns true, returns whether try ever returned true\nfunc tryUntil(until time.Time, try func() bool) bool {\n\tfor until.After(time.Now()) {\n\t\tif try() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc formatDuration(duration time.Duration) string {\n\treturn duration.Round(time.Second).String()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/create/create.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package create contains functionality for cluster creation.\npackage create\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"al.essio.dev/pkg/shellescape\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/delete\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config/encoding\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions\"\n\tconfigaction \"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/config\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/installcni\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/installstorage\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/kubeadminit\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/kubeadmjoin\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/loadbalancer\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/create/actions/waitforready\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig\"\n)\n\nconst (\n\t// Typical host name max limit is 64 characters (https://linux.die.net/man/2/sethostname)\n\t// We append -control-plane (14 characters) to the cluster name on the control plane container\n\tclusterNameMax = 50\n)\n\n// ClusterOptions holds cluster creation options\ntype ClusterOptions struct {\n\tConfig       *config.Cluster\n\tNameOverride string // overrides config.Name\n\t// NodeImage overrides the nodes' images in Config if non-zero\n\tNodeImage      string\n\tRetain         bool\n\tWaitForReady   time.Duration\n\tKubeconfigPath string\n\t// see https://github.com/kubernetes-sigs/kind/issues/324\n\tStopBeforeSettingUpKubernetes bool // if false kind should setup kubernetes after creating nodes\n\t// Options to control output\n\tDisplayUsage      bool\n\tDisplaySalutation bool\n}\n\n// Cluster creates a cluster\nfunc Cluster(logger log.Logger, p providers.Provider, opts *ClusterOptions) error {\n\t// validate provider first\n\tif err := validateProvider(p); err != nil {\n\t\treturn err\n\t}\n\n\t// default / process options (namely config)\n\tif err := fixupOptions(opts); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the cluster name already exists\n\tif err := alreadyExists(p, opts.Config.Name); err != nil {\n\t\treturn err\n\t}\n\n\t// warn if cluster name might typically be too long\n\tif len(opts.Config.Name) > clusterNameMax {\n\t\tlogger.Warnf(\"cluster name %q is probably too long, this might not work properly on some systems\", opts.Config.Name)\n\t}\n\n\t// then validate\n\tif err := opts.Config.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\t// setup a status object to show progress to the user\n\tstatus := cli.StatusForLogger(logger)\n\n\t// we're going to start creating now, tell the user\n\tlogger.V(0).Infof(\"Creating cluster %q ...\\n\", opts.Config.Name)\n\n\t// Create node containers implementing defined config Nodes\n\tif err := p.Provision(status, opts.Config); err != nil {\n\t\t// In case of errors nodes are deleted (except if retain is explicitly set)\n\t\tif !opts.Retain {\n\t\t\t_ = delete.Cluster(logger, p, opts.Config.Name, opts.KubeconfigPath)\n\t\t}\n\t\treturn err\n\t}\n\n\t// TODO(bentheelder): make this controllable from the command line?\n\tactionsToRun := []actions.Action{\n\t\tloadbalancer.NewAction(), // setup external loadbalancer\n\t\tconfigaction.NewAction(), // setup kubeadm config\n\t}\n\tif !opts.StopBeforeSettingUpKubernetes {\n\t\tactionsToRun = append(actionsToRun,\n\t\t\tkubeadminit.NewAction(opts.Config), // run kubeadm init\n\t\t)\n\t\t// this step might be skipped, but is next after init\n\t\tif !opts.Config.Networking.DisableDefaultCNI {\n\t\t\tactionsToRun = append(actionsToRun,\n\t\t\t\tinstallcni.NewAction(), // install CNI\n\t\t\t)\n\t\t}\n\t\t// add remaining steps\n\t\tactionsToRun = append(actionsToRun,\n\t\t\tinstallstorage.NewAction(),                // install StorageClass\n\t\t\tkubeadmjoin.NewAction(),                   // run kubeadm join\n\t\t\twaitforready.NewAction(opts.WaitForReady), // wait for cluster readiness\n\t\t)\n\t}\n\n\t// run all actions\n\tactionsContext := actions.NewActionContext(logger, status, p, opts.Config)\n\tfor _, action := range actionsToRun {\n\t\tif err := action.Execute(actionsContext); err != nil {\n\t\t\tif !opts.Retain {\n\t\t\t\t_ = delete.Cluster(logger, p, opts.Config.Name, opts.KubeconfigPath)\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// skip the rest if we're not setting up kubernetes\n\tif opts.StopBeforeSettingUpKubernetes {\n\t\treturn nil\n\t}\n\n\t// try exporting kubeconfig with backoff for locking failures\n\t// TODO: factor out into a public errors API w/ backoff handling?\n\t// for now this is easier than coming up with a good API\n\tvar err error\n\tfor _, b := range []time.Duration{0, time.Millisecond, time.Millisecond * 50, time.Millisecond * 100} {\n\t\ttime.Sleep(b)\n\t\tif err = kubeconfig.Export(p, opts.Config.Name, opts.KubeconfigPath, true); err == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// optionally display usage\n\tif opts.DisplayUsage {\n\t\tlogUsage(logger, opts.Config.Name, opts.KubeconfigPath)\n\t}\n\t// optionally give the user a friendly salutation\n\tif opts.DisplaySalutation {\n\t\tlogger.V(0).Info(\"\")\n\t\tlogSalutation(logger)\n\t}\n\treturn nil\n}\n\n// alreadyExists returns an error if the cluster name already exists\n// or if we had an error checking\nfunc alreadyExists(p providers.Provider, name string) error {\n\tn, err := p.ListNodes(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(n) != 0 {\n\t\treturn errors.Errorf(\"node(s) already exist for a cluster with the name %q\", name)\n\t}\n\treturn nil\n}\n\nfunc logUsage(logger log.Logger, name, explicitKubeconfigPath string) {\n\t// construct a sample command for interacting with the cluster\n\tkctx := kubeconfig.ContextForCluster(name)\n\tsampleCommand := fmt.Sprintf(\"kubectl cluster-info --context %s\", kctx)\n\tif explicitKubeconfigPath != \"\" {\n\t\t// explicit path, include this\n\t\tsampleCommand += \" --kubeconfig \" + shellescape.Quote(explicitKubeconfigPath)\n\t}\n\tlogger.V(0).Infof(`Set kubectl context to \"%s\"`, kctx)\n\tlogger.V(0).Infof(\"You can now use your cluster with:\\n\\n\" + sampleCommand)\n}\n\nfunc logSalutation(logger log.Logger) {\n\tsalutations := []string{\n\t\t\"Have a nice day! 👋\",\n\t\t\"Thanks for using kind! 😊\",\n\t\t\"Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/\",\n\t\t\"Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂\",\n\t}\n\tr := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))\n\ts := salutations[r.Intn(len(salutations))]\n\tlogger.V(0).Info(s)\n}\n\nfunc fixupOptions(opts *ClusterOptions) error {\n\t// do post processing for options\n\t// first ensure we at least have a default cluster config\n\tif opts.Config == nil {\n\t\tcfg, err := encoding.Load(\"\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topts.Config = cfg\n\t}\n\n\tif opts.NameOverride != \"\" {\n\t\topts.Config.Name = opts.NameOverride\n\t}\n\n\t// if NodeImage was set, override the image on all nodes\n\tif opts.NodeImage != \"\" {\n\t\t// Apply image override to all the Nodes defined in Config\n\t\t// TODO(fabrizio pandini): this should be reconsidered when implementing\n\t\t//     https://github.com/kubernetes-sigs/kind/issues/133\n\t\tfor i := range opts.Config.Nodes {\n\t\t\topts.Config.Nodes[i].Image = opts.NodeImage\n\t\t}\n\t}\n\n\t// default config fields (important for usage as a library, where the config\n\t// may be constructed in memory rather than from disk)\n\tconfig.SetDefaultsCluster(opts.Config)\n\n\treturn nil\n}\n\nfunc validateProvider(p providers.Provider) error {\n\tinfo, err := p.Info()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif info.Rootless {\n\t\tif !info.Cgroup2 {\n\t\t\treturn errors.New(\"running kind with rootless provider requires cgroup v2, see https://kind.sigs.k8s.io/docs/user/rootless/\")\n\t\t}\n\t\tif !info.SupportsMemoryLimit || !info.SupportsPidsLimit || !info.SupportsCPUShares {\n\t\t\treturn errors.New(\"running kind with rootless provider requires setting systemd property \\\"Delegate=yes\\\", see https://kind.sigs.k8s.io/docs/user/rootless/\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/delete/delete.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package delete contains the logic for performing cluster deletion.\npackage delete\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n)\n\n// Cluster deletes the cluster identified by ctx\n// explicitKubeconfigPath is --kubeconfig, following the rules from\n// https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands\nfunc Cluster(logger log.Logger, p providers.Provider, name, explicitKubeconfigPath string) error {\n\tn, err := p.ListNodes(name)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"error listing nodes\")\n\t}\n\n\tkerr := kubeconfig.Remove(name, explicitKubeconfigPath)\n\tif kerr != nil {\n\t\tlogger.Errorf(\"failed to update kubeconfig: %v\", kerr)\n\t}\n\n\tif len(n) > 0 {\n\t\terr = p.DeleteNodes(n)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlogger.V(0).Infof(\"Deleted nodes: %q\", n)\n\t}\n\n\tif kerr != nil {\n\t\treturn kerr\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeadm/config.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeadm\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\n// ConfigData is supplied to the kubeadm config template, with values populated\n// by the cluster package\ntype ConfigData struct {\n\tClusterName       string\n\tKubernetesVersion string\n\t// The ControlPlaneEndpoint, that is the address of the external loadbalancer\n\t// if defined or the bootstrap node\n\tControlPlaneEndpoint string\n\t// The Local API Server port\n\tAPIBindPort int\n\t// The API server external listen IP (which we will port forward)\n\tAPIServerAddress string\n\n\t// this should really be used for the --provider-id flag\n\t// ideally cluster config should not depend on the node backend otherwise ...\n\tNodeProvider string\n\n\t// ControlPlane flag specifies the node belongs to the control plane\n\tControlPlane bool\n\t// The IP address or comma separated list IP addresses of of the node\n\tNodeAddress string\n\t// The name for the node (not the address)\n\tNodeName string\n\n\t// The Token for TLS bootstrap\n\tToken string\n\n\t// KubeProxyMode defines the kube-proxy mode between iptables, ipvs or nftables\n\tKubeProxyMode string\n\t// The subnet used for pods\n\tPodSubnet string\n\t// The subnet used for services\n\tServiceSubnet string\n\n\t// Kubernetes FeatureGates\n\tFeatureGates map[string]bool\n\n\t// Kubernetes API Server RuntimeConfig\n\tRuntimeConfig map[string]string\n\n\t// IPFamily of the cluster, it can be IPv4, IPv6 or DualStack\n\tIPFamily config.ClusterIPFamily\n\n\t// Labels are the labels, in the format \"key1=val1,key2=val2\", with which the respective node will be labeled\n\tNodeLabels string\n\n\t// RootlessProvider is true if kind is running with rootless mode\n\tRootlessProvider bool\n\n\t// DerivedConfigData contains fields computed from the other fields for use\n\t// in the config templates and should only be populated by calling Derive()\n\tDerivedConfigData\n}\n\n// DerivedConfigData fields are automatically derived by\n// ConfigData.Derive if they are not specified / zero valued\ntype DerivedConfigData struct {\n\t// AdvertiseAddress is the first address in NodeAddress\n\tAdvertiseAddress string\n\t// DockerStableTag is automatically derived from KubernetesVersion\n\tDockerStableTag string\n\t// SortedFeatureGates allows us to iterate FeatureGates deterministically\n\tSortedFeatureGates []FeatureGate\n\t// FeatureGatesString is of the form `Foo=true,Baz=false`\n\tFeatureGatesString string\n\t// RuntimeConfigString is of the form `Foo=true,Baz=false`\n\tRuntimeConfigString string\n\t// KubeadmFeatureGates contains Kubeadm only feature gates\n\tKubeadmFeatureGates map[string]bool\n\t// IPv4 values take precedence over IPv6 by default, if true set IPv6 default values\n\tIPv6 bool\n\t// kubelet cgroup driver, based on kubernetes version\n\tCgroupDriver string\n\t// JoinSkipPhases are the skipPhases values for the JoinConfiguration.\n\tJoinSkipPhases []string\n\t// InitSkipPhases are the skipPhases values for the InitConfiguration.\n\tInitSkipPhases []string\n}\n\n// FeatureGate contains the enablement setting for a feature gate.\ntype FeatureGate struct {\n\tName  string\n\tValue bool\n}\n\n// Derive automatically derives DockerStableTag if not specified\nfunc (c *ConfigData) Derive() {\n\t// default cgroup driver\n\t// TODO: refactor and move all deriving logic to this method\n\tc.CgroupDriver = \"systemd\"\n\n\t// get the first address to use it as the API advertised address\n\tc.AdvertiseAddress = strings.Split(c.NodeAddress, \",\")[0]\n\n\tif c.DockerStableTag == \"\" {\n\t\tc.DockerStableTag = strings.ReplaceAll(c.KubernetesVersion, \"+\", \"_\")\n\t}\n\n\t// get the IP addresses family for defaulting components\n\tc.IPv6 = c.IPFamily == config.IPv6Family\n\n\t// get sorted list of FeatureGate keys\n\tfeatureGateKeys := make([]string, 0, len(c.FeatureGates))\n\tfor k := range c.FeatureGates {\n\t\tfeatureGateKeys = append(featureGateKeys, k)\n\t}\n\tsort.Strings(featureGateKeys)\n\n\t// create a sorted key=value,... string of FeatureGates\n\tc.SortedFeatureGates = make([]FeatureGate, 0, len(c.FeatureGates))\n\tfeatureGates := make([]string, 0, len(c.FeatureGates))\n\tfor _, k := range featureGateKeys {\n\t\tv := c.FeatureGates[k]\n\t\tfeatureGates = append(featureGates, fmt.Sprintf(\"%s=%t\", k, v))\n\t\tc.SortedFeatureGates = append(c.SortedFeatureGates, FeatureGate{\n\t\t\tName:  k,\n\t\t\tValue: v,\n\t\t})\n\t}\n\tc.FeatureGatesString = strings.Join(featureGates, \",\")\n\n\t// create a sorted key=value,... string of RuntimeConfig\n\t// first get sorted list of FeatureGate keys\n\truntimeConfigKeys := make([]string, 0, len(c.RuntimeConfig))\n\tfor k := range c.RuntimeConfig {\n\t\truntimeConfigKeys = append(runtimeConfigKeys, k)\n\t}\n\tsort.Strings(runtimeConfigKeys)\n\t// stringify\n\tvar runtimeConfig []string\n\tfor _, k := range runtimeConfigKeys {\n\t\tv := c.RuntimeConfig[k]\n\t\t// TODO: do we need to quote / escape these in the future?\n\t\t// Currently runtime config is in practice booleans, no special characters\n\t\truntimeConfig = append(runtimeConfig, fmt.Sprintf(\"%s=%s\", k, v))\n\t}\n\tc.RuntimeConfigString = strings.Join(runtimeConfig, \",\")\n\n\t// Skip preflight to avoid pulling images.\n\t// Kind pre-pulls images and preflight may conflict with that.\n\t// requires kubeadm 1.22+\n\tc.JoinSkipPhases = []string{\"preflight\"}\n\tc.InitSkipPhases = []string{\"preflight\"}\n\tif c.KubeProxyMode == string(config.NoneProxyMode) {\n\t\tc.InitSkipPhases = append(c.InitSkipPhases, \"addon/kube-proxy\")\n\t}\n}\n\n// See docs for these APIs at:\n// https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm#pkg-subdirectories\n// EG:\n// https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1\n\n// ConfigTemplateBetaV2 is the kubeadm config template for API version v1beta2\nconst ConfigTemplateBetaV2 = `# config generated by kind\napiVersion: kubeadm.k8s.io/v1beta2\nkind: ClusterConfiguration\nmetadata:\n  name: config\nkubernetesVersion: {{.KubernetesVersion}}\nclusterName: \"{{.ClusterName}}\"\n{{ if .KubeadmFeatureGates}}featureGates:\n{{ range $key, $value := .KubeadmFeatureGates }}\n  \"{{ $key }}\": {{ $value }}\n{{end}}{{end}}\ncontrolPlaneEndpoint: \"{{ .ControlPlaneEndpoint }}\"\n# on docker for mac we have to expose the api server via port forward,\n# so we need to ensure the cert is valid for localhost so we can talk\n# to the cluster after rewriting the kubeconfig to point to localhost\napiServer:\n  certSANs: [localhost, \"{{.APIServerAddress}}\"]\n  extraArgs:\n    \"runtime-config\": \"{{ .RuntimeConfigString }}\"\n{{ if .FeatureGates }}\n    \"feature-gates\": \"{{ .FeatureGatesString }}\"\n{{ end}}\ncontrollerManager:\n  extraArgs:\n{{ if .FeatureGates }}\n    \"feature-gates\": \"{{ .FeatureGatesString }}\"\n{{ end }}\n    enable-hostpath-provisioner: \"true\"\n    # configure ipv6 default addresses for IPv6 clusters\n    {{ if .IPv6 -}}\n    bind-address: \"::\"\n    {{- end }}\nscheduler:\n  extraArgs:\n{{ if .FeatureGates }}\n    \"feature-gates\": \"{{ .FeatureGatesString }}\"\n{{ end }}\n    # configure ipv6 default addresses for IPv6 clusters\n    {{ if .IPv6 -}}\n    bind-address: \"::1\"\n    {{- end }}\nnetworking:\n  podSubnet: \"{{ .PodSubnet }}\"\n  serviceSubnet: \"{{ .ServiceSubnet }}\"\n---\napiVersion: kubeadm.k8s.io/v1beta2\nkind: InitConfiguration\nmetadata:\n  name: config\n# we use a well know token for TLS bootstrap\nbootstrapTokens:\n- token: \"{{ .Token }}\"\n# we use a well know port for making the API server discoverable inside docker network. \n# from the host machine such port will be accessible via a random local port instead.\nlocalAPIEndpoint:\n  advertiseAddress: \"{{ .AdvertiseAddress }}\"\n  bindPort: {{.APIBindPort}}\nnodeRegistration:\n  criSocket: \"unix:///run/containerd/containerd.sock\"\n  kubeletExtraArgs:\n    node-ip: \"{{ .NodeAddress }}\"\n    provider-id: \"kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}\"\n    node-labels: \"{{ .NodeLabels }}\"\n---\n# no-op entry that exists solely so it can be patched\napiVersion: kubeadm.k8s.io/v1beta2\nkind: JoinConfiguration\nmetadata:\n  name: config\n{{ if .ControlPlane -}}\ncontrolPlane:\n  localAPIEndpoint:\n    advertiseAddress: \"{{ .AdvertiseAddress }}\"\n    bindPort: {{.APIBindPort}}\n{{- end }}\nnodeRegistration:\n  criSocket: \"unix:///run/containerd/containerd.sock\"\n  kubeletExtraArgs:\n    node-ip: \"{{ .NodeAddress }}\"\n    provider-id: \"kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}\"\n    node-labels: \"{{ .NodeLabels }}\"\ndiscovery:\n  bootstrapToken:\n    apiServerEndpoint: \"{{ .ControlPlaneEndpoint }}\"\n    token: \"{{ .Token }}\"\n    unsafeSkipCAVerification: true\n---\napiVersion: kubelet.config.k8s.io/v1beta1\nkind: KubeletConfiguration\nmetadata:\n  name: config\ncgroupDriver: {{ .CgroupDriver }}\ncgroupRoot: /kubelet\nfailSwapOn: false\n# configure ipv6 addresses in IPv6 mode\n{{ if .IPv6 -}}\naddress: \"::\"\nhealthzBindAddress: \"::\"\n{{- end }}\n# disable disk resource management by default\n# kubelet will see the host disk that the inner container runtime\n# is ultimately backed by and attempt to recover disk space. we don't want that.\nimageGCHighThresholdPercent: 100\nevictionHard:\n  nodefs.available: \"0%\"\n  nodefs.inodesFree: \"0%\"\n  imagefs.available: \"0%\"\n{{if .FeatureGates}}featureGates:\n{{ range $index, $gate := .SortedFeatureGates }}\n  \"{{ $gate.Name }}\": {{ $gate.Value }}\n{{end}}{{end}}\n{{if ne .KubeProxyMode \"none\"}}\n---\napiVersion: kubeproxy.config.k8s.io/v1alpha1\nkind: KubeProxyConfiguration\nmetadata:\n  name: config\nmode: \"{{ .KubeProxyMode }}\"\n{{if .FeatureGates}}featureGates:\n{{ range $index, $gate := .SortedFeatureGates }}\n  \"{{ $gate.Name }}\": {{ $gate.Value }}\n{{end}}{{end}}\niptables:\n  minSyncPeriod: 1s\nconntrack:\n# Skip setting sysctl value \"net.netfilter.nf_conntrack_max\"\n# It is a global variable that affects other namespaces\n  maxPerCore: 0\n# Set sysctl value \"net.netfilter.nf_conntrack_tcp_be_liberal\"\n# for nftables proxy (theoretically for kernels older than 6.1)\n# xref: https://github.com/kubernetes/kubernetes/issues/117924\n{{if and (eq .KubeProxyMode \"nftables\") (not .RootlessProvider)}}\n  tcpBeLiberal: true\n{{end}}\n{{if .RootlessProvider}}\n# Skip setting \"net.netfilter.nf_conntrack_tcp_timeout_established\"\n  tcpEstablishedTimeout: 0s\n# Skip setting \"net.netfilter.nf_conntrack_tcp_timeout_close\"\n  tcpCloseWaitTimeout: 0s\n{{end}}{{end}}\n`\n\n// ConfigTemplateBetaV3 is the kubeadm config template for API version v1beta3\nconst ConfigTemplateBetaV3 = `# config generated by kind\napiVersion: kubeadm.k8s.io/v1beta3\nkind: ClusterConfiguration\nmetadata:\n  name: config\nkubernetesVersion: {{.KubernetesVersion}}\nclusterName: \"{{.ClusterName}}\"\n{{ if .KubeadmFeatureGates}}featureGates:\n{{ range $key, $value := .KubeadmFeatureGates }}\n  \"{{ $key }}\": {{ $value }}\n{{end}}{{end}}\ncontrolPlaneEndpoint: \"{{ .ControlPlaneEndpoint }}\"\n# on docker for mac we have to expose the api server via port forward,\n# so we need to ensure the cert is valid for localhost so we can talk\n# to the cluster after rewriting the kubeconfig to point to localhost\napiServer:\n  certSANs: [localhost, \"{{.APIServerAddress}}\"]\n  extraArgs:\n    \"runtime-config\": \"{{ .RuntimeConfigString }}\"\n{{ if .FeatureGates }}\n    \"feature-gates\": \"{{ .FeatureGatesString }}\"\n{{ end}}\ncontrollerManager:\n  extraArgs:\n{{ if .FeatureGates }}\n    \"feature-gates\": \"{{ .FeatureGatesString }}\"\n{{ end }}\n    enable-hostpath-provisioner: \"true\"\n    # configure ipv6 default addresses for IPv6 clusters\n    {{ if .IPv6 -}}\n    bind-address: \"::\"\n    {{- end }}\nscheduler:\n  extraArgs:\n{{ if .FeatureGates }}\n    \"feature-gates\": \"{{ .FeatureGatesString }}\"\n{{ end }}\n    # configure ipv6 default addresses for IPv6 clusters\n    {{ if .IPv6 -}}\n    bind-address: \"::1\"\n    {{- end }}\nnetworking:\n  podSubnet: \"{{ .PodSubnet }}\"\n  serviceSubnet: \"{{ .ServiceSubnet }}\"\n---\napiVersion: kubeadm.k8s.io/v1beta3\nkind: InitConfiguration\nmetadata:\n  name: config\n# we use a well know token for TLS bootstrap\nbootstrapTokens:\n- token: \"{{ .Token }}\"\n# we use a well know port for making the API server discoverable inside docker network. \n# from the host machine such port will be accessible via a random local port instead.\nlocalAPIEndpoint:\n  advertiseAddress: \"{{ .AdvertiseAddress }}\"\n  bindPort: {{.APIBindPort}}\nnodeRegistration:\n  criSocket: \"unix:///run/containerd/containerd.sock\"\n  kubeletExtraArgs:\n    node-ip: \"{{ .NodeAddress }}\"\n    provider-id: \"kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}\"\n    node-labels: \"{{ .NodeLabels }}\"\n{{ if .InitSkipPhases -}}\nskipPhases:\n  {{- range $phase := .InitSkipPhases }}\n  - \"{{ $phase }}\"\n  {{- end }}\n{{- end }}\n---\n# no-op entry that exists solely so it can be patched\napiVersion: kubeadm.k8s.io/v1beta3\nkind: JoinConfiguration\nmetadata:\n  name: config\n{{ if .ControlPlane -}}\ncontrolPlane:\n  localAPIEndpoint:\n    advertiseAddress: \"{{ .AdvertiseAddress }}\"\n    bindPort: {{.APIBindPort}}\n{{- end }}\nnodeRegistration:\n  criSocket: \"unix:///run/containerd/containerd.sock\"\n  kubeletExtraArgs:\n    node-ip: \"{{ .NodeAddress }}\"\n    provider-id: \"kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}\"\n    node-labels: \"{{ .NodeLabels }}\"\ndiscovery:\n  bootstrapToken:\n    apiServerEndpoint: \"{{ .ControlPlaneEndpoint }}\"\n    token: \"{{ .Token }}\"\n    unsafeSkipCAVerification: true\n{{ if .JoinSkipPhases -}}\nskipPhases:\n  {{ range $phase := .JoinSkipPhases -}}\n  - \"{{ $phase }}\"\n  {{- end }}\n{{- end }}\n---\napiVersion: kubelet.config.k8s.io/v1beta1\nkind: KubeletConfiguration\nmetadata:\n  name: config\ncgroupDriver: {{ .CgroupDriver }}\ncgroupRoot: /kubelet\nfailSwapOn: false\n# configure ipv6 addresses in IPv6 mode\n{{ if .IPv6 -}}\naddress: \"::\"\nhealthzBindAddress: \"::\"\n{{- end }}\n# disable disk resource management by default\n# kubelet will see the host disk that the inner container runtime\n# is ultimately backed by and attempt to recover disk space. we don't want that.\nimageGCHighThresholdPercent: 100\nevictionHard:\n  nodefs.available: \"0%\"\n  nodefs.inodesFree: \"0%\"\n  imagefs.available: \"0%\"\n{{if .FeatureGates}}featureGates:\n{{ range $index, $gate := .SortedFeatureGates }}\n  \"{{ $gate.Name }}\": {{ $gate.Value }}\n{{end}}{{end}}\n{{if ne .KubeProxyMode \"none\"}}\n---\napiVersion: kubeproxy.config.k8s.io/v1alpha1\nkind: KubeProxyConfiguration\nmetadata:\n  name: config\nmode: \"{{ .KubeProxyMode }}\"\n{{if .FeatureGates}}featureGates:\n{{ range $index, $gate := .SortedFeatureGates }}\n  \"{{ $gate.Name }}\": {{ $gate.Value }}\n{{end}}{{end}}\niptables:\n  minSyncPeriod: 1s\nconntrack:\n# Skip setting sysctl value \"net.netfilter.nf_conntrack_max\"\n# It is a global variable that affects other namespaces\n  maxPerCore: 0\n# Set sysctl value \"net.netfilter.nf_conntrack_tcp_be_liberal\"\n# for nftables proxy (theoretically for kernels older than 6.1)\n# xref: https://github.com/kubernetes/kubernetes/issues/117924\n{{if and (eq .KubeProxyMode \"nftables\") (not .RootlessProvider)}}\n  tcpBeLiberal: true\n{{end}}\n{{if .RootlessProvider}}\n# Skip setting \"net.netfilter.nf_conntrack_tcp_timeout_established\"\n  tcpEstablishedTimeout: 0s\n# Skip setting \"net.netfilter.nf_conntrack_tcp_timeout_close\"\n  tcpCloseWaitTimeout: 0s\n{{end}}{{end}}\n`\n\n// Config returns a kubeadm config generated from config data, in particular\n// the kubernetes version\nfunc Config(data ConfigData) (config string, err error) {\n\tver, err := version.ParseGeneric(data.KubernetesVersion)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// ensure featureGates is non-nil, as we may add entries\n\tif data.FeatureGates == nil {\n\t\tdata.FeatureGates = make(map[string]bool)\n\t}\n\n\tif data.RootlessProvider {\n\t\tif ver.LessThan(version.MustParseSemantic(\"v1.22.0\")) {\n\t\t\t// rootless kind v0.12.x supports Kubernetes v1.22 with KubeletInUserNamespace gate.\n\t\t\t// rootless kind v0.11.x supports older Kubernetes with fake procfs.\n\t\t\treturn \"\", errors.Errorf(\"version %q is not compatible with rootless provider (hint: kind v0.11.x may work with this version)\", ver)\n\t\t}\n\t\tdata.FeatureGates[\"KubeletInUserNamespace\"] = true\n\t}\n\n\t// assume the latest API version, then fallback if the k8s version is too low\n\ttemplateSource := ConfigTemplateBetaV3\n\tif ver.LessThan(version.MustParseSemantic(\"v1.23.0\")) {\n\t\ttemplateSource = ConfigTemplateBetaV2\n\t}\n\n\tt, err := template.New(\"kubeadm-config\").Parse(templateSource)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse config template\")\n\t}\n\n\t// derive any automatic fields if not supplied\n\tdata.Derive()\n\n\t// Kubeadm has its own feature-gate for dual stack\n\t// we need to enable it for Kubernetes version 1.20 only\n\t// dual-stack is only supported in 1.20+\n\t// TODO: remove this when 1.20 is EOL or we no longer support\n\t// dual-stack for 1.20 in KIND\n\tif ver.LessThan(version.MustParseSemantic(\"v1.21.0\")) &&\n\t\tver.AtLeast(version.MustParseSemantic(\"v1.20.0\")) {\n\t\tdata.KubeadmFeatureGates = make(map[string]bool)\n\t\tdata.KubeadmFeatureGates[\"IPv6DualStack\"] = true\n\t}\n\n\t// before 1.24 kind uses cgroupfs\n\t// after 1.24 kind uses systemd starting in kind v0.13.0\n\t// before kind v0.13.0 kubernetes 1.24 wasn't released yet\n\tif ver.LessThan(version.MustParseSemantic(\"v1.24.0\")) {\n\t\tdata.CgroupDriver = \"cgroupfs\"\n\t}\n\n\t// execute the template\n\tvar buff bytes.Buffer\n\terr = t.Execute(&buff, data)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error executing config template\")\n\t}\n\treturn buff.String(), nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeadm/const.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeadm\n\n// Token defines a dummy, well known token for automating TLS bootstrap process\nconst Token = \"abcdef.0123456789abcdef\"\n\n// ObjectName is the name every generated object will have\n// I.E. `metadata:\\nname: config`\nconst ObjectName = \"config\"\n"
  },
  {
    "path": "pkg/cluster/internal/kubeadm/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeadm contains kubeadm related constants and configuration\npackage kubeadm\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/encode.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeconfig has various functions for working with the kubeconfig.\npackage kubeconfig\n\nimport (\n\t\"bytes\"\n\n\tyaml \"go.yaml.in/yaml/v3\"\n\tkubeyaml \"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// Encode encodes the cfg to yaml\nfunc Encode(cfg *Config) ([]byte, error) {\n\t// NOTE: kubernetes's yaml library doesn't handle inline fields very well\n\t// so we're not using that to marshal\n\tencoded, err := yaml.Marshal(cfg)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to encode KUBECONFIG\")\n\t}\n\n\t// normalize with kubernetes's yaml library\n\t// this is not strictly necessary, but it ensures minimal diffs when\n\t// modifying kubeconfig files, which is nice to have\n\tencoded, err = normYaml(encoded)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to normalize KUBECONFIG encoding\")\n\t}\n\n\treturn encoded, nil\n}\n\n// normYaml round trips yaml bytes through sigs.k8s.io/yaml to normalize them\n// versus other kubernetes ecosystem yaml output\nfunc normYaml(y []byte) ([]byte, error) {\n\tvar unstructured interface{}\n\tif err := kubeyaml.Unmarshal(y, &unstructured); err != nil {\n\t\treturn nil, err\n\t}\n\tencoded, err := kubeyaml.Marshal(&unstructured)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// special case: don't write anything when empty\n\tif bytes.Equal(encoded, []byte(\"{}\\n\")) {\n\t\treturn []byte{}, nil\n\t}\n\treturn encoded, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/encode_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestEncodeRoundtrip(t *testing.T) {\n\tt.Parallel()\n\t// test round tripping a kubeconfig\n\tconst aConfig = `apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kind-kind\ncontexts:\n- context:\n    cluster: kind-kind\n    user: kind-kind\n  name: kind-kind\ncurrent-context: kind-kind\nkind: Config\npreferences: {}\nusers:\n- name: kind-kind\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yup\n`\n\tcfg, err := KINDFromRawKubeadm(aConfig, \"kind\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to decode kubeconfig: %v\", err)\n\t}\n\tencoded, err := Encode(cfg)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to encode kubeconfig: %v\", err)\n\t}\n\tassert.StringEqual(t, aConfig, string(encoded))\n}\n\nfunc TestEncodeEmpty(t *testing.T) {\n\tt.Parallel()\n\tencoded, err := Encode(&Config{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to encode kubeconfig: %v\", err)\n\t}\n\tassert.StringEqual(t, \"\", string(encoded))\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/helpers.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// KINDClusterKey identifies kind clusters in kubeconfig files\nfunc KINDClusterKey(clusterName string) string {\n\treturn \"kind-\" + clusterName\n}\n\n// checkKubeadmExpectations validates that a kubeadm created KUBECONFIG meets\n// our expectations, namely on the number of entries\nfunc checkKubeadmExpectations(cfg *Config) error {\n\tif len(cfg.Clusters) != 1 {\n\t\treturn errors.Errorf(\"kubeadm KUBECONFIG should have one cluster, but read %d\", len(cfg.Clusters))\n\t}\n\tif len(cfg.Users) != 1 {\n\t\treturn errors.Errorf(\"kubeadm KUBECONFIG should have one user, but read %d\", len(cfg.Users))\n\t}\n\tif len(cfg.Contexts) != 1 {\n\t\treturn errors.Errorf(\"kubeadm KUBECONFIG should have one context, but read %d\", len(cfg.Contexts))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/helpers_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestKINDClusterKey(t *testing.T) {\n\tt.Parallel()\n\tassert.StringEqual(t, \"kind-foobar\", KINDClusterKey(\"foobar\"))\n}\n\nfunc TestCheckKubeadmExpectations(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tName        string\n\t\tConfig      *Config\n\t\tExpectError bool\n\t}{\n\t\t{\n\t\t\tName: \"too many of all entries\",\n\t\t\tConfig: &Config{\n\t\t\t\tClusters: make([]NamedCluster, 5),\n\t\t\t\tContexts: make([]NamedContext, 5),\n\t\t\t\tUsers:    make([]NamedUser, 5),\n\t\t\t},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName: \"too many users\",\n\t\t\tConfig: &Config{\n\t\t\t\tClusters: make([]NamedCluster, 1),\n\t\t\t\tContexts: make([]NamedContext, 1),\n\t\t\t\tUsers:    make([]NamedUser, 2),\n\t\t\t},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName: \"too many clusters\",\n\t\t\tConfig: &Config{\n\t\t\t\tClusters: make([]NamedCluster, 2),\n\t\t\t\tContexts: make([]NamedContext, 1),\n\t\t\t\tUsers:    make([]NamedUser, 1),\n\t\t\t},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName: \"too many contexts\",\n\t\t\tConfig: &Config{\n\t\t\t\tClusters: make([]NamedCluster, 1),\n\t\t\t\tContexts: make([]NamedContext, 2),\n\t\t\t\tUsers:    make([]NamedUser, 1),\n\t\t\t},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName: \"just right\",\n\t\t\tConfig: &Config{\n\t\t\t\tClusters: make([]NamedCluster, 1),\n\t\t\t\tContexts: make([]NamedContext, 1),\n\t\t\t\tUsers:    make([]NamedUser, 1),\n\t\t\t},\n\t\t\tExpectError: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tassert.ExpectError(t, tc.ExpectError, checkKubeadmExpectations(tc.Config))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/lock.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// these are from\n// https://github.com/kubernetes/client-go/blob/611184f7c43ae2d520727f01d49620c7ed33412d/tools/clientcmd/loader.go#L439-L440\n\nfunc lockFile(filename string) error {\n\t// Make sure the dir exists before we try to create a lock file.\n\tdir := filepath.Dir(filename)\n\tif _, err := os.Stat(dir); os.IsNotExist(err) {\n\t\tif err = os.MkdirAll(dir, 0755); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tf, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tf.Close()\n\treturn nil\n}\n\nfunc unlockFile(filename string) error {\n\treturn os.Remove(lockName(filename))\n}\n\nfunc lockName(filename string) string {\n\treturn filename + \".lock\"\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/merge.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"os\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// WriteMerged writes a kind kubeconfig (see KINDFromRawKubeadm) into configPath\n// merging with the existing contents if any and setting the current context to\n// the kind config's current context.\nfunc WriteMerged(kindConfig *Config, explicitConfigPath string) error {\n\t// figure out what filepath we should use\n\tconfigPath := pathForMerge(explicitConfigPath, os.Getenv)\n\n\t// lock config file the same as client-go\n\tif err := lockFile(configPath); err != nil {\n\t\treturn errors.Wrap(err, \"failed to lock config file\")\n\t}\n\tdefer func() {\n\t\t_ = unlockFile(configPath)\n\t}()\n\n\t// read in existing\n\texisting, err := read(configPath)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to get kubeconfig to merge\")\n\t}\n\n\t// merge with kind kubeconfig\n\tif err := merge(existing, kindConfig); err != nil {\n\t\treturn err\n\t}\n\n\t// write back out\n\treturn write(existing, configPath)\n}\n\n// merge kind config into an existing config\nfunc merge(existing, kind *Config) error {\n\t// verify assumptions about kubeadm / kind kubeconfigs\n\tif err := checkKubeadmExpectations(kind); err != nil {\n\t\treturn err\n\t}\n\n\t// insert or append cluster entry\n\tshouldAppend := true\n\tfor i := range existing.Clusters {\n\t\tif existing.Clusters[i].Name == kind.Clusters[0].Name {\n\t\t\texisting.Clusters[i] = kind.Clusters[0]\n\t\t\tshouldAppend = false\n\t\t}\n\t}\n\tif shouldAppend {\n\t\texisting.Clusters = append(existing.Clusters, kind.Clusters[0])\n\t}\n\n\t// insert or append user entry\n\tshouldAppend = true\n\tfor i := range existing.Users {\n\t\tif existing.Users[i].Name == kind.Users[0].Name {\n\t\t\texisting.Users[i] = kind.Users[0]\n\t\t\tshouldAppend = false\n\t\t}\n\t}\n\tif shouldAppend {\n\t\texisting.Users = append(existing.Users, kind.Users[0])\n\t}\n\n\t// insert or append context entry\n\tshouldAppend = true\n\tfor i := range existing.Contexts {\n\t\tif existing.Contexts[i].Name == kind.Contexts[0].Name {\n\t\t\texisting.Contexts[i] = kind.Contexts[0]\n\t\t\tshouldAppend = false\n\t\t}\n\t}\n\tif shouldAppend {\n\t\texisting.Contexts = append(existing.Contexts, kind.Contexts[0])\n\t}\n\n\t// set the current context\n\texisting.CurrentContext = kind.CurrentContext\n\n\t// TODO: We should not need this, but it allows broken clients that depend\n\t// on apiVersion and kind to work. Notably the upstream javascript client.\n\t// See: https://github.com/kubernetes-sigs/kind/issues/1242\n\tif len(existing.OtherFields) == 0 {\n\t\t// TODO: Should we be deep-copying? for now we don't need to\n\t\t// and doing so would be a pain (re and de-serialize maybe?) :shrug:\n\t\texisting.OtherFields = kind.OtherFields\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/merge_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestMerge(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tName        string\n\t\tExisting    *Config\n\t\tKind        *Config\n\t\tExpected    *Config\n\t\tExpectError bool\n\t}{\n\t\t{\n\t\t\tName:        \"bad kind config\",\n\t\t\tExisting:    &Config{},\n\t\t\tKind:        &Config{},\n\t\t\tExpected:    &Config{},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName:     \"empty existing\",\n\t\t\tExisting: &Config{},\n\t\t\tKind: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tName: \"replace existing\",\n\t\t\tExisting: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t\tCluster: Cluster{\n\t\t\t\t\t\t\tServer: \"foo\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tKind: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tName: \"add to existing\",\n\t\t\tExisting: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t\tCluster: Cluster{\n\t\t\t\t\t\t\tServer: \"foo\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tKind: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t\tCluster: Cluster{\n\t\t\t\t\t\t\tServer: \"foo\",\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\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpectError: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\terr := merge(tc.Existing, tc.Kind)\n\t\t\tassert.ExpectError(t, tc.ExpectError, err)\n\t\t\tif !tc.ExpectError && !reflect.DeepEqual(tc.Existing, tc.Expected) {\n\t\t\t\tt.Errorf(\"Merged Config did not equal Expected\")\n\t\t\t\tt.Errorf(\"Expected: %+v\", tc.Expected)\n\t\t\t\tt.Errorf(\"Actual: %+v\", tc.Existing)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestWriteMerged(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"normal merge\", testWriteMergedNormal)\n\tt.Run(\"bad kind config\", testWriteMergedBogusConfig)\n\tt.Run(\"merge into non-existent file\", testWriteMergedNoExistingFile)\n}\n\nfunc testWriteMergedNormal(t *testing.T) {\n\tt.Parallel()\n\tdir, err := os.MkdirTemp(\"\", \"kind-testwritemerged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %d\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\t// create an existing kubeconfig\n\tconst existingConfig = `clusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kind-foo\ncontexts:\n- context:\n    cluster: kind-foo\n    user: kind-foo\n  name: kind-foo\ncurrent-context: kind-foo\nkind: Config\napiVersion: v1\npreferences: {}\nusers:\n- name: kind-foo\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\texistingConfigPath := filepath.Join(dir, \"existing-kubeconfig\")\n\tif err := os.WriteFile(existingConfigPath, []byte(existingConfig), os.ModePerm); err != nil {\n\t\tt.Fatalf(\"Failed to create existing kubeconfig: %d\", err)\n\t}\n\n\tkindConfig := &Config{\n\t\tClusters: []NamedCluster{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tCluster: Cluster{\n\t\t\t\t\tServer: \"https://127.0.0.1:6443\",\n\t\t\t\t\tOtherFields: map[string]interface{}{\n\t\t\t\t\t\t\"certificate-authority-data\": \"definitelyacert\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tContexts: []NamedContext{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tContext: Context{\n\t\t\t\t\tUser:    \"kind-kind\",\n\t\t\t\t\tCluster: \"kind-kind\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tUsers: []NamedUser{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tUser: map[string]interface{}{\n\t\t\t\t\t\"client-certificate-data\": \"seemslegit\",\n\t\t\t\t\t\"client-key-data\":         \"yep\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tCurrentContext: \"kind-kind\",\n\t\tOtherFields: map[string]interface{}{\n\t\t\t\"apiVersion\":  \"v1\",\n\t\t\t\"kind\":        \"Config\",\n\t\t\t\"preferences\": map[string]interface{}{},\n\t\t},\n\t}\n\t// ensure that we can write this merged config\n\tif err := WriteMerged(kindConfig, existingConfigPath); err != nil {\n\t\tt.Fatalf(\"Failed to write merged kubeconfig: %v\", err)\n\t}\n\n\t// ensure the output matches expected\n\tf, err := os.Open(existingConfigPath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open merged kubeconfig: %v\", err)\n\t}\n\tcontents, err := io.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read merged kubeconfig: %v\", err)\n\t}\n\texpected := `apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kind-foo\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://127.0.0.1:6443\n  name: kind-kind\ncontexts:\n- context:\n    cluster: kind-foo\n    user: kind-foo\n  name: kind-foo\n- context:\n    cluster: kind-kind\n    user: kind-kind\n  name: kind-kind\ncurrent-context: kind-kind\nkind: Config\npreferences: {}\nusers:\n- name: kind-foo\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n- name: kind-kind\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\tassert.StringEqual(t, expected, string(contents))\n}\n\nfunc testWriteMergedBogusConfig(t *testing.T) {\n\tt.Parallel()\n\tdir, err := os.MkdirTemp(\"\", \"kind-testwritemerged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %d\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\terr = WriteMerged(&Config{}, filepath.Join(dir, \"bogus\"))\n\tassert.ExpectError(t, true, err)\n}\n\nfunc testWriteMergedNoExistingFile(t *testing.T) {\n\tt.Parallel()\n\tdir, err := os.MkdirTemp(\"\", \"kind-testwritemerged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %d\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\tkindConfig := &Config{\n\t\tClusters: []NamedCluster{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tCluster: Cluster{\n\t\t\t\t\tServer: \"https://127.0.0.1:6443\",\n\t\t\t\t\tOtherFields: map[string]interface{}{\n\t\t\t\t\t\t\"certificate-authority-data\": \"definitelyacert\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tContexts: []NamedContext{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tContext: Context{\n\t\t\t\t\tUser:    \"kind-kind\",\n\t\t\t\t\tCluster: \"kind-kind\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tUsers: []NamedUser{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tUser: map[string]interface{}{\n\t\t\t\t\t\"client-certificate-data\": \"seemslegit\",\n\t\t\t\t\t\"client-key-data\":         \"yep\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tCurrentContext: \"kind-kind\",\n\t\tOtherFields: map[string]interface{}{\n\t\t\t\"apiVersion\":  \"v1\",\n\t\t\t\"kind\":        \"Config\",\n\t\t\t\"preferences\": map[string]interface{}{},\n\t\t},\n\t}\n\n\tnonExistentPath := filepath.Join(dir, \"bogus\", \"extra-bogus\")\n\terr = WriteMerged(kindConfig, nonExistentPath)\n\tassert.ExpectError(t, false, err)\n\n\t// ensure the output matches expected\n\tf, err := os.Open(nonExistentPath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open merged kubeconfig: %v\", err)\n\t}\n\tcontents, err := io.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read merged kubeconfig: %v\", err)\n\t}\n\texpected := `apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://127.0.0.1:6443\n  name: kind-kind\ncontexts:\n- context:\n    cluster: kind-kind\n    user: kind-kind\n  name: kind-kind\ncurrent-context: kind-kind\nkind: Config\npreferences: {}\nusers:\n- name: kind-kind\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\tassert.StringEqual(t, expected, string(contents))\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/paths.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"runtime\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\nconst kubeconfigEnv = \"KUBECONFIG\"\n\n/*\npaths returns the list of paths to be considered for kubeconfig files\nwhere explicitPath is the value of --kubeconfig\n\n# Logic based on kubectl\n\nhttps://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands\n\n- If the --kubeconfig flag is set, then only that file is loaded. The flag may only be set once and no merging takes place.\n\n- If $KUBECONFIG environment variable is set, then it is used as a list of paths (normal path delimiting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. - If no files in the chain exist, then it creates the last file in the list.\n\n- Otherwise, ${HOME}/.kube/config is used and no merging takes place.\n*/\nfunc paths(explicitPath string, getEnv func(string) string) []string {\n\tif explicitPath != \"\" {\n\t\treturn []string{explicitPath}\n\t}\n\n\tpaths := discardEmptyAndDuplicates(\n\t\tfilepath.SplitList(getEnv(kubeconfigEnv)),\n\t)\n\tif len(paths) != 0 {\n\t\treturn paths\n\t}\n\n\treturn []string{path.Join(homeDir(runtime.GOOS, getEnv), \".kube\", \"config\")}\n}\n\n// pathForMerge returns the file that kubectl would merge into\nfunc pathForMerge(explicitPath string, getEnv func(string) string) string {\n\t// find the first file that exists\n\tp := paths(explicitPath, getEnv)\n\tif len(p) == 1 {\n\t\treturn p[0]\n\t}\n\tfor _, filename := range p {\n\t\tif fileExists(filename) {\n\t\t\treturn filename\n\t\t}\n\t}\n\t// otherwise the last file\n\treturn p[len(p)-1]\n}\n\nfunc fileExists(filename string) bool {\n\tinfo, err := os.Stat(filename)\n\tif os.IsNotExist(err) {\n\t\treturn false\n\t}\n\treturn !info.IsDir()\n}\n\nfunc discardEmptyAndDuplicates(paths []string) []string {\n\tseen := sets.NewString()\n\tkept := 0\n\tfor _, p := range paths {\n\t\tif p != \"\" && !seen.Has(p) {\n\t\t\tpaths[kept] = p\n\t\t\tkept++\n\t\t\tseen.Insert(p)\n\t\t}\n\t}\n\treturn paths[:kept]\n}\n\n// homeDir returns the home directory for the current user.\n// On Windows:\n// 1. the first of %HOME%, %HOMEDRIVE%%HOMEPATH%, %USERPROFILE% containing a `.kube\\config` file is returned.\n// 2. if none of those locations contain a `.kube\\config` file, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists and is writeable is returned.\n// 3. if none of those locations are writeable, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists is returned.\n// 4. if none of those locations exists, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that is set is returned.\n// NOTE this is from client-go. Rather than pull in client-go for this one\n// standalone method, we have a fork here.\n// https://github.com/kubernetes/client-go/blob/6d7018244d72350e2e8c4a19ccdbe4c8083a9143/util/homedir/homedir.go\n// We've modified this to require injecting os.Getenv and runtime.GOOS as a dependencies for testing purposes\nfunc homeDir(GOOS string, getEnv func(string) string) string {\n\tif GOOS == \"windows\" {\n\t\thome := getEnv(\"HOME\")\n\t\thomeDriveHomePath := \"\"\n\t\tif homeDrive, homePath := getEnv(\"HOMEDRIVE\"), getEnv(\"HOMEPATH\"); len(homeDrive) > 0 && len(homePath) > 0 {\n\t\t\thomeDriveHomePath = homeDrive + homePath\n\t\t}\n\t\tuserProfile := getEnv(\"USERPROFILE\")\n\n\t\t// Return first of %HOME%, %HOMEDRIVE%/%HOMEPATH%, %USERPROFILE% that contains a `.kube\\config` file.\n\t\t// %HOMEDRIVE%/%HOMEPATH% is preferred over %USERPROFILE% for backwards-compatibility.\n\t\tfor _, p := range []string{home, homeDriveHomePath, userProfile} {\n\t\t\tif len(p) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, err := os.Stat(filepath.Join(p, \".kube\", \"config\")); err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn p\n\t\t}\n\n\t\tfirstSetPath := \"\"\n\t\tfirstExistingPath := \"\"\n\n\t\t// Prefer %USERPROFILE% over %HOMEDRIVE%/%HOMEPATH% for compatibility with other auth-writing tools\n\t\tfor _, p := range []string{home, userProfile, homeDriveHomePath} {\n\t\t\tif len(p) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(firstSetPath) == 0 {\n\t\t\t\t// remember the first path that is set\n\t\t\t\tfirstSetPath = p\n\t\t\t}\n\t\t\tinfo, err := os.Stat(p)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(firstExistingPath) == 0 {\n\t\t\t\t// remember the first path that exists\n\t\t\t\tfirstExistingPath = p\n\t\t\t}\n\t\t\tif info.IsDir() && info.Mode().Perm()&(1<<(uint(7))) != 0 {\n\t\t\t\t// return first path that is writeable\n\t\t\t\treturn p\n\t\t\t}\n\t\t}\n\n\t\t// If none are writeable, return first location that exists\n\t\tif len(firstExistingPath) > 0 {\n\t\t\treturn firstExistingPath\n\t\t}\n\n\t\t// If none exist, return first location that is set\n\t\tif len(firstSetPath) > 0 {\n\t\t\treturn firstSetPath\n\t\t}\n\n\t\t// We've got nothing\n\t\treturn \"\"\n\t}\n\treturn getEnv(\"HOME\")\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/paths_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/fs\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestPaths(t *testing.T) {\n\tt.Parallel()\n\t// test explicit kubeconfig\n\tt.Run(\"explicit path\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\texplicitPath := \"foo\"\n\t\tresult := paths(explicitPath, func(s string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"KUBECONFIG\": strings.Join([]string{\"/foo\", \"/bar\", \"\", \"/foo\", \"/bar\"}, string(filepath.ListSeparator)),\n\t\t\t\t\"HOME\":       \"/home\",\n\t\t\t}[s]\n\t\t})\n\t\texpected := []string{explicitPath}\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n\tt.Run(\"KUBECONFIG list\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tresult := paths(\"\", func(s string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"KUBECONFIG\": strings.Join([]string{\"/foo\", \"/bar\", \"\", \"/foo\", \"/bar\"}, string(filepath.ListSeparator)),\n\t\t\t\t\"HOME\":       \"/home\",\n\t\t\t}[s]\n\t\t})\n\t\texpected := []string{\"/foo\", \"/bar\"}\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n\tt.Run(\"$HOME/.kube/config\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tresult := paths(\"\", func(s string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"HOME\": \"/home\",\n\t\t\t}[s]\n\t\t})\n\t\texpected := []string{\"/home/.kube/config\"}\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n}\n\nfunc TestPathForMerge(t *testing.T) {\n\tt.Parallel()\n\t// create a directory structure with some files to be \"kubeconfigs\"\n\tdir, err := fs.TempDir(\"\", \"kind-testwritemerged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %v\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\t// create a fake homedir\n\thomeDir := filepath.Join(dir, \"fake-home\")\n\tif err := os.Mkdir(homeDir, os.ModePerm); err != nil {\n\t\tt.Fatalf(\"failed to create fake home dir: %v\", err)\n\t}\n\n\t// create some fake KUBECONFIG files\n\tfakeKubeconfigs := []string{}\n\tfor _, partialPath := range []string{\"foo\", \"bar\", \"baz\"} {\n\t\tp := filepath.Join(dir, partialPath)\n\t\tfakeKubeconfigs = append(fakeKubeconfigs, p)\n\t\tf, err := os.Create(p)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create fake kubeconfig file: %v\", err)\n\t\t}\n\t\tf.Close()\n\t}\n\n\t// test explicit kubeconfig\n\tt.Run(\"explicit path\", func(t *testing.T) {\n\t\texplicitPath := \"foo\"\n\t\tresult := pathForMerge(explicitPath, func(s string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"KUBECONFIG\": strings.Join([]string{\"/foo\", \"/bar\", \"\", \"/foo\", \"/bar\"}, string(filepath.ListSeparator)),\n\t\t\t\t\"HOME\":       \"/home\",\n\t\t\t}[s]\n\t\t})\n\t\texpected := explicitPath\n\t\tassert.StringEqual(t, expected, result)\n\t})\n\tt.Run(\"KUBECONFIG list\", func(t *testing.T) {\n\t\tresult := pathForMerge(\"\", func(s string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"KUBECONFIG\": strings.Join(fakeKubeconfigs, string(filepath.ListSeparator)),\n\t\t\t}[s]\n\t\t})\n\t\texpected := fakeKubeconfigs[0]\n\t\tassert.StringEqual(t, expected, result)\n\t})\n\tt.Run(\"KUBECONFIG select last if none exist\", func(t *testing.T) {\n\t\tkubeconfigEnvValue := strings.Join([]string{\"/bogus/path\", \"/bogus/path/two\"}, string(filepath.ListSeparator))\n\t\tresult := pathForMerge(\"\", func(s string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"KUBECONFIG\": kubeconfigEnvValue,\n\t\t\t}[s]\n\t\t})\n\t\texpected := \"/bogus/path/two\"\n\t\tassert.StringEqual(t, expected, result)\n\t})\n}\n\nfunc TestHomeDir(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"windows HOME with .kube/config\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t// create a directory structure with a \"kubeconfigs\"\n\t\tdir, err := fs.TempDir(\"\", \"kind-testwritemerged\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create tempdir: %v\", err)\n\t\t}\n\t\tdefer os.RemoveAll(dir)\n\n\t\t// create the fake kubeconfig\n\t\tfakeHomeDir := path.Join(dir, \"fake-home\")\n\t\tfakeKubeConfig := path.Join(fakeHomeDir, \".kube\", \"config\")\n\t\tif err := os.MkdirAll(path.Dir(fakeKubeConfig), os.ModePerm); err != nil {\n\t\t\tt.Fatalf(\"Failed to create fake kubeconfig dir: %v\", err)\n\t\t}\n\t\tf, err := os.Create(fakeKubeConfig)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create tempdir: %v\", err)\n\t\t}\n\t\tf.Close()\n\n\t\t// this should return the fake kubeconfig\n\t\tresult := homeDir(\"windows\", func(e string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"HOME\":      fakeHomeDir,\n\t\t\t\t\"HOMEDRIVE\": \"ZZ:\",\n\t\t\t\t\"HOMEPATH\":  `ZZ:\\Users\\fake-user-zzz`,\n\t\t\t}[e]\n\t\t})\n\t\tassert.StringEqual(t, fakeHomeDir, result)\n\t})\n\tt.Run(\"windows HOME without .kube/config\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t// create a fake home dir\n\t\tfakeHomeDir, err := fs.TempDir(\"\", \"kind-testwritemerged\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create tempdir: %v\", err)\n\t\t}\n\t\tdefer os.RemoveAll(fakeHomeDir)\n\n\t\t// this should return the fake kubeconfig\n\t\tresult := homeDir(\"windows\", func(e string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"HOME\":      fakeHomeDir,\n\t\t\t\t\"HOMEDRIVE\": filepath.VolumeName(fakeHomeDir),\n\t\t\t\t\"HOMEPATH\":  path.Join(\"Users\", \"fake-user-zzz\"),\n\t\t\t}[e]\n\t\t})\n\t\tassert.StringEqual(t, fakeHomeDir, result)\n\t})\n\tt.Run(\"windows HOME none exist\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t// this should return the fake kubeconfig\n\t\tresult := homeDir(\"windows\", func(e string) string {\n\t\t\treturn map[string]string{\n\t\t\t\t\"HOME\":      \"Z:/faaaaake\",\n\t\t\t\t\"HOMEDRIVE\": \"Z:/\",\n\t\t\t\t\"HOMEPATH\":  path.Join(\"Users\", \"fake-user-zzz\"),\n\t\t\t}[e]\n\t\t})\n\t\tassert.StringEqual(t, \"Z:/faaaaake\", result)\n\t})\n\tt.Run(\"windows no path\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t// this should return the fake kubeconfig\n\t\tresult := homeDir(\"windows\", func(e string) string {\n\t\t\treturn \"\"\n\t\t})\n\t\tassert.StringEqual(t, \"\", result)\n\t})\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/read.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\tyaml \"go.yaml.in/yaml/v3\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// KINDFromRawKubeadm returns a kind kubeconfig derived from the raw kubeadm kubeconfig,\n// the kind clusterName, and the server.\n// server is ignored if unset.\nfunc KINDFromRawKubeadm(rawKubeadmKubeConfig, clusterName, server string) (*Config, error) {\n\tcfg := &Config{}\n\tif err := yaml.Unmarshal([]byte(rawKubeadmKubeConfig), cfg); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// verify assumptions about kubeadm kubeconfigs\n\tif err := checkKubeadmExpectations(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// compute unique kubeconfig key for this cluster\n\tkey := KINDClusterKey(clusterName)\n\n\t// use the unique key for all named references\n\tcfg.Clusters[0].Name = key\n\tcfg.Users[0].Name = key\n\tcfg.Contexts[0].Name = key\n\tcfg.Contexts[0].Context.User = key\n\tcfg.Contexts[0].Context.Cluster = key\n\tcfg.CurrentContext = key\n\n\t// patch server field if server was set\n\tif server != \"\" {\n\t\tcfg.Clusters[0].Cluster.Server = server\n\t}\n\n\treturn cfg, nil\n}\n\n// read loads a KUBECONFIG file from configPath\nfunc read(configPath string) (*Config, error) {\n\t// try to open, return default if no such file\n\tf, err := os.Open(configPath)\n\tif os.IsNotExist(err) {\n\t\treturn &Config{}, nil\n\t} else if err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\n\t// otherwise read in and deserialize\n\tcfg := &Config{}\n\trawExisting, err := io.ReadAll(f)\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\tif err := yaml.Unmarshal(rawExisting, cfg); err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\n\treturn cfg, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/read_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestKINDFromRawKubeadm(t *testing.T) {\n\tt.Parallel()\n\t// test that a bogus config is caught\n\tt.Run(\"bad config\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t_, err := KINDFromRawKubeadm(\"\t\", \"kind\", \"\")\n\t\tassert.ExpectError(t, true, err)\n\t})\n\t// test reading a legitimate kubeadm config and converting it to a kind config\n\tt.Run(\"valid config\", func(t *testing.T) {\n\t\tconst rawConfig = `apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kind\ncontexts:\n- context:\n    cluster: kind\n    user: kubernetes-admin\n  name: kubernetes-admin@kind\ncurrent-context: kubernetes-admin@kind\nkind: Config\npreferences: {}\nusers:\n- name: kubernetes-admin\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\t\tserver := \"https://127.0.0.1:6443\"\n\t\texpected := &Config{\n\t\t\tClusters: []NamedCluster{\n\t\t\t\t{\n\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\tCluster: Cluster{\n\t\t\t\t\t\tServer: server,\n\t\t\t\t\t\tOtherFields: map[string]interface{}{\n\t\t\t\t\t\t\t\"certificate-authority-data\": \"definitelyacert\",\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\tContexts: []NamedContext{\n\t\t\t\t{\n\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\tContext: Context{\n\t\t\t\t\t\tUser:    \"kind-kind\",\n\t\t\t\t\t\tCluster: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tUsers: []NamedUser{\n\t\t\t\t{\n\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\tUser: map[string]interface{}{\n\t\t\t\t\t\t\"client-certificate-data\": \"seemslegit\",\n\t\t\t\t\t\t\"client-key-data\":         \"yep\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tCurrentContext: \"kind-kind\",\n\t\t\tOtherFields: map[string]interface{}{\n\t\t\t\t\"apiVersion\":  \"v1\",\n\t\t\t\t\"kind\":        \"Config\",\n\t\t\t\t\"preferences\": map[string]interface{}{},\n\t\t\t},\n\t\t}\n\t\tcfg, err := KINDFromRawKubeadm(rawConfig, \"kind\", server)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to decode kubeconfig: %v\", err)\n\t\t}\n\t\tif !reflect.DeepEqual(cfg, expected) {\n\t\t\tt.Errorf(\"Read Config did not equal Expected\")\n\t\t\tt.Errorf(\"Expected: %+v\", expected)\n\t\t\tt.Errorf(\"Actual: %+v\", cfg)\n\t\t\tt.Errorf(\"type: %s\", reflect.TypeOf(cfg.OtherFields[\"preferences\"]))\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/remove.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"os\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// RemoveKIND removes the kind cluster kindClusterName from the KUBECONFIG\n// files at configPaths\nfunc RemoveKIND(kindClusterName string, explicitPath string) error {\n\t// remove kind from each if present\n\tfor _, configPath := range paths(explicitPath, os.Getenv) {\n\t\tif err := func(configPath string) error {\n\t\t\t// lock before modifying\n\t\t\tif err := lockFile(configPath); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"failed to lock config file\")\n\t\t\t}\n\t\t\tdefer func(configPath string) {\n\t\t\t\t_ = unlockFile(configPath)\n\t\t\t}(configPath)\n\n\t\t\t// read in existing\n\t\t\texisting, err := read(configPath)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"failed to read kubeconfig to remove KIND entry\")\n\t\t\t}\n\n\t\t\t// remove the kind cluster from the config\n\t\t\tif remove(existing, kindClusterName) {\n\t\t\t\t// write out the updated config if we modified anything\n\t\t\t\tif err := write(existing, configPath); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}(configPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// remove drops kindClusterName entries from the cfg\nfunc remove(cfg *Config, kindClusterName string) bool {\n\tmutated := false\n\n\t// get kind cluster identifier\n\tkey := KINDClusterKey(kindClusterName)\n\n\t// filter out kind cluster from clusters\n\tkept := 0\n\tfor _, c := range cfg.Clusters {\n\t\tif c.Name != key {\n\t\t\tcfg.Clusters[kept] = c\n\t\t\tkept++\n\t\t} else {\n\t\t\tmutated = true\n\t\t}\n\t}\n\tcfg.Clusters = cfg.Clusters[:kept]\n\n\t// filter out kind cluster from users\n\tkept = 0\n\tfor _, u := range cfg.Users {\n\t\tif u.Name != key {\n\t\t\tcfg.Users[kept] = u\n\t\t\tkept++\n\t\t} else {\n\t\t\tmutated = true\n\t\t}\n\t}\n\tcfg.Users = cfg.Users[:kept]\n\n\t// filter out kind cluster from contexts\n\tkept = 0\n\tfor _, c := range cfg.Contexts {\n\t\tif c.Name != key {\n\t\t\tcfg.Contexts[kept] = c\n\t\t\tkept++\n\t\t} else {\n\t\t\tmutated = true\n\t\t}\n\t}\n\tcfg.Contexts = cfg.Contexts[:kept]\n\n\t// unset current context if it points to this cluster\n\tif cfg.CurrentContext == key {\n\t\tcfg.CurrentContext = \"\"\n\t\tmutated = true\n\t}\n\n\treturn mutated\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/remove_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestRemove(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tName           string\n\t\tExisting       *Config\n\t\tClusterName    string\n\t\tExpected       *Config\n\t\tExpectModified bool\n\t}{\n\t\t{\n\t\t\tName:           \"empty config\",\n\t\t\tExisting:       &Config{},\n\t\t\tClusterName:    \"foo\",\n\t\t\tExpected:       &Config{},\n\t\t\tExpectModified: false,\n\t\t},\n\t\t{\n\t\t\tName: \"remove kind from only kind\",\n\t\t\tExisting: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tClusterName: \"kind\",\n\t\t\tExpected: &Config{\n\t\t\t\tClusters: []NamedCluster{},\n\t\t\t\tUsers:    []NamedUser{},\n\t\t\t\tContexts: []NamedContext{},\n\t\t\t},\n\t\t\tExpectModified: true,\n\t\t},\n\t\t{\n\t\t\tName: \"remove kind, leave kops\",\n\t\t\tExisting: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t\tCluster: Cluster{\n\t\t\t\t\t\t\tServer: \"foo\",\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\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kind-kind\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tCurrentContext: \"kind-kind\",\n\t\t\t},\n\t\t\tClusterName: \"kind\",\n\t\t\tExpected: &Config{\n\t\t\t\tClusters: []NamedCluster{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t\tCluster: Cluster{\n\t\t\t\t\t\t\tServer: \"foo\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tUsers: []NamedUser{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContexts: []NamedContext{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"kops-blah\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tCurrentContext: \"\",\n\t\t\t},\n\t\t\tExpectModified: true,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tmodified := remove(tc.Existing, tc.ClusterName)\n\t\t\tif modified != tc.ExpectModified {\n\t\t\t\tif tc.ExpectModified {\n\t\t\t\t\tt.Errorf(\"Expected config to be modified but got modified == false\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"Expected config to be modified but got modified == true\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.DeepEqual(t, tc.Expected, tc.Existing)\n\t\t})\n\t}\n}\n\nfunc TestRemoveKIND(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"only kind\", testRemoveKINDTrivial)\n\tt.Run(\"leave another cluster\", testRemoveKINDKeepOther)\n}\n\nfunc testRemoveKINDTrivial(t *testing.T) {\n\tt.Parallel()\n\tdir, err := os.MkdirTemp(\"\", \"kind-testremovekind\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %d\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\t// create an existing kubeconfig\n\tconst existingConfig = `clusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kind-foo\ncontexts:\n- context:\n    cluster: kind-foo\n    user: kind-foo\n  name: kind-foo\ncurrent-context: kind-foo\nkind: Config\napiVersion: v1\npreferences: {}\nusers:\n- name: kind-foo\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\texistingConfigPath := filepath.Join(dir, \"existing-kubeconfig\")\n\tif err := os.WriteFile(existingConfigPath, []byte(existingConfig), os.ModePerm); err != nil {\n\t\tt.Fatalf(\"Failed to create existing kubeconfig: %d\", err)\n\t}\n\n\t// ensure that we can write this merged config\n\tif err := RemoveKIND(\"foo\", existingConfigPath); err != nil {\n\t\tt.Fatalf(\"Failed to remove kind from kubeconfig: %v\", err)\n\t}\n\n\t// ensure the output matches expected\n\tf, err := os.Open(existingConfigPath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open merged kubeconfig: %v\", err)\n\t}\n\tcontents, err := io.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read merged kubeconfig: %v\", err)\n\t}\n\texpected := `apiVersion: v1\nkind: Config\npreferences: {}\n`\n\tassert.StringEqual(t, expected, string(contents))\n}\n\nfunc testRemoveKINDKeepOther(t *testing.T) {\n\t// tests removing a kind cluster but keeping another cluster\n\tt.Parallel()\n\tdir, err := os.MkdirTemp(\"\", \"kind-testremovekind\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %d\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\t// create an existing kubeconfig\n\tconst existingConfig = `clusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kind-foo\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kops-foo\ncontexts:\n- context:\n    cluster: kind-foo\n    user: kind-foo\n  name: kind-foo\n- context:\n    cluster: kops-foo\n    user: kops-foo\n  name: kops-foo\ncurrent-context: kops-foo\nkind: Config\napiVersion: v1\npreferences: {}\nusers:\n- name: kind-foo\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n- name: kops-foo\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\texistingConfigPath := filepath.Join(dir, \"existing-kubeconfig\")\n\tif err := os.WriteFile(existingConfigPath, []byte(existingConfig), os.ModePerm); err != nil {\n\t\tt.Fatalf(\"Failed to create existing kubeconfig: %d\", err)\n\t}\n\n\t// ensure that we can write this merged config\n\tif err := RemoveKIND(\"foo\", existingConfigPath); err != nil {\n\t\tt.Fatalf(\"Failed to remove kind from kubeconfig: %v\", err)\n\t}\n\n\t// ensure the output matches expected\n\tf, err := os.Open(existingConfigPath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open merged kubeconfig: %v\", err)\n\t}\n\tcontents, err := io.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read merged kubeconfig: %v\", err)\n\t}\n\texpected := `apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://192.168.9.4:6443\n  name: kops-foo\ncontexts:\n- context:\n    cluster: kops-foo\n    user: kops-foo\n  name: kops-foo\ncurrent-context: kops-foo\nkind: Config\npreferences: {}\nusers:\n- name: kops-foo\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\tassert.StringEqual(t, expected, string(contents))\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/types.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\n/*\nNOTE: all of these types are based on the upstream v1 types from client-go\nhttps://github.com/kubernetes/client-go/blob/0bdba2f9188006fc64057c2f6d82a0f9ee0ee422/tools/clientcmd/api/v1/types.go\n\nWe've forked them to:\n- remove types and fields kind does not need to inspect / modify\n- generically support fields kind doesn't inspect / modify using yaml.v3\n- have clearer names (AuthInfo -> User)\n*/\n\n// Config represents a KUBECONFIG, with the fields kind is likely to use\n// Other fields are handled as unstructured data purely read for writing back\n// to disk via the OtherFields field\ntype Config struct {\n\t// Clusters is a map of referenceable names to cluster configs\n\tClusters []NamedCluster `yaml:\"clusters,omitempty\"`\n\t// Users is a map of referenceable names to user configs\n\tUsers []NamedUser `yaml:\"users,omitempty\"`\n\t// Contexts is a map of referenceable names to context configs\n\tContexts []NamedContext `yaml:\"contexts,omitempty\"`\n\t// CurrentContext is the name of the context that you would like to use by default\n\tCurrentContext string `yaml:\"current-context,omitempty\"`\n\t// OtherFields contains fields kind does not inspect or modify, these are\n\t// read purely for writing back\n\tOtherFields map[string]interface{} `yaml:\",inline,omitempty\"`\n}\n\n// NamedCluster relates nicknames to cluster information\ntype NamedCluster struct {\n\t// Name is the nickname for this Cluster\n\tName string `yaml:\"name\"`\n\t// Cluster holds the cluster information\n\tCluster Cluster `yaml:\"cluster\"`\n}\n\n// Cluster contains information about how to communicate with a kubernetes cluster\ntype Cluster struct {\n\t// Server is the address of the kubernetes cluster (https://hostname:port).\n\tServer string `yaml:\"server,omitempty\"`\n\t// OtherFields contains fields kind does not inspect or modify, these are\n\t// read purely for writing back\n\tOtherFields map[string]interface{} `yaml:\",inline,omitempty\"`\n}\n\n// NamedUser relates nicknames to user information\ntype NamedUser struct {\n\t// Name is the nickname for this User\n\tName string `yaml:\"name\"`\n\t// User holds the user information\n\t// We do not touch this and merely write it back\n\tUser map[string]interface{} `yaml:\"user\"`\n}\n\n// NamedContext relates nicknames to context information\ntype NamedContext struct {\n\t// Name is the nickname for this Context\n\tName string `yaml:\"name\"`\n\t// Context holds the context information\n\tContext Context `yaml:\"context\"`\n}\n\n// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)\ntype Context struct {\n\t// Cluster is the name of the cluster for this context\n\tCluster string `yaml:\"cluster\"`\n\t// User is the name of the User for this context\n\tUser string `yaml:\"user\"`\n\t// OtherFields contains fields kind does not inspect or modify, these are\n\t// read purely for writing back\n\tOtherFields map[string]interface{} `yaml:\",inline,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/write.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// write writes cfg to configPath\n// it will ensure the directories in the path if necessary\nfunc write(cfg *Config, configPath string) error {\n\tencoded, err := Encode(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// NOTE: 0755 / 0600 are to match client-go\n\tdir := filepath.Dir(configPath)\n\tif _, err := os.Stat(dir); os.IsNotExist(err) {\n\t\tif err = os.MkdirAll(dir, 0755); err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to create directory for KUBECONFIG\")\n\t\t}\n\t}\n\tif err := os.WriteFile(configPath, encoded, 0600); err != nil {\n\t\treturn errors.Wrap(err, \"failed to write KUBECONFIG\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/internal/kubeconfig/write_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kubeconfig\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestWrite(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"non-existent file\", testWriteNoExistingFile)\n}\n\nfunc testWriteNoExistingFile(t *testing.T) {\n\tt.Parallel()\n\tdir, err := os.MkdirTemp(\"\", \"kind-testwritemerged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempdir: %d\", err)\n\t}\n\tdefer os.RemoveAll(dir)\n\n\tkindConfig := &Config{\n\t\tClusters: []NamedCluster{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tCluster: Cluster{\n\t\t\t\t\tServer: \"https://127.0.0.1:6443\",\n\t\t\t\t\tOtherFields: map[string]interface{}{\n\t\t\t\t\t\t\"certificate-authority-data\": \"definitelyacert\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tContexts: []NamedContext{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tContext: Context{\n\t\t\t\t\tUser:    \"kind-kind\",\n\t\t\t\t\tCluster: \"kind-kind\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tUsers: []NamedUser{\n\t\t\t{\n\t\t\t\tName: \"kind-kind\",\n\t\t\t\tUser: map[string]interface{}{\n\t\t\t\t\t\"client-certificate-data\": \"seemslegit\",\n\t\t\t\t\t\"client-key-data\":         \"yep\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tCurrentContext: \"kind-kind\",\n\t\tOtherFields: map[string]interface{}{\n\t\t\t\"apiVersion\":  \"v1\",\n\t\t\t\"kind\":        \"Config\",\n\t\t\t\"preferences\": map[string]interface{}{},\n\t\t},\n\t}\n\n\tnonExistentPath := filepath.Join(dir, \"bogus\", \"extra-bogus\")\n\terr = write(kindConfig, nonExistentPath)\n\tassert.ExpectError(t, false, err)\n\n\t// ensure the output matches expected\n\tf, err := os.Open(nonExistentPath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to open merged kubeconfig: %v\", err)\n\t}\n\tcontents, err := io.ReadAll(f)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read merged kubeconfig: %v\", err)\n\t}\n\texpected := `apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: definitelyacert\n    server: https://127.0.0.1:6443\n  name: kind-kind\ncontexts:\n- context:\n    cluster: kind-kind\n    user: kind-kind\n  name: kind-kind\ncurrent-context: kind-kind\nkind: Config\npreferences: {}\nusers:\n- name: kind-kind\n  user:\n    client-certificate-data: seemslegit\n    client-key-data: yep\n`\n\tassert.StringEqual(t, expected, string(contents))\n}\n"
  },
  {
    "path": "pkg/cluster/internal/kubeconfig/kubeconfig.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeconfig provides utilities kind uses internally to manage\n// kind cluster kubeconfigs\npackage kubeconfig\n\nimport (\n\t\"bytes\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t// this package has slightly more generic kubeconfig helpers\n\t// and minimal dependencies on the rest of kind\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig/internal/kubeconfig\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n)\n\n// Export exports the kubeconfig given the cluster context and a path to write it to\n// This will always be an external kubeconfig\nfunc Export(p providers.Provider, name, explicitPath string, external bool) error {\n\tcfg, err := get(p, name, external)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn kubeconfig.WriteMerged(cfg, explicitPath)\n}\n\n// Remove removes clusterName from the kubeconfig paths detected based on\n// either explicitPath being set or $KUBECONFIG or $HOME/.kube/config, following\n// the rules set by kubectl\n// clusterName must identify a kind cluster.\nfunc Remove(clusterName, explicitPath string) error {\n\treturn kubeconfig.RemoveKIND(clusterName, explicitPath)\n}\n\n// Get returns the kubeconfig for the cluster\n// external controls if the internal IP address is used or the host endpoint\nfunc Get(p providers.Provider, name string, external bool) (string, error) {\n\tcfg, err := get(p, name, external)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tb, err := kubeconfig.Encode(cfg)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), err\n}\n\n// ContextForCluster returns the context name for a kind cluster based on\n// its name. This key is used for all list entries of kind clusters\nfunc ContextForCluster(kindClusterName string) string {\n\treturn kubeconfig.KINDClusterKey(kindClusterName)\n}\n\nfunc get(p providers.Provider, name string, external bool) (*kubeconfig.Config, error) {\n\t// find a control plane node to get the kubeadm config from\n\tn, err := p.ListNodes(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar buff bytes.Buffer\n\tnodes, err := nodeutils.ControlPlaneNodes(n)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) < 1 {\n\t\treturn nil, errors.Errorf(\"could not locate any control plane nodes for cluster named '%s'. \"+\n\t\t\t\"Use the --name option to select a different cluster\", name)\n\t}\n\tnode := nodes[0]\n\n\t// grab kubeconfig version from the node\n\tif err := node.Command(\"cat\", \"/etc/kubernetes/admin.conf\").SetStdout(&buff).Run(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get cluster internal kubeconfig\")\n\t}\n\n\t// if we're doing external we need to override the server endpoint\n\tserver := \"\"\n\tif external {\n\t\tendpoint, err := p.GetAPIServerEndpoint(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tserver = \"https://\" + endpoint\n\t}\n\n\t// actually encode\n\treturn kubeconfig.KINDFromRawKubeadm(buff.String(), name, server)\n}\n"
  },
  {
    "path": "pkg/cluster/internal/loadbalancer/config.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage loadbalancer\n\nimport (\n\t\"bytes\"\n\t\"text/template\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// ConfigData is supplied to the loadbalancer config template\ntype ConfigData struct {\n\tControlPlanePort int\n\tBackendServers   map[string]string\n\tIPv6             bool\n}\n\n// DefaultConfigTemplate is the loadbalancer config template\nconst DefaultConfigTemplate = `# generated by kind\nglobal\n  log /dev/log local0\n  log /dev/log local1 notice\n  daemon\n  # limit memory usage to approximately 18 MB\n  maxconn 100000\n\nresolvers docker\n  parse-resolv-conf\n\ndefaults\n  log global\n  mode tcp\n  option dontlognull\n  # TODO: tune these\n  timeout connect 5000\n  timeout client 50000\n  timeout server 50000\n  # allow to boot despite dns don't resolve backends\n  default-server init-addr none\n\nfrontend control-plane\n  bind *:{{ .ControlPlanePort }}\n  {{ if .IPv6 -}}\n  bind :::{{ .ControlPlanePort }};\n  {{- end }}\n  default_backend kube-apiservers\n\nbackend kube-apiservers\n  option httpchk GET /healthz\n  # TODO: we should be verifying (!)\n  {{range $server, $address := .BackendServers}}\n  server {{ $server }} {{ $address }} check check-ssl verify none resolvers docker resolve-prefer {{ if $.IPv6 -}} ipv6 {{- else -}} ipv4 {{- end }}\n  {{- end}}\n`\n\n// Config returns a kubeadm config generated from config data, in particular\n// the kubernetes version\nfunc Config(data *ConfigData) (config string, err error) {\n\tt, err := template.New(\"loadbalancer-config\").Parse(DefaultConfigTemplate)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse config template\")\n\t}\n\t// execute the template\n\tvar buff bytes.Buffer\n\terr = t.Execute(&buff, data)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error executing config template\")\n\t}\n\treturn buff.String(), nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/loadbalancer/const.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage loadbalancer\n\n// Image defines the loadbalancer image:tag\nconst Image = \"docker.io/kindest/haproxy:v20260131-7181c60a\"\n\n// ConfigPath defines the path to the config file in the image\nconst ConfigPath = \"/usr/local/etc/haproxy/haproxy.cfg\"\n"
  },
  {
    "path": "pkg/cluster/internal/loadbalancer/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package loadbalancer contains external loadbalancer related constants and configuration\npackage loadbalancer\n"
  },
  {
    "path": "pkg/cluster/internal/logs/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package logs contains tooling for obtaining cluster logs\npackage logs\n"
  },
  {
    "path": "pkg/cluster/internal/logs/logs.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logs\n\nimport (\n\t\"archive/tar\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\n\t\"al.essio.dev/pkg/shellescape\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// DumpDir dumps the dir nodeDir on the node to the dir hostDir on the host\nfunc DumpDir(logger log.Logger, node nodes.Node, nodeDir, hostDir string) (err error) {\n\tcmd := node.Command(\n\t\t\"sh\", \"-c\",\n\t\t// Tar will exit 1 if a file changed during the archival.\n\t\t// We don't care about this, so we're invoking it in a shell\n\t\t// And masking out 1 as a return value.\n\t\t// Fatal errors will return exit code 2.\n\t\t// http://man7.org/linux/man-pages/man1/tar.1.html#RETURN_VALUE\n\t\tfmt.Sprintf(\n\t\t\t`tar --hard-dereference -C %s -chf - . || (r=$?; [ $r -eq 1 ] || exit $r)`,\n\t\t\tshellescape.Quote(path.Clean(nodeDir)+\"/\"),\n\t\t),\n\t)\n\n\treturn exec.RunWithStdoutReader(cmd, func(outReader io.Reader) error {\n\t\tif err := untar(logger, outReader, hostDir); err != nil {\n\t\t\treturn errors.Wrapf(err, \"Untarring %q: %v\", nodeDir, err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// untar reads the tar file from r and writes it into dir.\nfunc untar(logger log.Logger, r io.Reader, dir string) (err error) {\n\ttr := tar.NewReader(r)\n\tfor {\n\t\tf, err := tr.Next()\n\n\t\tswitch {\n\t\tcase err == io.EOF:\n\t\t\t// drain the reader, which may have trailing null bytes\n\t\t\t// we don't want to leave the writer hanging\n\t\t\t_, err := io.Copy(io.Discard, r)\n\t\t\treturn err\n\t\tcase err != nil:\n\t\t\treturn errors.Wrapf(err, \"tar reading error: %v\", err)\n\t\tcase f == nil:\n\t\t\tcontinue\n\t\t}\n\n\t\trel := filepath.FromSlash(f.Name)\n\t\tabs := filepath.Join(dir, rel)\n\n\t\tswitch f.Typeflag {\n\t\tcase tar.TypeReg:\n\t\t\twf, err := os.OpenFile(abs, os.O_CREATE|os.O_RDWR, os.FileMode(f.Mode))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tn, err := io.Copy(wf, tr)\n\t\t\tif closeErr := wf.Close(); closeErr != nil && err == nil {\n\t\t\t\terr = closeErr\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Errorf(\"error writing to %s: %v\", abs, err)\n\t\t\t}\n\t\t\tif n != f.Size {\n\t\t\t\treturn errors.Errorf(\"only wrote %d bytes to %s; expected %d\", n, abs, f.Size)\n\t\t\t}\n\t\tcase tar.TypeDir:\n\t\t\tif _, err := os.Stat(abs); err != nil {\n\t\t\t\tif err := os.MkdirAll(abs, 0755); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tlogger.Warnf(\"tar file entry %s contained unsupported file type %v\", f.Name, f.Typeflag)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/cgroups.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"os\"\n\t\"regexp\"\n\t\"sync\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\nvar nodeReachedCgroupsReadyRegexp *regexp.Regexp\nvar nodeReachedCgroupsReadyRegexpCompileOnce sync.Once\n\n// NodeReachedCgroupsReadyRegexp returns a regexp for use with WaitUntilLogRegexpMatches\n//\n// This is used to avoid \"ERROR: this script needs /sys/fs/cgroup/cgroup.procs to be empty (for writing the top-level cgroup.subtree_control)\"\n// See https://github.com/kubernetes-sigs/kind/issues/2409\n//\n// This pattern matches either \"detected cgroupv1\" from the kind node image's entrypoint logs\n// or \"Multi-User System\" target if is using cgroups v2,\n// so that `docker exec` can be executed safely without breaking cgroup v2 hierarchy.\nfunc NodeReachedCgroupsReadyRegexp() *regexp.Regexp {\n\tnodeReachedCgroupsReadyRegexpCompileOnce.Do(func() {\n\t\t// This is an approximation, see: https://github.com/kubernetes-sigs/kind/pull/2421\n\t\tnodeReachedCgroupsReadyRegexp = regexp.MustCompile(\"Reached target .*Multi-User System.*|detected cgroup v1\")\n\t})\n\treturn nodeReachedCgroupsReadyRegexp\n}\n\n// WaitUntilLogRegexpMatches waits until logCmd output produces a line matching re.\n// It will use logCtx to determine if the logCmd deadline was exceeded for producing\n// the most useful error message in failure cases, logCtx should be the context\n// supplied to create logCmd with CommandContext\nfunc WaitUntilLogRegexpMatches(logCtx context.Context, logCmd exec.Cmd, re *regexp.Regexp) error {\n\tpr, pw, err := os.Pipe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogCmd.SetStdout(pw)\n\tlogCmd.SetStderr(pw)\n\n\tdefer pr.Close()\n\tcmdErrC := make(chan error, 1)\n\tgo func() {\n\t\tdefer pw.Close()\n\t\tcmdErrC <- logCmd.Run()\n\t}()\n\n\tsc := bufio.NewScanner(pr)\n\tfor sc.Scan() {\n\t\tline := sc.Text()\n\t\tif re.MatchString(line) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// when we timeout the process will have been killed due to the timeout, which is not interesting\n\t// in other cases if the command errored this may be a useful error\n\tif ctxErr := logCtx.Err(); ctxErr != context.DeadlineExceeded {\n\t\tif cmdErr := <-cmdErrC; cmdErr != nil {\n\t\t\treturn errors.Wrap(cmdErr, \"failed to read logs\")\n\t\t}\n\t}\n\t// otherwise generic error\n\treturn errors.Errorf(\"could not find a log line that matches %q\", re.String())\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/constants.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\n// APIServerInternalPort defines the port where the control plane is listening\n// _inside_ the node network\nconst APIServerInternalPort = 6443\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package common contains common code for implementing providers\npackage common\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/getport.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"net\"\n)\n\n// PortOrGetFreePort is a helper that either returns the provided port\n// if valid or returns a new free port on listenAddr and a cleanup function\nfunc PortOrGetFreePort(port int32, listenAddr string) (int32, func(), error) {\n\t// in the case of -1 we actually want to pass 0 to the backend to let it pick\n\tif port == -1 {\n\t\treturn 0, nil, nil\n\t}\n\t// in the case of 0 (unset) we want kind to pick one and supply it to the backend\n\tif port == 0 {\n\t\treturn GetFreePort(listenAddr)\n\t}\n\t// otherwise keep the port\n\treturn port, nil, nil\n}\n\n// GetFreePort is a helper used to get a free TCP port on the host\n// returns the free port and a cleanup function, the cleanup function must be called\n// after all free ports have been determined to ensure the same port is not returned\n// multiple times\nfunc GetFreePort(listenAddr string) (int32, func(), error) {\n\tdummyListener, err := net.Listen(\"tcp\", net.JoinHostPort(listenAddr, \"0\"))\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\tport := dummyListener.Addr().(*net.TCPAddr).Port\n\treturn int32(port), func() { dummyListener.Close() }, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/getport_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport \"testing\"\n\nfunc TestPortOrGetFreePort(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname    string\n\t\tport    int32\n\t\twant    int32\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Valid port\",\n\t\t\tport:    80,\n\t\t\twant:    80,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"No port\",\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tgot, releaseHostPortFn, err := PortOrGetFreePort(tt.port, \"localhost\")\n\t\t\tif releaseHostPortFn != nil {\n\t\t\t\treleaseHostPortFn()\n\t\t\t}\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PortOrGetFreePort() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif tt.want != 0 && got != tt.want {\n\t\t\t\tt.Errorf(\"PortOrGetFreePort() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetFreePort(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tlistenAddr string\n\t\twantErr    bool\n\t}{\n\t\t{\n\t\t\tname:       \"listen on localhost\",\n\t\t\tlistenAddr: \"localhost\",\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"listen on IPv4 localhost address\",\n\t\t\tlistenAddr: \"127.0.0.1\",\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"listen on IPv4 non existent address\",\n\t\t\tlistenAddr: \"88.88.88.0\",\n\t\t\twantErr:    true,\n\t\t},\n\t\t{\n\t\t\tname:       \"listen on IPv6 non existent address\",\n\t\t\tlistenAddr: \"2112:beaf:beaf:2:3\",\n\t\t\twantErr:    true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tgot, releaseHostPortFn, err := GetFreePort(tt.listenAddr)\n\t\t\tif releaseHostPortFn != nil {\n\t\t\t\treleaseHostPortFn()\n\t\t\t}\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetFreePort() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got < 0 || got > 65535 && err != nil {\n\t\t\t\tt.Errorf(\"GetFreePort() = %v is not a valid port number \", got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/images.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\n// RequiredNodeImages returns the set of _node_ images specified by the config\n// This does not include the loadbalancer image, and is only used to improve\n// the UX by explicit pulling the node images prior to running\nfunc RequiredNodeImages(cfg *config.Cluster) sets.String {\n\timages := sets.NewString()\n\tfor _, node := range cfg.Nodes {\n\t\timages.Insert(node.Image)\n\t}\n\treturn images\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/images_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\nfunc TestRequiredNodeImages(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname    string\n\t\tcluster *config.Cluster\n\t\twant    sets.String\n\t}{\n\t\t{\n\t\t\tname: \"Cluster with different images\",\n\t\t\tcluster: func() *config.Cluster {\n\t\t\t\tc := config.Cluster{}\n\t\t\t\tn, n2 := config.Node{}, config.Node{}\n\t\t\t\tn.Image = \"node1\"\n\t\t\t\tn2.Image = \"node2\"\n\t\t\t\tc.Nodes = []config.Node{n, n2}\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\twant: sets.NewString(\"node1\", \"node2\"),\n\t\t},\n\t\t{\n\t\t\tname: \"Cluster with nodes with same image\",\n\t\t\tcluster: func() *config.Cluster {\n\t\t\t\tc := config.Cluster{}\n\t\t\t\tn, n2 := config.Node{}, config.Node{}\n\t\t\t\tn.Image = \"node1\"\n\t\t\t\tn2.Image = \"node1\"\n\t\t\t\tc.Nodes = []config.Node{n, n2}\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\twant: sets.NewString(\"node1\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tif got := RequiredNodeImages(tt.cluster); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"RequiredNodeImages() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/logs.go",
    "content": "package common\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// FileOnHost is a helper to create a file at path\n// even if the parent directory doesn't exist\n// in which case it will be created with ModePerm\nfunc FileOnHost(path string) (*os.File, error) {\n\tif err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {\n\t\treturn nil, err\n\t}\n\treturn os.Create(path)\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/namer.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"fmt\"\n)\n\n// MakeNodeNamer returns a func(role string)(nodeName string)\n// used to name nodes based on their role and the clusterName\nfunc MakeNodeNamer(clusterName string) func(string) string {\n\tcounter := make(map[string]int)\n\treturn func(role string) string {\n\t\tcount := 1\n\t\tsuffix := \"\"\n\t\tif v, ok := counter[role]; ok {\n\t\t\tcount += v\n\t\t\tsuffix = fmt.Sprintf(\"%d\", count)\n\t\t}\n\t\tcounter[role] = count\n\t\treturn fmt.Sprintf(\"%s-%s%s\", clusterName, role, suffix)\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/namer_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestMakeNodeNamer(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tname        string\n\t\tclusterName string\n\t\tnodes       []string // list of role nodes that belong to the cluster\n\t\twant        []string\n\t}{\n\t\t{\n\t\t\tname:        \"Default cluster name one node\",\n\t\t\tclusterName: \"kind\",\n\t\t\tnodes:       []string{\"control-plane\"},\n\t\t\twant:        []string{\"kind-control-plane\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"Cluster with 3 nodes\",\n\t\t\tclusterName: \"kind-test\",\n\t\t\tnodes:       []string{\"control-plane\", \"worker\", \"worker\"},\n\t\t\twant:        []string{\"kind-test-control-plane\", \"kind-test-worker\", \"kind-test-worker2\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"Cluster with many nodes\",\n\t\t\tclusterName: \"ab1\",\n\t\t\tnodes:       []string{\"control-plane\", \"control-plane\", \"control-plane\", \"external-load-balancer\", \"worker\", \"worker\", \"worker\"},\n\t\t\twant:        []string{\"ab1-control-plane\", \"ab1-control-plane2\", \"ab1-control-plane3\", \"ab1-external-load-balancer\", \"ab1-worker\", \"ab1-worker2\", \"ab1-worker3\"},\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tvar names []string\n\t\t\tnodeNamer := MakeNodeNamer(tc.clusterName)\n\t\t\tfor _, nodeRole := range tc.nodes {\n\t\t\t\tnames = append(names, nodeNamer(nodeRole))\n\t\t\t}\n\t\t\tassert.DeepEqual(t, tc.want, names)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/proxy.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\nconst (\n\t// HTTPProxy is the HTTP_PROXY environment variable key\n\tHTTPProxy = \"HTTP_PROXY\"\n\t// HTTPSProxy is the HTTPS_PROXY environment variable key\n\tHTTPSProxy = \"HTTPS_PROXY\"\n\t// NOProxy is the NO_PROXY environment variable key\n\tNOProxy = \"NO_PROXY\"\n)\n\n// GetProxyEnvs returns a map of proxy environment variables to their values\n// If proxy settings are set, NO_PROXY is modified to include the cluster subnets\nfunc GetProxyEnvs(cfg *config.Cluster) map[string]string {\n\treturn getProxyEnvs(cfg, os.Getenv)\n}\n\nfunc getProxyEnvs(cfg *config.Cluster, getEnv func(string) string) map[string]string {\n\tenvs := make(map[string]string)\n\tfor _, name := range []string{HTTPProxy, HTTPSProxy, NOProxy} {\n\t\tval := getEnv(name)\n\t\tif val == \"\" {\n\t\t\tval = getEnv(strings.ToLower(name))\n\t\t}\n\t\tif val != \"\" {\n\t\t\tenvs[name] = val\n\t\t\tenvs[strings.ToLower(name)] = val\n\t\t}\n\t}\n\t// Specifically add the cluster subnets to NO_PROXY if we are using a proxy\n\tif len(envs) > 0 {\n\t\tnoProxy := envs[NOProxy]\n\t\tif noProxy != \"\" {\n\t\t\tnoProxy += \",\"\n\t\t}\n\t\tnoProxy += cfg.Networking.ServiceSubnet + \",\" + cfg.Networking.PodSubnet\n\t\tenvs[NOProxy] = noProxy\n\t\tenvs[strings.ToLower(NOProxy)] = noProxy\n\t}\n\treturn envs\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/common/proxy_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestGetProxyEnvs(t *testing.T) {\n\tt.Parallel()\n\t// first test the public method\n\tcfg := &config.Cluster{}\n\tconfig.SetDefaultsCluster(cfg)\n\tenvs := GetProxyEnvs(cfg)\n\t// GetProxyEnvs should always return a valid map\n\tif envs == nil {\n\t\tt.Errorf(\"GetProxyEnvs returned nil but should not\")\n\t}\n\n\t// now test the internal one (with all of the logic)\n\tcases := []struct {\n\t\tname    string\n\t\tcluster *config.Cluster\n\t\tenv     map[string]string\n\t\twant    map[string]string\n\t}{\n\t\t{\n\t\t\tname: \"No environment variables\",\n\t\t\tcluster: func() *config.Cluster {\n\t\t\t\tc := config.Cluster{}\n\t\t\t\tc.Networking.ServiceSubnet = \"10.0.0.0/24\"\n\t\t\t\tc.Networking.PodSubnet = \"12.0.0.0/24\"\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\twant: map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP_PROXY environment variables\",\n\t\t\tcluster: func() *config.Cluster {\n\t\t\t\tc := config.Cluster{}\n\t\t\t\tc.Networking.ServiceSubnet = \"10.0.0.0/24\"\n\t\t\t\tc.Networking.PodSubnet = \"12.0.0.0/24\"\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\tenv: map[string]string{\n\t\t\t\t\"HTTP_PROXY\": \"5.5.5.5\",\n\t\t\t},\n\t\t\twant: map[string]string{\"HTTP_PROXY\": \"5.5.5.5\", \"http_proxy\": \"5.5.5.5\", \"NO_PROXY\": \"10.0.0.0/24,12.0.0.0/24\", \"no_proxy\": \"10.0.0.0/24,12.0.0.0/24\"},\n\t\t},\n\t\t{\n\t\t\tname: \"HTTPS_PROXY environment variables\",\n\t\t\tcluster: func() *config.Cluster {\n\t\t\t\tc := config.Cluster{}\n\t\t\t\tc.Networking.ServiceSubnet = \"10.0.0.0/24\"\n\t\t\t\tc.Networking.PodSubnet = \"12.0.0.0/24\"\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\tenv: map[string]string{\n\t\t\t\t\"HTTPS_PROXY\": \"5.5.5.5\",\n\t\t\t},\n\t\t\twant: map[string]string{\"HTTPS_PROXY\": \"5.5.5.5\", \"https_proxy\": \"5.5.5.5\", \"NO_PROXY\": \"10.0.0.0/24,12.0.0.0/24\", \"no_proxy\": \"10.0.0.0/24,12.0.0.0/24\"},\n\t\t},\n\t\t{\n\t\t\tname: \"NO_PROXY environment variables\",\n\t\t\tcluster: func() *config.Cluster {\n\t\t\t\tc := config.Cluster{}\n\t\t\t\tc.Networking.ServiceSubnet = \"10.0.0.0/24\"\n\t\t\t\tc.Networking.PodSubnet = \"12.0.0.0/24\"\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\tenv: map[string]string{\n\t\t\t\t\"HTTPS_PROXY\": \"5.5.5.5\",\n\t\t\t\t\"NO_PROXY\":    \"8.8.8.8\",\n\t\t\t},\n\t\t\twant: map[string]string{\"HTTPS_PROXY\": \"5.5.5.5\", \"https_proxy\": \"5.5.5.5\", \"NO_PROXY\": \"8.8.8.8,10.0.0.0/24,12.0.0.0/24\", \"no_proxy\": \"8.8.8.8,10.0.0.0/24,12.0.0.0/24\"},\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tresult := getProxyEnvs(tc.cluster, func(e string) string {\n\t\t\t\tif tc.env == nil {\n\t\t\t\t\treturn \"\"\n\t\t\t\t}\n\t\t\t\treturn tc.env[e]\n\t\t\t})\n\t\t\tassert.DeepEqual(t, tc.want, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/OWNERS",
    "content": "labels:\n- area/provider/docker\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/constants.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package docker is the implementation for the docker kind provider.\npackage docker\n\n// clusterLabelKey is applied to each \"node\" docker container for identification\nconst clusterLabelKey = \"io.x-k8s.kind.cluster\"\n\n// nodeRoleLabelKey is applied to each \"node\" docker container for categorization\n// of nodes by role\nconst nodeRoleLabelKey = \"io.x-k8s.kind.role\"\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/images.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n)\n\n// ensureNodeImages ensures that the node images used by the create\n// configuration are present\nfunc ensureNodeImages(logger log.Logger, status *cli.Status, cfg *config.Cluster) error {\n\t// pull each required image\n\tfor _, image := range common.RequiredNodeImages(cfg).List() {\n\t\t// prints user friendly message\n\t\tfriendlyImageName, image := sanitizeImage(image)\n\t\tstatus.Start(fmt.Sprintf(\"Ensuring node image (%s) 🖼\", friendlyImageName))\n\t\tif _, err := pullIfNotPresent(logger, image, 4); err != nil {\n\t\t\tstatus.End(false)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// pullIfNotPresent will pull an image if it is not present locally\n// retrying up to retries times\n// it returns true if it attempted to pull, and any errors from pulling\nfunc pullIfNotPresent(logger log.Logger, image string, retries int) (pulled bool, err error) {\n\t// TODO(bentheelder): switch most (all) of the logging here to debug level\n\t// once we have configurable log levels\n\t// if this did not return an error, then the image exists locally\n\tcmd := exec.Command(\"docker\", \"inspect\", \"--type=image\", image)\n\tif err := cmd.Run(); err == nil {\n\t\tlogger.V(1).Infof(\"Image: %s present locally\", image)\n\t\treturn false, nil\n\t}\n\t// otherwise try to pull it\n\treturn true, pull(logger, image, retries)\n}\n\n// pull pulls an image, retrying up to retries times\nfunc pull(logger log.Logger, image string, retries int) error {\n\tlogger.V(1).Infof(\"Pulling image: %s ...\", image)\n\terr := exec.Command(\"docker\", \"pull\", image).Run()\n\t// retry pulling up to retries times if necessary\n\tif err != nil {\n\t\tfor i := 0; i < retries; i++ {\n\t\t\ttime.Sleep(time.Second * time.Duration(i+1))\n\t\t\tlogger.V(1).Infof(\"Trying again to pull image: %q ... %v\", image, err)\n\t\t\t// TODO(bentheelder): add some backoff / sleep?\n\t\t\terr = exec.Command(\"docker\", \"pull\", image).Run()\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.Wrapf(err, \"failed to pull image %q\", image)\n}\n\n// sanitizeImage is a helper to return human readable image name and\n// the docker pullable image name from the provided image\nfunc sanitizeImage(image string) (string, string) {\n\tif strings.Contains(image, \"@sha256:\") {\n\t\treturn strings.Split(image, \"@sha256:\")[0], image\n\t}\n\treturn image, image\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/network.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha1\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// This may be overridden by KIND_EXPERIMENTAL_DOCKER_NETWORK env,\n// experimentally...\n//\n// By default currently picking a single network is equivalent to the previous\n// behavior *except* that we moved from the default bridge to a user defined\n// network because the default bridge is actually special versus any other\n// docker network and lacks the embedded DNS\n//\n// For now this also makes it easier for apps to join the same network, and\n// leaves users with complex networking desires to create and manage their own\n// networks.\nconst fixedNetworkName = \"kind\"\n\n// ensureNetwork checks if docker network by name exists, if not it creates it\nfunc ensureNetwork(name string) error {\n\t// check if network exists already and remove any duplicate networks\n\texists, err := removeDuplicateNetworks(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// network already exists, we're good\n\t// TODO: the network might already exist and not have ipv6 ... :|\n\t// discussion: https://github.com/kubernetes-sigs/kind/pull/1508#discussion_r414594198\n\tif exists {\n\t\treturn nil\n\t}\n\n\t// Generate unique subnet per network based on the name\n\t// obtained from the ULA fc00::/8 range\n\t// Use the MTU configured for the docker default network\n\t// Make N attempts with \"probing\" in case we happen to collide\n\tsubnet := generateULASubnetFromName(name, 0)\n\tmtu := getDefaultNetworkMTU()\n\terr = createNetworkNoDuplicates(name, subnet, mtu)\n\tif err == nil {\n\t\t// Success!\n\t\treturn nil\n\t}\n\n\t// On the first try check if ipv6 fails entirely on this machine\n\t// https://github.com/kubernetes-sigs/kind/issues/1544\n\t// Otherwise if it's not a pool overlap error, fail\n\t// If it is, make more attempts below\n\tif isIPv6UnavailableError(err) {\n\t\t// only one attempt, IPAM is automatic in ipv4 only\n\t\treturn createNetworkNoDuplicates(name, \"\", mtu)\n\t}\n\tif isPoolOverlapError(err) {\n\t\t// pool overlap suggests perhaps another process created the network\n\t\t// check if network exists already and remove any duplicate networks\n\t\texists, err := checkIfNetworkExists(name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif exists {\n\t\t\treturn nil\n\t\t}\n\t\t// otherwise we'll start trying with different subnets\n\t} else {\n\t\t// unknown error ...\n\t\treturn err\n\t}\n\n\t// keep trying for ipv6 subnets\n\tconst maxAttempts = 5\n\tfor attempt := int32(1); attempt < maxAttempts; attempt++ {\n\t\tsubnet := generateULASubnetFromName(name, attempt)\n\t\terr = createNetworkNoDuplicates(name, subnet, mtu)\n\t\tif err == nil {\n\t\t\t// success!\n\t\t\treturn nil\n\t\t}\n\t\tif isPoolOverlapError(err) {\n\t\t\t// pool overlap suggests perhaps another process created the network\n\t\t\t// check if network exists already and remove any duplicate networks\n\t\t\texists, err := checkIfNetworkExists(name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif exists {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// otherwise we'll try again\n\t\t\tcontinue\n\t\t}\n\t\t// unknown error ...\n\t\treturn err\n\t}\n\treturn errors.New(\"exhausted attempts trying to find a non-overlapping subnet\")\n}\n\nfunc createNetworkNoDuplicates(name, ipv6Subnet string, mtu int) error {\n\tif err := createNetwork(name, ipv6Subnet, mtu); err != nil && !isNetworkAlreadyExistsError(err) {\n\t\treturn err\n\t}\n\t_, err := removeDuplicateNetworks(name)\n\treturn err\n}\n\nfunc removeDuplicateNetworks(name string) (bool, error) {\n\tnetworks, err := sortedNetworksWithName(name)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif len(networks) > 1 {\n\t\tif err := deleteNetworks(networks[1:]...); err != nil && !isOnlyErrorNoSuchNetwork(err) {\n\t\t\treturn false, err\n\t\t}\n\t}\n\treturn len(networks) > 0, nil\n}\n\nfunc createNetwork(name, ipv6Subnet string, mtu int) error {\n\targs := []string{\"network\", \"create\", \"-d=bridge\",\n\t\t\"-o\", \"com.docker.network.bridge.enable_ip_masquerade=true\",\n\t}\n\tif mtu > 0 {\n\t\targs = append(args, \"-o\", fmt.Sprintf(\"com.docker.network.driver.mtu=%d\", mtu))\n\t}\n\tif ipv6Subnet != \"\" {\n\t\targs = append(args, \"--ipv6\", \"--subnet\", ipv6Subnet)\n\t}\n\targs = append(args, name)\n\treturn exec.Command(\"docker\", args...).Run()\n}\n\n// getDefaultNetworkMTU obtains the MTU from the docker default network\nfunc getDefaultNetworkMTU() int {\n\tcmd := exec.Command(\"docker\", \"network\", \"inspect\", \"bridge\",\n\t\t\"-f\", `{{ index .Options \"com.docker.network.driver.mtu\" }}`)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\treturn 0\n\t}\n\tmtu, err := strconv.Atoi(lines[0])\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn mtu\n}\n\nfunc sortedNetworksWithName(name string) ([]string, error) {\n\t// query which networks exist with the name\n\tids, err := networksWithName(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// we can skip sorting if there are less than 2\n\tif len(ids) < 2 {\n\t\treturn ids, nil\n\t}\n\t// inspect them to get more detail for sorting\n\tnetworks, err := inspectNetworks(ids)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// deterministically sort networks\n\t// NOTE: THIS PART IS IMPORTANT!\n\tsortNetworkInspectEntries(networks)\n\t// return network IDs\n\tsortedIDs := make([]string, 0, len(networks))\n\tfor i := range networks {\n\t\tsortedIDs = append(sortedIDs, networks[i].ID)\n\t}\n\treturn sortedIDs, nil\n}\n\nfunc sortNetworkInspectEntries(networks []networkInspectEntry) {\n\tsort.Slice(networks, func(i, j int) bool {\n\t\t// we want networks with active containers first\n\t\tif len(networks[i].Containers) > len(networks[j].Containers) {\n\t\t\treturn true\n\t\t}\n\t\treturn networks[i].ID < networks[j].ID\n\t})\n}\n\nfunc inspectNetworks(networkIDs []string) ([]networkInspectEntry, error) {\n\tinspectOut, err := exec.Output(exec.Command(\"docker\", append([]string{\"network\", \"inspect\"}, networkIDs...)...))\n\t// NOTE: the caller can detect if the network isn't present in the output anyhow\n\t// we don't want to fail on this here.\n\tif err != nil && !isOnlyErrorNoSuchNetwork(err) {\n\t\treturn nil, err\n\t}\n\t// parse\n\tnetworks := []networkInspectEntry{}\n\tif err := json.Unmarshal(inspectOut, &networks); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to decode networks list\")\n\t}\n\treturn networks, nil\n}\n\ntype networkInspectEntry struct {\n\tID string `json:\"Id\"`\n\t// NOTE: we don't care about the contents here but we need to parse\n\t// how many entries exist in the containers map\n\tContainers map[string]map[string]string `json:\"Containers\"`\n}\n\n// networksWithName returns a list of network IDs for networks with this name\nfunc networksWithName(name string) ([]string, error) {\n\tlsOut, err := exec.Output(exec.Command(\n\t\t\"docker\", \"network\", \"ls\",\n\t\t\"--filter=name=^\"+regexp.QuoteMeta(name)+\"$\",\n\t\t\"--format={{.ID}}\", // output as unambiguous IDs\n\t))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcleaned := strings.TrimSuffix(string(lsOut), \"\\n\")\n\tif cleaned == \"\" { // avoid returning []string{\"\"}\n\t\treturn nil, nil\n\t}\n\treturn strings.Split(cleaned, \"\\n\"), nil\n}\n\nfunc checkIfNetworkExists(name string) (bool, error) {\n\tout, err := exec.Output(exec.Command(\n\t\t\"docker\", \"network\", \"ls\",\n\t\t\"--filter=name=^\"+regexp.QuoteMeta(name)+\"$\",\n\t\t\"--format={{.Name}}\",\n\t))\n\treturn strings.HasPrefix(string(out), name), err\n}\n\nfunc isIPv6UnavailableError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\tif rerr == nil {\n\t\treturn false\n\t}\n\terrorMessage := string(rerr.Output)\n\t// we get this error when ipv6 was disabled in docker\n\tconst dockerIPV6DisabledError = \"Error response from daemon: Cannot read IPv6 setup for bridge\"\n\t// TODO: this is fragile, and only necessary due to docker enabling ipv6 by default\n\t// even on hosts that lack ip6tables setup.\n\t// Preferably users would either have ip6tables setup properly or else disable ipv6 in docker\n\tconst dockerIPV6TablesError = \"Error response from daemon: Failed to Setup IP tables: Unable to enable NAT rule:  (iptables failed: ip6tables\"\n\t// we get this error when ipv6 is missing in kernel\n\tconst dockerIPV6PolicyError = \"Error response from daemon: setting default policy to DROP in FORWARD chain failed:  (iptables failed: ip6tables\"\n\n\treturn strings.HasPrefix(errorMessage, dockerIPV6DisabledError) || strings.HasPrefix(errorMessage, dockerIPV6TablesError) || strings.HasPrefix(errorMessage, dockerIPV6PolicyError)\n}\n\nfunc isPoolOverlapError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\treturn rerr != nil && strings.HasPrefix(string(rerr.Output), \"Error response from daemon: Pool overlaps with other one on this address space\") || strings.Contains(string(rerr.Output), \"networks have overlapping\")\n}\n\nfunc isNetworkAlreadyExistsError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\treturn rerr != nil && strings.HasPrefix(string(rerr.Output), \"Error response from daemon: network with name\") && strings.Contains(string(rerr.Output), \"already exists\")\n}\n\n// returns true if:\n// - err only contains no such network errors\nfunc isOnlyErrorNoSuchNetwork(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\tif rerr == nil {\n\t\treturn false\n\t}\n\t// check all lines of output from errored command\n\tb := bytes.NewBuffer(rerr.Output)\n\tfor {\n\t\tl, err := b.ReadBytes('\\n')\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn false\n\t\t}\n\t\t// if the line begins with Error: No such network: it's fine\n\t\ts := string(l)\n\t\tif strings.HasPrefix(s, \"Error: No such network:\") {\n\t\t\tcontinue\n\t\t}\n\t\t// other errors are not fine\n\t\tif strings.HasPrefix(s, \"Error: \") {\n\t\t\treturn false\n\t\t}\n\t\t// other line contents should just be network references\n\t}\n\treturn true\n}\n\nfunc deleteNetworks(networks ...string) error {\n\treturn exec.Command(\"docker\", append([]string{\"network\", \"rm\"}, networks...)...).Run()\n}\n\n// generateULASubnetFromName generate an IPv6 subnet based on the\n// name and Nth probing attempt\nfunc generateULASubnetFromName(name string, attempt int32) string {\n\tip := make([]byte, 16)\n\tip[0] = 0xfc\n\tip[1] = 0x00\n\th := sha1.New()\n\t_, _ = h.Write([]byte(name))\n\t_ = binary.Write(h, binary.LittleEndian, attempt)\n\tbs := h.Sum(nil)\n\tfor i := 2; i < 8; i++ {\n\t\tip[i] = bs[i]\n\t}\n\tsubnet := &net.IPNet{\n\t\tIP:   net.IP(ip),\n\t\tMask: net.CIDRMask(64, 128),\n\t}\n\treturn subnet.String()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/network_integration_test.go",
    "content": "//go:build !nointegration\n// +build !nointegration\n\n/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/integration\"\n)\n\nfunc TestIntegrationEnsureNetworkConcurrent(t *testing.T) {\n\tintegration.MaybeSkip(t)\n\n\ttestNetworkName := \"integration-test-ensure-kind-network\"\n\n\t// cleanup\n\tcleanup := func() {\n\t\tids, _ := networksWithName(testNetworkName)\n\t\tif len(ids) > 0 {\n\t\t\t_ = deleteNetworks(ids...)\n\t\t}\n\t}\n\tcleanup()\n\tdefer cleanup()\n\n\t// this is more than enough to trigger race conditions\n\tnetworkConcurrency := 10\n\n\t// Create multiple networks concurrently\n\terrCh := make(chan error, networkConcurrency)\n\tfor i := 0; i < networkConcurrency; i++ {\n\t\tgo func() {\n\t\t\terrCh <- ensureNetwork(testNetworkName)\n\t\t}()\n\t}\n\tfor i := 0; i < networkConcurrency; i++ {\n\t\tif err := <-errCh; err != nil {\n\t\t\tt.Errorf(\"error creating network: %v\", err)\n\t\t\trerr := exec.RunErrorForError(err)\n\t\t\tif rerr != nil {\n\t\t\t\tt.Errorf(\"%q\", rerr.Output)\n\t\t\t}\n\t\t\tt.Errorf(\"%+v\", errors.StackTrace(err))\n\t\t}\n\t}\n\n\tcmd := exec.Command(\n\t\t\"docker\", \"network\", \"ls\",\n\t\tfmt.Sprintf(\"--filter=name=^%s$\", regexp.QuoteMeta(testNetworkName)),\n\t\t\"--format={{.Name}}\",\n\t)\n\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\tt.Errorf(\"obtaining the docker networks\")\n\t}\n\tif len(lines) != 1 {\n\t\tt.Errorf(\"wrong number of networks created: %d\", len(lines))\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/network_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc Test_generateULASubnetFromName(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tname    string\n\t\tattempt int32\n\t\tsubnet  string\n\t}{\n\t\t{\n\t\t\tname:   \"kind\",\n\t\t\tsubnet: \"fc00:f853:ccd:e793::/64\",\n\t\t},\n\t\t{\n\t\t\tname:    \"foo\",\n\t\t\tattempt: 1,\n\t\t\tsubnet:  \"fc00:8edf:7f02:ec8f::/64\",\n\t\t},\n\t\t{\n\t\t\tname:    \"foo\",\n\t\t\tattempt: 2,\n\t\t\tsubnet:  \"fc00:9968:306b:2c65::/64\",\n\t\t},\n\t\t{\n\t\t\tname:   \"kind2\",\n\t\t\tsubnet: \"fc00:444c:147a:44ab::/64\",\n\t\t},\n\t\t{\n\t\t\tname:   \"kin\",\n\t\t\tsubnet: \"fc00:fcd9:c2be:8e23::/64\",\n\t\t},\n\t\t{\n\t\t\tname:   \"mysupernetwork\",\n\t\t\tsubnet: \"fc00:7ae1:1e0d:b4d4::/64\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture variable\n\t\tt.Run(fmt.Sprintf(\"%s,%d\", tc.name, tc.attempt), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tsubnet := generateULASubnetFromName(tc.name, tc.attempt)\n\t\t\tif subnet != tc.subnet {\n\t\t\t\tt.Errorf(\"Wrong subnet from %v: expected %v, received %v\", tc.name, tc.subnet, subnet)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_sortNetworkInspectEntries(t *testing.T) {\n\tcases := []struct {\n\t\tName     string\n\t\tNetworks []networkInspectEntry\n\t\tSorted   []networkInspectEntry\n\t}{\n\t\t{\n\t\t\tName: \"simple ID sort\",\n\t\t\tNetworks: []networkInspectEntry{\n\t\t\t\t{\n\t\t\t\t\tID: \"dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSorted: []networkInspectEntry{\n\t\t\t\t{\n\t\t\t\t\tID: \"1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"containers attached sort\",\n\t\t\tNetworks: []networkInspectEntry{\n\t\t\t\t{\n\t\t\t\t\tID: \"1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e\",\n\t\t\t\t\tContainers: map[string]map[string]string{\n\t\t\t\t\t\t\"a37779e06f3b694eba491dd450aad18bbbaa0a0fce2952e7c9195ea45ae79d41\": {\n\t\t\t\t\t\t\t\"Name\":       \"buildx_buildkit_kind-builder0\",\n\t\t\t\t\t\t\t\"EndpointID\": \"8f6411fb4360059b2f91028f91ef03130abc96d6381afc265ce53c9df89d5a3d\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"f0445f08b9989921da00250d778975202267fbab364e5fbad0ceb6db24f3f91e\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"128154205c7d88c7bb9c255d389bc9e222b58a48cf83619976e7665a48e79918\",\n\t\t\t\t\tContainers: map[string]map[string]string{\n\t\t\t\t\t\t\"aad18bbbaa0a0fce2952e7c9195ea45ae79d41a37779e06f3b694eba491dd450\": {\n\t\t\t\t\t\t\t\"Name\":       \"fakey-fake\",\n\t\t\t\t\t\t\t\"EndpointID\": \"f03130abc96d6381afc265ce53c9df89d5a3d8f6411fb4360059b2f91028f91e\",\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\tSorted: []networkInspectEntry{\n\t\t\t\t{\n\t\t\t\t\tID: \"128154205c7d88c7bb9c255d389bc9e222b58a48cf83619976e7665a48e79918\",\n\t\t\t\t\tContainers: map[string]map[string]string{\n\t\t\t\t\t\t\"aad18bbbaa0a0fce2952e7c9195ea45ae79d41a37779e06f3b694eba491dd450\": {\n\t\t\t\t\t\t\t\"Name\":       \"fakey-fake\",\n\t\t\t\t\t\t\t\"EndpointID\": \"f03130abc96d6381afc265ce53c9df89d5a3d8f6411fb4360059b2f91028f91e\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e\",\n\t\t\t\t\tContainers: map[string]map[string]string{\n\t\t\t\t\t\t\"a37779e06f3b694eba491dd450aad18bbbaa0a0fce2952e7c9195ea45ae79d41\": {\n\t\t\t\t\t\t\t\"Name\":       \"buildx_buildkit_kind-builder0\",\n\t\t\t\t\t\t\t\"EndpointID\": \"8f6411fb4360059b2f91028f91ef03130abc96d6381afc265ce53c9df89d5a3d\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID: \"f0445f08b9989921da00250d778975202267fbab364e5fbad0ceb6db24f3f91e\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\ttoSort := make([]networkInspectEntry, len(tc.Networks))\n\t\t\tcopy(toSort, tc.Networks)\n\t\t\tsortNetworkInspectEntries(toSort)\n\t\t\tassert.DeepEqual(t, tc.Sorted, toSort)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/node.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// nodes.Node implementation for the docker provider\ntype node struct {\n\tname string\n}\n\nfunc (n *node) String() string {\n\treturn n.name\n}\n\nfunc (n *node) Role() (string, error) {\n\tcmd := exec.Command(\"docker\", \"inspect\",\n\t\t\"--format\", fmt.Sprintf(`{{ index .Config.Labels \"%s\"}}`, nodeRoleLabelKey),\n\t\tn.name,\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get role for node\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"failed to get role for node: output lines %d != 1\", len(lines))\n\t}\n\treturn lines[0], nil\n}\n\nfunc (n *node) IP() (ipv4 string, ipv6 string, err error) {\n\t// retrieve the IP address of the node using docker inspect\n\tcmd := exec.Command(\"docker\", \"inspect\",\n\t\t\"-f\", \"{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}\",\n\t\tn.name, // ... against the \"node\" container\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"failed to get container details\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", \"\", errors.Errorf(\"file should only be one line, got %d lines\", len(lines))\n\t}\n\tips := strings.Split(lines[0], \",\")\n\tif len(ips) != 2 {\n\t\treturn \"\", \"\", errors.Errorf(\"container addresses should have 2 values, got %d values\", len(ips))\n\t}\n\treturn ips[0], ips[1], nil\n}\n\nfunc (n *node) Command(command string, args ...string) exec.Cmd {\n\treturn &nodeCmd{\n\t\tnameOrID: n.name,\n\t\tcommand:  command,\n\t\targs:     args,\n\t}\n}\n\nfunc (n *node) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd {\n\treturn &nodeCmd{\n\t\tnameOrID: n.name,\n\t\tcommand:  command,\n\t\targs:     args,\n\t\tctx:      ctx,\n\t}\n}\n\n// nodeCmd implements exec.Cmd for docker nodes\ntype nodeCmd struct {\n\tnameOrID string // the container name or ID\n\tcommand  string\n\targs     []string\n\tenv      []string\n\tstdin    io.Reader\n\tstdout   io.Writer\n\tstderr   io.Writer\n\tctx      context.Context\n}\n\nfunc (c *nodeCmd) Run() error {\n\targs := []string{\n\t\t\"exec\",\n\t\t// run with privileges so we can remount etc..\n\t\t// this might not make sense in the most general sense, but it is\n\t\t// important to many kind commands\n\t\t\"--privileged\",\n\t}\n\tif c.stdin != nil {\n\t\targs = append(args,\n\t\t\t\"-i\", // interactive so we can supply input\n\t\t)\n\t}\n\t// set env\n\tfor _, env := range c.env {\n\t\targs = append(args, \"-e\", env)\n\t}\n\t// specify the container and command, after this everything will be\n\t// args the command in the container rather than to docker\n\targs = append(\n\t\targs,\n\t\tc.nameOrID, // ... against the container\n\t\tc.command,  // with the command specified\n\t)\n\targs = append(\n\t\targs,\n\t\t// finally, with the caller args\n\t\tc.args...,\n\t)\n\tvar cmd exec.Cmd\n\tif c.ctx != nil {\n\t\tcmd = exec.CommandContext(c.ctx, \"docker\", args...)\n\t} else {\n\t\tcmd = exec.Command(\"docker\", args...)\n\t}\n\tif c.stdin != nil {\n\t\tcmd.SetStdin(c.stdin)\n\t}\n\tif c.stderr != nil {\n\t\tcmd.SetStderr(c.stderr)\n\t}\n\tif c.stdout != nil {\n\t\tcmd.SetStdout(c.stdout)\n\t}\n\treturn cmd.Run()\n}\n\nfunc (c *nodeCmd) SetEnv(env ...string) exec.Cmd {\n\tc.env = env\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStdin(r io.Reader) exec.Cmd {\n\tc.stdin = r\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStdout(w io.Writer) exec.Cmd {\n\tc.stdout = w\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStderr(w io.Writer) exec.Cmd {\n\tc.stderr = w\n\treturn c\n}\n\nfunc (n *node) SerialLogs(w io.Writer) error {\n\treturn exec.Command(\"docker\", \"logs\", n.name).SetStdout(w).SetStderr(w).Run()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/provider.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\n// NewProvider returns a new provider based on executing `docker ...`\nfunc NewProvider(logger log.Logger) providers.Provider {\n\treturn &provider{\n\t\tlogger: logger,\n\t}\n}\n\n// Provider implements provider.Provider\n// see NewProvider\ntype provider struct {\n\tlogger log.Logger\n\tinfo   *providers.ProviderInfo\n}\n\n// String implements fmt.Stringer\n// NOTE: the value of this should not currently be relied upon for anything!\n// This is only used for setting the Node's providerID\nfunc (p *provider) String() string {\n\treturn \"docker\"\n}\n\n// Provision is part of the providers.Provider interface\nfunc (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) {\n\t// TODO: validate cfg\n\t// ensure node images are pulled before actually provisioning\n\tif err := ensureNodeImages(p.logger, status, cfg); err != nil {\n\t\treturn err\n\t}\n\n\t// ensure the pre-requisite network exists\n\tnetworkName := fixedNetworkName\n\tif n := os.Getenv(\"KIND_EXPERIMENTAL_DOCKER_NETWORK\"); n != \"\" {\n\t\tp.logger.Warn(\"WARNING: Overriding docker network due to KIND_EXPERIMENTAL_DOCKER_NETWORK\")\n\t\tp.logger.Warn(\"WARNING: Here be dragons! This is not supported currently.\")\n\t\tnetworkName = n\n\t}\n\tif err := ensureNetwork(networkName); err != nil {\n\t\treturn errors.Wrap(err, \"failed to ensure docker network\")\n\t}\n\n\t// actually provision the cluster\n\ticons := strings.Repeat(\"📦 \", len(cfg.Nodes))\n\tstatus.Start(fmt.Sprintf(\"Preparing nodes %s\", icons))\n\tdefer func() { status.End(err == nil) }()\n\n\t// plan creating the containers\n\tcreateContainerFuncs, err := planCreation(cfg, networkName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// actually create nodes\n\treturn errors.UntilErrorConcurrent(createContainerFuncs)\n}\n\n// ListClusters is part of the providers.Provider interface\nfunc (p *provider) ListClusters() ([]string, error) {\n\tcmd := exec.Command(\"docker\",\n\t\t\"ps\",\n\t\t\"-a\", // show stopped nodes\n\t\t// filter for nodes with the cluster label\n\t\t\"--filter\", \"label=\"+clusterLabelKey,\n\t\t// format to include the cluster name\n\t\t\"--format\", fmt.Sprintf(`{{.Label \"%s\"}}`, clusterLabelKey),\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to list clusters\")\n\t}\n\treturn sets.NewString(lines...).List(), nil\n}\n\n// ListNodes is part of the providers.Provider interface\nfunc (p *provider) ListNodes(cluster string) ([]nodes.Node, error) {\n\tcmd := exec.Command(\"docker\",\n\t\t\"ps\",\n\t\t\"-a\", // show stopped nodes\n\t\t// filter for nodes with the cluster label\n\t\t\"--filter\", fmt.Sprintf(\"label=%s=%s\", clusterLabelKey, cluster),\n\t\t// format to include the cluster name\n\t\t\"--format\", `{{.Names}}`,\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to list nodes\")\n\t}\n\t// convert names to node handles\n\tret := make([]nodes.Node, 0, len(lines))\n\tfor _, name := range lines {\n\t\tret = append(ret, p.node(name))\n\t}\n\treturn ret, nil\n}\n\n// DeleteNodes is part of the providers.Provider interface\nfunc (p *provider) DeleteNodes(n []nodes.Node) error {\n\tif len(n) == 0 {\n\t\treturn nil\n\t}\n\tconst command = \"docker\"\n\targs := make([]string, 0, len(n)+3) // allocate once\n\targs = append(args,\n\t\t\"rm\",\n\t\t\"-f\", // force the container to be delete now\n\t\t\"-v\", // delete volumes\n\t)\n\tfor _, node := range n {\n\t\targs = append(args, node.String())\n\t}\n\tif err := exec.Command(command, args...).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to delete nodes\")\n\t}\n\treturn nil\n}\n\n// GetAPIServerEndpoint is part of the providers.Provider interface\nfunc (p *provider) GetAPIServerEndpoint(cluster string) (string, error) {\n\t// locate the node that hosts this\n\tallNodes, err := p.ListNodes(cluster)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tn, err := nodeutils.APIServerEndpointNode(allNodes)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server endpoint\")\n\t}\n\n\t// if the 'desktop.docker.io/ports/<PORT>/tcp' label is present,\n\t// defer to its value for the api server endpoint\n\t//\n\t// For example:\n\t// \"Labels\": {\n\t// \t\"desktop.docker.io/ports/6443/tcp\": \"10.0.1.7:6443\",\n\t// }\n\tcmd := exec.Command(\n\t\t\"docker\", \"inspect\",\n\t\t\"--format\", fmt.Sprintf(\n\t\t\t\"{{ index .Config.Labels \\\"desktop.docker.io/ports/%d/tcp\\\" }}\", common.APIServerInternalPort,\n\t\t),\n\t\tn.String(),\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server port\")\n\t}\n\tif len(lines) == 1 && lines[0] != \"\" {\n\t\treturn lines[0], nil\n\t}\n\n\t// else, retrieve the specific port mapping via NetworkSettings.Ports\n\tcmd = exec.Command(\n\t\t\"docker\", \"inspect\",\n\t\t\"--format\", fmt.Sprintf(\n\t\t\t\"{{ with (index (index .NetworkSettings.Ports \\\"%d/tcp\\\") 0) }}{{ printf \\\"%%s\\t%%s\\\" .HostIp .HostPort }}{{ end }}\", common.APIServerInternalPort,\n\t\t),\n\t\tn.String(),\n\t)\n\tlines, err = exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server port\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"network details should only be one line, got %d lines\", len(lines))\n\t}\n\tparts := strings.Split(lines[0], \"\\t\")\n\tif len(parts) != 2 {\n\t\treturn \"\", errors.Errorf(\"network details should only be two parts, got %d\", len(parts))\n\t}\n\n\t// join host and port\n\treturn net.JoinHostPort(parts[0], parts[1]), nil\n}\n\n// GetAPIServerInternalEndpoint is part of the providers.Provider interface\nfunc (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) {\n\t// locate the node that hosts this\n\tallNodes, err := p.ListNodes(cluster)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tn, err := nodeutils.APIServerEndpointNode(allNodes)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server endpoint\")\n\t}\n\t// NOTE: we're using the nodes's hostnames which are their names\n\treturn net.JoinHostPort(n.String(), fmt.Sprintf(\"%d\", common.APIServerInternalPort)), nil\n}\n\n// node returns a new node handle for this provider\nfunc (p *provider) node(name string) nodes.Node {\n\treturn &node{\n\t\tname: name,\n\t}\n}\n\n// CollectLogs will populate dir with cluster logs and other debug files\nfunc (p *provider) CollectLogs(dir string, nodes []nodes.Node) error {\n\texecToPathFn := func(cmd exec.Cmd, path string) func() error {\n\t\treturn func() error {\n\t\t\tf, err := common.FileOnHost(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\treturn cmd.SetStdout(f).SetStderr(f).Run()\n\t\t}\n\t}\n\t// construct a slice of methods to collect logs\n\tfns := []func() error{\n\t\t// record info about the host docker\n\t\texecToPathFn(\n\t\t\texec.Command(\"docker\", \"info\"),\n\t\t\tfilepath.Join(dir, \"docker-info.txt\"),\n\t\t),\n\t}\n\t// inspect each node\n\tfor _, n := range nodes {\n\t\tnode := n // https://golang.org/doc/faq#closures_and_goroutines\n\t\tname := node.String()\n\t\tpath := filepath.Join(dir, name)\n\t\tfns = append(fns,\n\t\t\texecToPathFn(exec.Command(\"docker\", \"inspect\", name), filepath.Join(path, \"inspect.json\")),\n\t\t)\n\t}\n\t// run and collect up all errors\n\treturn errors.AggregateConcurrent(fns)\n}\n\n// Info returns the provider info.\n// The info is cached on the first time of the execution.\nfunc (p *provider) Info() (*providers.ProviderInfo, error) {\n\tvar err error\n\tif p.info == nil {\n\t\tp.info, err = info()\n\t}\n\treturn p.info, err\n}\n\n// dockerInfo corresponds to `docker info --format '{{json .}}'`\ntype dockerInfo struct {\n\tCgroupDriver    string   `json:\"CgroupDriver\"`  // \"systemd\", \"cgroupfs\", \"none\"\n\tCgroupVersion   string   `json:\"CgroupVersion\"` // e.g. \"2\"\n\tMemoryLimit     bool     `json:\"MemoryLimit\"`\n\tPidsLimit       bool     `json:\"PidsLimit\"`\n\tCPUShares       bool     `json:\"CPUShares\"`\n\tSecurityOptions []string `json:\"SecurityOptions\"`\n}\n\nfunc info() (*providers.ProviderInfo, error) {\n\tcmd := exec.Command(\"docker\", \"info\", \"--format\", \"{{json .}}\")\n\tout, err := exec.Output(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get docker info\")\n\t}\n\tvar dInfo dockerInfo\n\tif err := json.Unmarshal(out, &dInfo); err != nil {\n\t\treturn nil, err\n\t}\n\tinfo := providers.ProviderInfo{\n\t\tCgroup2: dInfo.CgroupVersion == \"2\",\n\t}\n\t// When CgroupDriver == \"none\", the MemoryLimit/PidsLimit/CPUShares\n\t// values are meaningless and need to be considered false.\n\t// https://github.com/moby/moby/issues/42151\n\tif dInfo.CgroupDriver != \"none\" {\n\t\tinfo.SupportsMemoryLimit = dInfo.MemoryLimit\n\t\tinfo.SupportsPidsLimit = dInfo.PidsLimit\n\t\tinfo.SupportsCPUShares = dInfo.CPUShares\n\t}\n\tfor _, o := range dInfo.SecurityOptions {\n\t\t// o is like \"name=seccomp,profile=default\", or \"name=rootless\",\n\t\tcsvReader := csv.NewReader(strings.NewReader(o))\n\t\tsliceSlice, err := csvReader.ReadAll()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, f := range sliceSlice {\n\t\t\tfor _, ff := range f {\n\t\t\t\tif ff == \"name=rootless\" {\n\t\t\t\t\tinfo.Rootless = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn &info, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/provision.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/fs\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// planCreation creates a slice of funcs that will create the containers\nfunc planCreation(cfg *config.Cluster, networkName string) (createContainerFuncs []func() error, err error) {\n\t// we need to know all the names for NO_PROXY\n\t// compute the names first before any actual node details\n\tnodeNamer := common.MakeNodeNamer(cfg.Name)\n\tnames := make([]string, len(cfg.Nodes))\n\tfor i, node := range cfg.Nodes {\n\t\tname := nodeNamer(string(node.Role)) // name the node\n\t\tnames[i] = name\n\t}\n\thaveLoadbalancer := config.ClusterHasImplicitLoadBalancer(cfg)\n\tif haveLoadbalancer {\n\t\tnames = append(names, nodeNamer(constants.ExternalLoadBalancerNodeRoleValue))\n\t}\n\n\t// these apply to all container creation\n\tgenericArgs, err := commonArgs(cfg.Name, cfg, networkName, names)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// only the external LB should reflect the port if we have multiple control planes\n\tapiServerPort := cfg.Networking.APIServerPort\n\tapiServerAddress := cfg.Networking.APIServerAddress\n\tif haveLoadbalancer {\n\t\t// TODO: picking ports locally is less than ideal with remote docker\n\t\t// but this is supposed to be an implementation detail and NOT picking\n\t\t// them breaks host reboot ...\n\t\t// For now remote docker + multi control plane is not supported\n\t\tapiServerPort = 0              // replaced with random ports\n\t\tapiServerAddress = \"127.0.0.1\" // only the LB needs to be non-local\n\t\t// only for IPv6 only clusters\n\t\tif cfg.Networking.IPFamily == config.IPv6Family {\n\t\t\tapiServerAddress = \"::1\" // only the LB needs to be non-local\n\t\t}\n\t\t// plan loadbalancer node\n\t\tname := names[len(names)-1]\n\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\targs, err := runArgsForLoadBalancer(cfg, name, genericArgs)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn createContainer(name, args)\n\t\t})\n\t}\n\n\t// plan normal nodes\n\tfor i, node := range cfg.Nodes {\n\t\tnode := node.DeepCopy() // copy so we can modify\n\t\tname := names[i]\n\n\t\t// fixup relative paths, docker can only handle absolute paths\n\t\tfor m := range node.ExtraMounts {\n\t\t\thostPath := node.ExtraMounts[m].HostPath\n\t\t\tif !fs.IsAbs(hostPath) {\n\t\t\t\tabsHostPath, err := filepath.Abs(hostPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.Wrapf(err, \"unable to resolve absolute path for hostPath: %q\", hostPath)\n\t\t\t\t}\n\t\t\t\tnode.ExtraMounts[m].HostPath = absHostPath\n\t\t\t}\n\t\t}\n\n\t\t// plan actual creation based on role\n\t\tswitch node.Role {\n\t\tcase config.ControlPlaneRole:\n\t\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\t\tnode.ExtraPortMappings = append(node.ExtraPortMappings,\n\t\t\t\t\tconfig.PortMapping{\n\t\t\t\t\t\tListenAddress: apiServerAddress,\n\t\t\t\t\t\tHostPort:      apiServerPort,\n\t\t\t\t\t\tContainerPort: common.APIServerInternalPort,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\targs, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args)\n\t\t\t})\n\t\tcase config.WorkerRole:\n\t\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\t\targs, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args)\n\t\t\t})\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"unknown node role: %q\", node.Role)\n\t\t}\n\t}\n\treturn createContainerFuncs, nil\n}\n\n// commonArgs computes static arguments that apply to all containers\nfunc commonArgs(cluster string, cfg *config.Cluster, networkName string, nodeNames []string) ([]string, error) {\n\t// standard arguments all nodes containers need, computed once\n\targs := []string{\n\t\t\"--detach\", // run the container detached\n\t\t\"--tty\",    // allocate a tty for entrypoint logs\n\t\t// label the node with the cluster ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", clusterLabelKey, cluster),\n\t\t// user a user defined docker network so we get embedded DNS\n\t\t\"--net\", networkName,\n\t\t// Docker supports the following restart modes:\n\t\t// - no\n\t\t// - on-failure[:max-retries]\n\t\t// - unless-stopped\n\t\t// - always\n\t\t// https://docs.docker.com/engine/reference/commandline/run/#restart-policies---restart\n\t\t//\n\t\t// What we desire is:\n\t\t// - restart on host / dockerd reboot\n\t\t// - don't restart for any other reason\n\t\t//\n\t\t// This means:\n\t\t// - no is out of the question ... it never restarts\n\t\t// - always is a poor choice, we'll keep trying to restart nodes that were\n\t\t// never going to work\n\t\t// - unless-stopped will also retry failures indefinitely, similar to always\n\t\t// except that it won't restart when the container is `docker stop`ed\n\t\t// - on-failure is not great, we're only interested in restarting on\n\t\t// reboots, not failures. *however* we can limit the number of retries\n\t\t// *and* it forgets all state on dockerd restart and retries anyhow.\n\t\t// - on-failure:0 is what we want .. restart on failures, except max\n\t\t// retries is 0, so only restart on reboots.\n\t\t// however this _actually_ means the same thing as always\n\t\t// so the closest thing is on-failure:1, which will retry *once*\n\t\t\"--restart=on-failure:1\",\n\t\t// this can be enabled by default in docker daemon.json, so we explicitly\n\t\t// disable it, we want our entrypoint to be PID1, not docker-init / tini\n\t\t\"--init=false\",\n\t\t// note: requires API v1.41+ from Dec 2020 in Docker 20.10.0\n\t\t// this is the default with cgroups v2 but not with cgroups v1, unless\n\t\t// overridden in the daemon --default-cgroupns-mode\n\t\t// https://github.com/docker/cli/pull/3699#issuecomment-1191675788\n\t\t\"--cgroupns=private\",\n\t}\n\n\t// enable IPv6 if necessary\n\tif config.ClusterHasIPv6(cfg) {\n\t\targs = append(args, \"--sysctl=net.ipv6.conf.all.disable_ipv6=0\", \"--sysctl=net.ipv6.conf.all.forwarding=1\")\n\t}\n\n\t// pass proxy environment variables\n\tproxyEnv, err := getProxyEnv(cfg, networkName, nodeNames)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"proxy setup error\")\n\t}\n\tfor key, val := range proxyEnv {\n\t\targs = append(args, \"-e\", fmt.Sprintf(\"%s=%s\", key, val))\n\t}\n\n\t// handle hosts that have user namespace remapping enabled\n\tif usernsRemap() {\n\t\targs = append(args, \"--userns=host\")\n\t}\n\n\t// handle Docker on Btrfs or ZFS\n\t// https://github.com/kubernetes-sigs/kind/issues/1416#issuecomment-606514724\n\tif mountDevMapper() {\n\t\targs = append(args, \"--volume\", \"/dev/mapper:/dev/mapper\")\n\t}\n\n\t// enable /dev/fuse explicitly for fuse-overlayfs\n\t// (Rootless Docker does not automatically mount /dev/fuse with --privileged)\n\tif mountFuse() {\n\t\targs = append(args, \"--device\", \"/dev/fuse\")\n\t}\n\n\tif cfg.Networking.DNSSearch != nil {\n\t\targs = append(args, \"-e\", \"KIND_DNS_SEARCH=\"+strings.Join(*cfg.Networking.DNSSearch, \" \"))\n\t}\n\n\treturn args, nil\n}\n\nfunc runArgsForNode(node *config.Node, clusterIPFamily config.ClusterIPFamily, name string, args []string) ([]string, error) {\n\targs = append([]string{\n\t\t\"--hostname\", name, // make hostname match container name\n\t\t// label the node with the role ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", nodeRoleLabelKey, node.Role),\n\t\t// running containers in a container requires privileged\n\t\t// NOTE: we could try to replicate this with --cap-add, and use less\n\t\t// privileges, but this flag also changes some mounts that are necessary\n\t\t// including some ones docker would otherwise do by default.\n\t\t// for now this is what we want. in the future we may revisit this.\n\t\t\"--privileged\",\n\t\t\"--security-opt\", \"seccomp=unconfined\", // also ignore seccomp\n\t\t\"--security-opt\", \"apparmor=unconfined\", // also ignore apparmor\n\t\t// runtime temporary storage\n\t\t\"--tmpfs\", \"/tmp\", // various things depend on working /tmp\n\t\t\"--tmpfs\", \"/run\", // systemd wants a writable /run\n\t\t// runtime persistent storage\n\t\t// this ensures that E.G. pods, logs etc. are not on the container\n\t\t// filesystem, which is not only better for performance, but allows\n\t\t// running kind in kind for \"party tricks\"\n\t\t// (please don't depend on doing this though!)\n\t\t\"--volume\", \"/var\",\n\t\t// some k8s things want to read /lib/modules\n\t\t\"--volume\", \"/lib/modules:/lib/modules:ro\",\n\t\t// propagate KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER to the entrypoint script\n\t\t\"-e\", \"KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER\",\n\t},\n\t\targs...,\n\t)\n\n\t// convert mounts and port mappings to container run args\n\targs = append(args, generateMountBindings(node.ExtraMounts...)...)\n\tmappingArgs, err := generatePortMappings(clusterIPFamily, node.ExtraPortMappings...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, mappingArgs...)\n\n\tswitch node.Role {\n\tcase config.ControlPlaneRole:\n\t\targs = append(args, \"-e\", \"KUBECONFIG=/etc/kubernetes/admin.conf\")\n\t}\n\n\t// finally, specify the image to run\n\treturn append(args, node.Image), nil\n}\n\nfunc runArgsForLoadBalancer(cfg *config.Cluster, name string, args []string) ([]string, error) {\n\targs = append([]string{\n\t\t\"--hostname\", name, // make hostname match container name\n\t\t// label the node with the role ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", nodeRoleLabelKey, constants.ExternalLoadBalancerNodeRoleValue),\n\t},\n\t\targs...,\n\t)\n\n\t// load balancer port mapping\n\tmappingArgs, err := generatePortMappings(cfg.Networking.IPFamily,\n\t\tconfig.PortMapping{\n\t\t\tListenAddress: cfg.Networking.APIServerAddress,\n\t\t\tHostPort:      cfg.Networking.APIServerPort,\n\t\t\tContainerPort: common.APIServerInternalPort,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, mappingArgs...)\n\n\t// finally, specify the image to run\n\treturn append(args, loadbalancer.Image), nil\n}\n\nfunc getProxyEnv(cfg *config.Cluster, networkName string, nodeNames []string) (map[string]string, error) {\n\tenvs := common.GetProxyEnvs(cfg)\n\t// Specifically add the docker network subnets to NO_PROXY if we are using a proxy\n\tif len(envs) > 0 {\n\t\tsubnets, err := getSubnets(networkName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnoProxyList := append(subnets, envs[common.NOProxy])\n\t\tnoProxyList = append(noProxyList, nodeNames...)\n\t\t// Add pod and service dns names to no_proxy to allow in cluster\n\t\t// Note: this is best effort based on the default CoreDNS spec\n\t\t// https://github.com/kubernetes/dns/blob/master/docs/specification.md\n\t\t// Any user created pod/service hostnames, namespaces, custom DNS services\n\t\t// are expected to be no-proxied by the user explicitly.\n\t\tnoProxyList = append(noProxyList, \".svc\", \".svc.cluster\", \".svc.cluster.local\")\n\t\tnoProxyJoined := strings.Join(noProxyList, \",\")\n\t\tenvs[common.NOProxy] = noProxyJoined\n\t\tenvs[strings.ToLower(common.NOProxy)] = noProxyJoined\n\t}\n\treturn envs, nil\n}\n\nfunc getSubnets(networkName string) ([]string, error) {\n\tformat := `{{range (index (index . \"IPAM\") \"Config\")}}{{index . \"Subnet\"}} {{end}}`\n\tcmd := exec.Command(\"docker\", \"network\", \"inspect\", \"-f\", format, networkName)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get subnets\")\n\t}\n\treturn strings.Split(strings.TrimSpace(lines[0]), \" \"), nil\n}\n\n// generateMountBindings converts the mount list to a list of args for docker\n// '<HostPath>:<ContainerPath>[:options]', where 'options'\n// is a comma-separated list of the following strings:\n// 'ro', if the path is read only\n// 'Z', if the volume requires SELinux relabeling\nfunc generateMountBindings(mounts ...config.Mount) []string {\n\targs := make([]string, 0, len(mounts))\n\tfor _, m := range mounts {\n\t\tbind := fmt.Sprintf(\"%s:%s\", m.HostPath, m.ContainerPath)\n\t\tvar attrs []string\n\t\tif m.Readonly {\n\t\t\tattrs = append(attrs, \"ro\")\n\t\t}\n\t\t// Only request relabeling if the pod provides an SELinux context. If the pod\n\t\t// does not provide an SELinux context relabeling will label the volume with\n\t\t// the container's randomly allocated MCS label. This would restrict access\n\t\t// to the volume to the container which mounts it first.\n\t\tif m.SelinuxRelabel {\n\t\t\tattrs = append(attrs, \"Z\")\n\t\t}\n\t\tswitch m.Propagation {\n\t\tcase config.MountPropagationNone:\n\t\t\t// noop, private is default\n\t\tcase config.MountPropagationBidirectional:\n\t\t\tattrs = append(attrs, \"rshared\")\n\t\tcase config.MountPropagationHostToContainer:\n\t\t\tattrs = append(attrs, \"rslave\")\n\t\tdefault: // Falls back to \"private\"\n\t\t}\n\t\tif len(attrs) > 0 {\n\t\t\tbind = fmt.Sprintf(\"%s:%s\", bind, strings.Join(attrs, \",\"))\n\t\t}\n\t\targs = append(args, fmt.Sprintf(\"--volume=%s\", bind))\n\t}\n\treturn args\n}\n\n// generatePortMappings converts the portMappings list to a list of args for docker\nfunc generatePortMappings(clusterIPFamily config.ClusterIPFamily, portMappings ...config.PortMapping) ([]string, error) {\n\targs := make([]string, 0, len(portMappings))\n\tfor _, pm := range portMappings {\n\t\t// do provider internal defaulting\n\t\t// in a future API revision we will handle this at the API level and remove this\n\t\tif pm.ListenAddress == \"\" {\n\t\t\tswitch clusterIPFamily {\n\t\t\tcase config.IPv4Family, config.DualStackFamily:\n\t\t\t\tpm.ListenAddress = \"0.0.0.0\" // this is the docker default anyhow\n\t\t\tcase config.IPv6Family:\n\t\t\t\tpm.ListenAddress = \"::\"\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.Errorf(\"unknown cluster IP family: %v\", clusterIPFamily)\n\t\t\t}\n\t\t}\n\t\tif string(pm.Protocol) == \"\" {\n\t\t\tpm.Protocol = config.PortMappingProtocolTCP // TCP is the default\n\t\t}\n\n\t\t// validate that the provider can handle this binding\n\t\tswitch pm.Protocol {\n\t\tcase config.PortMappingProtocolTCP:\n\t\tcase config.PortMappingProtocolUDP:\n\t\tcase config.PortMappingProtocolSCTP:\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"unknown port mapping protocol: %v\", pm.Protocol)\n\t\t}\n\n\t\t// get a random port if necessary (port = 0)\n\t\thostPort, releaseHostPortFn, err := common.PortOrGetFreePort(pm.HostPort, pm.ListenAddress)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"failed to get random host port for port mapping\")\n\t\t}\n\t\tif releaseHostPortFn != nil {\n\t\t\tdefer releaseHostPortFn()\n\t\t}\n\n\t\t// generate the actual mapping arg\n\t\tprotocol := string(pm.Protocol)\n\t\thostPortBinding := net.JoinHostPort(pm.ListenAddress, fmt.Sprintf(\"%d\", hostPort))\n\t\targs = append(args, fmt.Sprintf(\"--publish=%s:%d/%s\", hostPortBinding, pm.ContainerPort, protocol))\n\t}\n\treturn args, nil\n}\n\nfunc createContainer(name string, args []string) error {\n\treturn exec.Command(\"docker\", append([]string{\"run\", \"--name\", name}, args...)...).Run()\n}\n\nfunc createContainerWithWaitUntilSystemdReachesMultiUserSystem(name string, args []string) error {\n\tif err := exec.Command(\"docker\", append([]string{\"run\", \"--name\", name}, args...)...).Run(); err != nil {\n\t\treturn err\n\t}\n\n\tlogCtx, logCancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tlogCmd := exec.CommandContext(logCtx, \"docker\", \"logs\", \"-f\", name)\n\tdefer logCancel()\n\treturn common.WaitUntilLogRegexpMatches(logCtx, logCmd, common.NodeReachedCgroupsReadyRegexp())\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/docker/util.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage docker\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// IsAvailable checks if docker is available in the system\nfunc IsAvailable() bool {\n\tcmd := exec.Command(\"docker\", \"-v\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\treturn false\n\t}\n\treturn strings.HasPrefix(lines[0], \"Docker version\")\n}\n\n// usernsRemap checks if userns-remap is enabled in dockerd\nfunc usernsRemap() bool {\n\tcmd := exec.Command(\"docker\", \"info\", \"--format\", \"'{{json .SecurityOptions}}'\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif len(lines) > 0 {\n\t\tif strings.Contains(lines[0], \"name=userns\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// mountDevMapper checks if the Docker storage driver is Btrfs or ZFS\n// or if the backing filesystem is Btrfs\nfunc mountDevMapper() bool {\n\tstorage := \"\"\n\t// check the docker storage driver\n\tcmd := exec.Command(\"docker\", \"info\", \"-f\", \"{{.Driver}}\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\treturn false\n\t}\n\n\tstorage = strings.ToLower(strings.TrimSpace(lines[0]))\n\tif storage == \"btrfs\" || storage == \"zfs\" || storage == \"devicemapper\" {\n\t\treturn true\n\t}\n\n\t// check the backing file system\n\t// docker info -f '{{json .DriverStatus  }}'\n\t// [[\"Backing Filesystem\",\"extfs\"],[\"Supports d_type\",\"true\"],[\"Native Overlay Diff\",\"true\"]]\n\tcmd = exec.Command(\"docker\", \"info\", \"-f\", \"{{json .DriverStatus }}\")\n\tlines, err = exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\treturn false\n\t}\n\tvar dat [][]string\n\tif err := json.Unmarshal([]byte(lines[0]), &dat); err != nil {\n\t\treturn false\n\t}\n\tfor _, item := range dat {\n\t\tif item[0] == \"Backing Filesystem\" {\n\t\t\tstorage = strings.ToLower(item[1])\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn storage == \"btrfs\" || storage == \"zfs\" || storage == \"xfs\"\n}\n\n// rootless: use fuse-overlayfs by default\n// https://github.com/kubernetes-sigs/kind/issues/2275\nfunc mountFuse() bool {\n\ti, err := info()\n\tif err != nil {\n\t\treturn false\n\t}\n\tif i != nil && i.Rootless {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/OWNERS",
    "content": "labels:\n- area/provider/nerdctl\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/constants.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package nerdctl implements the nerdctl kind provider.\npackage nerdctl\n\n// clusterLabelKey is applied to each \"node\" container for identification\nconst clusterLabelKey = \"io.x-k8s.kind.cluster\"\n\n// nodeRoleLabelKey is applied to each \"node\" container for categorization\n// of nodes by role\nconst nodeRoleLabelKey = \"io.x-k8s.kind.role\"\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/images.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n)\n\n// ensureNodeImages ensures that the node images used by the create\n// configuration are present\nfunc ensureNodeImages(logger log.Logger, status *cli.Status, cfg *config.Cluster, binaryName string) error {\n\t// pull each required image\n\tfor _, image := range common.RequiredNodeImages(cfg).List() {\n\t\t// prints user friendly message\n\t\tfriendlyImageName, image := sanitizeImage(image)\n\t\tstatus.Start(fmt.Sprintf(\"Ensuring node image (%s) 🖼\", friendlyImageName))\n\t\tif _, err := pullIfNotPresent(logger, image, 4, binaryName); err != nil {\n\t\t\tstatus.End(false)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// pullIfNotPresent will pull an image if it is not present locally\n// retrying up to retries times\n// it returns true if it attempted to pull, and any errors from pulling\nfunc pullIfNotPresent(logger log.Logger, image string, retries int, binaryName string) (pulled bool, err error) {\n\t// TODO(bentheelder): switch most (all) of the logging here to debug level\n\t// once we have configurable log levels\n\t// if this did not return an error, then the image exists locally\n\tcmd := exec.Command(binaryName, \"inspect\", \"--type=image\", image)\n\tif err := cmd.Run(); err == nil {\n\t\tlogger.V(1).Infof(\"Image: %s present locally\", image)\n\t\treturn false, nil\n\t}\n\t// otherwise try to pull it\n\treturn true, pull(logger, image, retries, binaryName)\n}\n\n// pull pulls an image, retrying up to retries times\nfunc pull(logger log.Logger, image string, retries int, binaryName string) error {\n\tlogger.V(1).Infof(\"Pulling image: %s ...\", image)\n\terr := exec.Command(binaryName, \"pull\", image).Run()\n\t// retry pulling up to retries times if necessary\n\tif err != nil {\n\t\tfor i := 0; i < retries; i++ {\n\t\t\ttime.Sleep(time.Second * time.Duration(i+1))\n\t\t\tlogger.V(1).Infof(\"Trying again to pull image: %q ... %v\", image, err)\n\t\t\t// TODO(bentheelder): add some backoff / sleep?\n\t\t\terr = exec.Command(binaryName, \"pull\", image).Run()\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.Wrapf(err, \"failed to pull image %q\", image)\n}\n\n// sanitizeImage is a helper to return human readable image name and\n// the docker pullable image name from the provided image\nfunc sanitizeImage(image string) (string, string) {\n\tif strings.Contains(image, \"@sha256:\") {\n\t\treturn strings.Split(image, \"@sha256:\")[0], image\n\t}\n\treturn image, image\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/network.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"crypto/sha1\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// This may be overridden by KIND_EXPERIMENTAL_DOCKER_NETWORK env,\n// experimentally...\n//\n// By default currently picking a single network is equivalent to the previous\n// behavior *except* that we moved from the default bridge to a user defined\n// network because the default bridge is actually special versus any other\n// docker network and lacks the embedded DNS\n//\n// For now this also makes it easier for apps to join the same network, and\n// leaves users with complex networking desires to create and manage their own\n// networks.\nconst fixedNetworkName = \"kind\"\n\n// ensureNetwork checks if docker network by name exists, if not it creates it\nfunc ensureNetwork(name, binaryName string) error {\n\t// check if network exists already and remove any duplicate networks\n\texists, err := checkIfNetworkExists(name, binaryName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// network already exists, we're good\n\t// TODO: the network might already exist and not have ipv6 ... :|\n\t// discussion: https://github.com/kubernetes-sigs/kind/pull/1508#discussion_r414594198\n\tif exists {\n\t\treturn nil\n\t}\n\n\tsubnet := generateULASubnetFromName(name, 0)\n\tmtu := getDefaultNetworkMTU(binaryName)\n\terr = createNetwork(name, subnet, mtu, binaryName)\n\tif err == nil {\n\t\t// Success!\n\t\treturn nil\n\t}\n\n\t// On the first try check if ipv6 fails entirely on this machine\n\t// https://github.com/kubernetes-sigs/kind/issues/1544\n\t// Otherwise if it's not a pool overlap error, fail\n\t// If it is, make more attempts below\n\tif isIPv6UnavailableError(err) {\n\t\t// only one attempt, IPAM is automatic in ipv4 only\n\t\treturn createNetwork(name, \"\", mtu, binaryName)\n\t}\n\tif isPoolOverlapError(err) {\n\t\t// pool overlap suggests perhaps another process created the network\n\t\t// check if network exists already and remove any duplicate networks\n\t\texists, err := checkIfNetworkExists(name, binaryName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif exists {\n\t\t\treturn nil\n\t\t}\n\t\t// otherwise we'll start trying with different subnets\n\t} else {\n\t\t// unknown error ...\n\t\treturn err\n\t}\n\n\t// keep trying for ipv6 subnets\n\tconst maxAttempts = 5\n\tfor attempt := int32(1); attempt < maxAttempts; attempt++ {\n\t\tsubnet := generateULASubnetFromName(name, attempt)\n\t\terr = createNetwork(name, subnet, mtu, binaryName)\n\t\tif err == nil {\n\t\t\t// success!\n\t\t\treturn nil\n\t\t}\n\t\tif isPoolOverlapError(err) {\n\t\t\t// pool overlap suggests perhaps another process created the network\n\t\t\t// check if network exists already and remove any duplicate networks\n\t\t\texists, err := checkIfNetworkExists(name, binaryName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif exists {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// otherwise we'll try again\n\t\t\tcontinue\n\t\t}\n\t\t// unknown error ...\n\t\treturn err\n\t}\n\treturn errors.New(\"exhausted attempts trying to find a non-overlapping subnet\")\n}\n\nfunc createNetwork(name, ipv6Subnet string, mtu int, binaryName string) error {\n\targs := []string{\"network\", \"create\", \"-d=bridge\"}\n\t// TODO: Not supported in nerdctl yet\n\t//\t\"-o\", \"com.docker.network.bridge.enable_ip_masquerade=true\",\n\tif mtu > 0 {\n\t\targs = append(args, \"-o\", fmt.Sprintf(\"com.docker.network.driver.mtu=%d\", mtu))\n\t}\n\tif ipv6Subnet != \"\" {\n\t\targs = append(args, \"--ipv6\", \"--subnet\", ipv6Subnet)\n\t}\n\targs = append(args, name)\n\treturn exec.Command(binaryName, args...).Run()\n}\n\n// getDefaultNetworkMTU obtains the MTU from the docker default network\nfunc getDefaultNetworkMTU(binaryName string) int {\n\tcmd := exec.Command(binaryName, \"network\", \"inspect\", \"bridge\",\n\t\t\"-f\", `{{ index .Options \"com.docker.network.driver.mtu\" }}`)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\treturn 0\n\t}\n\tmtu, err := strconv.Atoi(lines[0])\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn mtu\n}\n\nfunc checkIfNetworkExists(name, binaryName string) (bool, error) {\n\tout, err := exec.Output(exec.Command(\n\t\tbinaryName, \"network\", \"inspect\",\n\t\tname, \"--format={{.Name}}\",\n\t))\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\treturn strings.HasPrefix(string(out), name), err\n}\n\nfunc isIPv6UnavailableError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\treturn rerr != nil && strings.HasPrefix(string(rerr.Output), \"Error response from daemon: Cannot read IPv6 setup for bridge\")\n}\n\nfunc isPoolOverlapError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\treturn rerr != nil && strings.HasPrefix(string(rerr.Output), \"Error response from daemon: Pool overlaps with other one on this address space\") || strings.Contains(string(rerr.Output), \"networks have overlapping\")\n}\n\n// generateULASubnetFromName generate an IPv6 subnet based on the\n// name and Nth probing attempt\nfunc generateULASubnetFromName(name string, attempt int32) string {\n\tip := make([]byte, 16)\n\tip[0] = 0xfc\n\tip[1] = 0x00\n\th := sha1.New()\n\t_, _ = h.Write([]byte(name))\n\t_ = binary.Write(h, binary.LittleEndian, attempt)\n\tbs := h.Sum(nil)\n\tfor i := 2; i < 8; i++ {\n\t\tip[i] = bs[i]\n\t}\n\tsubnet := &net.IPNet{\n\t\tIP:   net.IP(ip),\n\t\tMask: net.CIDRMask(64, 128),\n\t}\n\treturn subnet.String()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/network_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc Test_generateULASubnetFromName(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tname    string\n\t\tattempt int32\n\t\tsubnet  string\n\t}{\n\t\t{\n\t\t\tname:   \"kind\",\n\t\t\tsubnet: \"fc00:f853:ccd:e793::/64\",\n\t\t},\n\t\t{\n\t\t\tname:    \"foo\",\n\t\t\tattempt: 1,\n\t\t\tsubnet:  \"fc00:8edf:7f02:ec8f::/64\",\n\t\t},\n\t\t{\n\t\t\tname:    \"foo\",\n\t\t\tattempt: 2,\n\t\t\tsubnet:  \"fc00:9968:306b:2c65::/64\",\n\t\t},\n\t\t{\n\t\t\tname:   \"kind2\",\n\t\t\tsubnet: \"fc00:444c:147a:44ab::/64\",\n\t\t},\n\t\t{\n\t\t\tname:   \"kin\",\n\t\t\tsubnet: \"fc00:fcd9:c2be:8e23::/64\",\n\t\t},\n\t\t{\n\t\t\tname:   \"mysupernetwork\",\n\t\t\tsubnet: \"fc00:7ae1:1e0d:b4d4::/64\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture variable\n\t\tt.Run(fmt.Sprintf(\"%s,%d\", tc.name, tc.attempt), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tsubnet := generateULASubnetFromName(tc.name, tc.attempt)\n\t\t\tif subnet != tc.subnet {\n\t\t\t\tt.Errorf(\"Wrong subnet from %v: expected %v, received %v\", tc.name, tc.subnet, subnet)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/node.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// nodes.Node implementation for the docker provider\ntype node struct {\n\tname       string\n\tbinaryName string\n}\n\nfunc (n *node) String() string {\n\treturn n.name\n}\n\nfunc (n *node) Role() (string, error) {\n\tcmd := exec.Command(n.binaryName, \"inspect\",\n\t\t\"--format\", fmt.Sprintf(`{{ index .Config.Labels \"%s\"}}`, nodeRoleLabelKey),\n\t\tn.name,\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get role for node\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"failed to get role for node: output lines %d != 1\", len(lines))\n\t}\n\treturn lines[0], nil\n}\n\nfunc (n *node) IP() (ipv4 string, ipv6 string, err error) {\n\t// retrieve the IP address of the node using docker inspect\n\tcmd := exec.Command(n.binaryName, \"inspect\",\n\t\t\"-f\", \"{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}\",\n\t\tn.name, // ... against the \"node\" container\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"failed to get container details\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", \"\", errors.Errorf(\"file should only be one line, got %d lines\", len(lines))\n\t}\n\tips := strings.Split(lines[0], \",\")\n\tif len(ips) != 2 {\n\t\treturn \"\", \"\", errors.Errorf(\"container addresses should have 2 values, got %d values\", len(ips))\n\t}\n\treturn ips[0], ips[1], nil\n}\n\nfunc (n *node) Command(command string, args ...string) exec.Cmd {\n\treturn &nodeCmd{\n\t\tbinaryName: n.binaryName,\n\t\tnameOrID:   n.name,\n\t\tcommand:    command,\n\t\targs:       args,\n\t}\n}\n\nfunc (n *node) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd {\n\treturn &nodeCmd{\n\t\tbinaryName: n.binaryName,\n\t\tnameOrID:   n.name,\n\t\tcommand:    command,\n\t\targs:       args,\n\t\tctx:        ctx,\n\t}\n}\n\n// nodeCmd implements exec.Cmd for docker nodes\ntype nodeCmd struct {\n\tbinaryName string\n\tnameOrID   string // the container name or ID\n\tcommand    string\n\targs       []string\n\tenv        []string\n\tstdin      io.Reader\n\tstdout     io.Writer\n\tstderr     io.Writer\n\tctx        context.Context\n}\n\nfunc (c *nodeCmd) Run() error {\n\targs := []string{\n\t\t\"exec\",\n\t\t// run with privileges so we can remount etc..\n\t\t// this might not make sense in the most general sense, but it is\n\t\t// important to many kind commands\n\t\t\"--privileged\",\n\t}\n\tif c.stdin != nil {\n\t\targs = append(args,\n\t\t\t\"-i\", // interactive so we can supply input\n\t\t)\n\t}\n\t// set env\n\tfor _, env := range c.env {\n\t\targs = append(args, \"-e\", env)\n\t}\n\t// specify the container and command, after this everything will be\n\t// args the command in the container rather than to docker\n\targs = append(\n\t\targs,\n\t\tc.nameOrID, // ... against the container\n\t\tc.command,  // with the command specified\n\t)\n\targs = append(\n\t\targs,\n\t\t// finally, with the caller args\n\t\tc.args...,\n\t)\n\tvar cmd exec.Cmd\n\tif c.ctx != nil {\n\t\tcmd = exec.CommandContext(c.ctx, c.binaryName, args...)\n\t} else {\n\t\tcmd = exec.Command(c.binaryName, args...)\n\t}\n\tif c.stdin != nil {\n\t\tcmd.SetStdin(c.stdin)\n\t}\n\tif c.stderr != nil {\n\t\tcmd.SetStderr(c.stderr)\n\t}\n\tif c.stdout != nil {\n\t\tcmd.SetStdout(c.stdout)\n\t}\n\treturn cmd.Run()\n}\n\nfunc (c *nodeCmd) SetEnv(env ...string) exec.Cmd {\n\tc.env = env\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStdin(r io.Reader) exec.Cmd {\n\tc.stdin = r\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStdout(w io.Writer) exec.Cmd {\n\tc.stdout = w\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStderr(w io.Writer) exec.Cmd {\n\tc.stderr = w\n\treturn c\n}\n\nfunc (n *node) SerialLogs(w io.Writer) error {\n\treturn exec.Command(n.binaryName, \"logs\", n.name).SetStdout(w).SetStderr(w).Run()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/provider.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\tosexec \"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\n// NewProvider returns a new provider based on executing `nerdctl ...`\nfunc NewProvider(logger log.Logger, binaryName string) providers.Provider {\n\t// if binaryName is unset, do a lookup; we may be here via a\n\t// library call to provider.DetectNodeProvider(), which returns\n\t// true from nerdctl.IsAvailable() by checking for both finch\n\t// and nerdctl. If we don't redo the lookup here, then a finch\n\t// install that triggered IsAvailable() to be true would fail\n\t// to be used if we default to nerdctl when unset.\n\tif binaryName == \"\" {\n\t\t// default to \"nerdctl\"; but look for \"finch\" if\n\t\t// nerctl binary lookup fails\n\t\tbinaryName = \"nerdctl\"\n\t\tif _, err := osexec.LookPath(\"nerdctl\"); err != nil {\n\t\t\tif _, err := osexec.LookPath(\"finch\"); err == nil {\n\t\t\t\tbinaryName = \"finch\"\n\t\t\t}\n\t\t}\n\t}\n\treturn &provider{\n\t\tlogger:     logger,\n\t\tbinaryName: binaryName,\n\t}\n}\n\n// Provider implements provider.Provider\n// see NewProvider\ntype provider struct {\n\tlogger     log.Logger\n\tbinaryName string\n\tinfo       *providers.ProviderInfo\n}\n\n// String implements fmt.Stringer\n// NOTE: the value of this should not currently be relied upon for anything!\n// This is only used for setting the Node's providerID\nfunc (p *provider) String() string {\n\treturn \"nerdctl\"\n}\n\nfunc (p *provider) Binary() string {\n\treturn p.binaryName\n}\n\n// Provision is part of the providers.Provider interface\nfunc (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) {\n\t// TODO: validate cfg\n\t// ensure node images are pulled before actually provisioning\n\tif err := ensureNodeImages(p.logger, status, cfg, p.Binary()); err != nil {\n\t\treturn err\n\t}\n\n\t// ensure the pre-requisite network exists\n\tif err := ensureNetwork(fixedNetworkName, p.Binary()); err != nil {\n\t\treturn errors.Wrap(err, \"failed to ensure nerdctl network\")\n\t}\n\n\t// actually provision the cluster\n\ticons := strings.Repeat(\"📦 \", len(cfg.Nodes))\n\tstatus.Start(fmt.Sprintf(\"Preparing nodes %s\", icons))\n\tdefer func() { status.End(err == nil) }()\n\n\t// plan creating the containers\n\tcreateContainerFuncs, err := planCreation(cfg, fixedNetworkName, p.Binary())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// actually create nodes\n\t// TODO: remove once nerdctl handles concurrency better\n\t// xref: https://github.com/containerd/nerdctl/issues/2908\n\tfor _, f := range createContainerFuncs {\n\t\tif err := f(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ListClusters is part of the providers.Provider interface\nfunc (p *provider) ListClusters() ([]string, error) {\n\tcmd := exec.Command(p.Binary(),\n\t\t\"ps\",\n\t\t\"-a\", // show stopped nodes\n\t\t// filter for nodes with the cluster label\n\t\t\"--filter\", \"label=\"+clusterLabelKey,\n\t\t// format to include the cluster name\n\t\t\"--format\", fmt.Sprintf(`{{.Label \"%s\"}}`, clusterLabelKey),\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to list clusters\")\n\t}\n\treturn sets.NewString(lines...).List(), nil\n}\n\n// ListNodes is part of the providers.Provider interface\nfunc (p *provider) ListNodes(cluster string) ([]nodes.Node, error) {\n\tcmd := exec.Command(p.Binary(),\n\t\t\"ps\",\n\t\t\"-a\", // show stopped nodes\n\t\t// filter for nodes with the cluster label\n\t\t\"--filter\", fmt.Sprintf(\"label=%s=%s\", clusterLabelKey, cluster),\n\t\t// format to include the cluster name\n\t\t\"--format\", `{{.Names}}`,\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tlength := len(lines)\n\t// convert names to node handles\n\tret := make([]nodes.Node, 0, length)\n\tfor _, name := range lines {\n\t\tif name != \"\" {\n\t\t\tret = append(ret, p.node(name))\n\t\t}\n\t}\n\treturn ret, nil\n}\n\n// DeleteNodes is part of the providers.Provider interface\nfunc (p *provider) DeleteNodes(n []nodes.Node) error {\n\tif len(n) == 0 {\n\t\treturn nil\n\t}\n\targsNoRestart := make([]string, 0, len(n)+2)\n\targsNoRestart = append(argsNoRestart,\n\t\t\"update\",\n\t\t\"--restart=no\",\n\t)\n\targsStop := make([]string, 0, len(n)+1)\n\targsStop = append(argsStop, \"stop\")\n\targsWait := make([]string, 0, len(n)+1)\n\targsWait = append(argsWait, \"wait\")\n\n\targsRm := make([]string, 0, len(n)+3) // allocate once\n\targsRm = append(argsRm,\n\t\t\"rm\",\n\t\t\"-f\",\n\t\t\"-v\", // delete volumes\n\t)\n\tfor _, node := range n {\n\t\targsRm = append(argsRm, node.String())\n\t\targsStop = append(argsStop, node.String())\n\t\targsWait = append(argsWait, node.String())\n\t\targsNoRestart = append(argsNoRestart, node.String())\n\t}\n\tif err := exec.Command(p.Binary(), argsNoRestart...).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to update restart policy to 'no'\")\n\t}\n\tif err := exec.Command(p.Binary(), argsStop...).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to stop nodes\")\n\t}\n\tif err := exec.Command(p.Binary(), argsWait...).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to wait for node exit\")\n\t}\n\tif err := exec.Command(p.Binary(), argsRm...).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to delete nodes\")\n\t}\n\treturn nil\n}\n\n// GetAPIServerEndpoint is part of the providers.Provider interface\nfunc (p *provider) GetAPIServerEndpoint(cluster string) (string, error) {\n\t// locate the node that hosts this\n\tallNodes, err := p.ListNodes(cluster)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tn, err := nodeutils.APIServerEndpointNode(allNodes)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server endpoint\")\n\t}\n\n\t// if the 'desktop.docker.io/ports/<PORT>/tcp' label is present,\n\t// defer to its value for the api server endpoint\n\t//\n\t// For example:\n\t// \"Labels\": {\n\t// \t\"desktop.docker.io/ports/6443/tcp\": \"10.0.1.7:6443\",\n\t// }\n\tcmd := exec.Command(\n\t\tp.Binary(), \"inspect\",\n\t\t\"--format\", fmt.Sprintf(\n\t\t\t\"{{ index .Config.Labels \\\"desktop.docker.io/ports/%d/tcp\\\" }}\", common.APIServerInternalPort,\n\t\t),\n\t\tn.String(),\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server port\")\n\t}\n\tif len(lines) == 1 && lines[0] != \"\" {\n\t\treturn lines[0], nil\n\t}\n\n\t// else, retrieve the specific port mapping via NetworkSettings.Ports\n\tcmd = exec.Command(\n\t\tp.Binary(), \"inspect\",\n\t\t\"--format\", fmt.Sprintf(\n\t\t\t\"{{ with (index (index .NetworkSettings.Ports \\\"%d/tcp\\\") 0) }}{{ printf \\\"%%s\\t%%s\\\" .HostIp .HostPort }}{{ end }}\", common.APIServerInternalPort,\n\t\t),\n\t\tn.String(),\n\t)\n\tlines, err = exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server port\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"network details should only be one line, got %d lines\", len(lines))\n\t}\n\tparts := strings.Split(lines[0], \"\\t\")\n\tif len(parts) != 2 {\n\t\treturn \"\", errors.Errorf(\"network details should only be two parts, got %d\", len(parts))\n\t}\n\n\t// join host and port\n\treturn net.JoinHostPort(parts[0], parts[1]), nil\n}\n\n// GetAPIServerInternalEndpoint is part of the providers.Provider interface\nfunc (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) {\n\t// locate the node that hosts this\n\tallNodes, err := p.ListNodes(cluster)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tn, err := nodeutils.APIServerEndpointNode(allNodes)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server endpoint\")\n\t}\n\t// NOTE: we're using the nodes's hostnames which are their names\n\treturn net.JoinHostPort(n.String(), fmt.Sprintf(\"%d\", common.APIServerInternalPort)), nil\n}\n\n// node returns a new node handle for this provider\nfunc (p *provider) node(name string) nodes.Node {\n\treturn &node{\n\t\tbinaryName: p.binaryName,\n\t\tname:       name,\n\t}\n}\n\n// CollectLogs will populate dir with cluster logs and other debug files\nfunc (p *provider) CollectLogs(dir string, nodes []nodes.Node) error {\n\texecToPathFn := func(cmd exec.Cmd, path string) func() error {\n\t\treturn func() error {\n\t\t\tf, err := common.FileOnHost(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\treturn cmd.SetStdout(f).SetStderr(f).Run()\n\t\t}\n\t}\n\t// construct a slice of methods to collect logs\n\tfns := []func() error{\n\t\t// record info about the host nerdctl\n\t\texecToPathFn(\n\t\t\texec.Command(p.Binary(), \"info\"),\n\t\t\tfilepath.Join(dir, \"docker-info.txt\"),\n\t\t),\n\t}\n\t// inspect each node\n\tfor _, n := range nodes {\n\t\tnode := n // https://golang.org/doc/faq#closures_and_goroutines\n\t\tname := node.String()\n\t\tpath := filepath.Join(dir, name)\n\t\tfns = append(fns,\n\t\t\texecToPathFn(exec.Command(p.Binary(), \"inspect\", name), filepath.Join(path, \"inspect.json\")),\n\t\t)\n\t}\n\t// run and collect up all errors\n\treturn errors.AggregateConcurrent(fns)\n}\n\n// Info returns the provider info.\n// The info is cached on the first time of the execution.\nfunc (p *provider) Info() (*providers.ProviderInfo, error) {\n\tvar err error\n\tif p.info == nil {\n\t\tp.info, err = info(p.Binary())\n\t}\n\treturn p.info, err\n}\n\n// dockerInfo corresponds to `docker info --format '{{json .}}'`\ntype dockerInfo struct {\n\tCgroupDriver    string   `json:\"CgroupDriver\"`  // \"systemd\", \"cgroupfs\", \"none\"\n\tCgroupVersion   string   `json:\"CgroupVersion\"` // e.g. \"2\"\n\tMemoryLimit     bool     `json:\"MemoryLimit\"`\n\tPidsLimit       bool     `json:\"PidsLimit\"`\n\tCPUShares       bool     `json:\"CPUShares\"`\n\tSecurityOptions []string `json:\"SecurityOptions\"`\n}\n\nfunc info(binaryName string) (*providers.ProviderInfo, error) {\n\tcmd := exec.Command(binaryName, \"info\", \"--format\", \"{{json .}}\")\n\tout, err := exec.Output(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get nerdctl info\")\n\t}\n\tvar dInfo dockerInfo\n\tif err := json.Unmarshal(out, &dInfo); err != nil {\n\t\treturn nil, err\n\t}\n\tinfo := providers.ProviderInfo{\n\t\tCgroup2: dInfo.CgroupVersion == \"2\",\n\t}\n\t// When CgroupDriver == \"none\", the MemoryLimit/PidsLimit/CPUShares\n\t// values are meaningless and need to be considered false.\n\t// https://github.com/moby/moby/issues/42151\n\tif dInfo.CgroupDriver != \"none\" {\n\t\tinfo.SupportsMemoryLimit = dInfo.MemoryLimit\n\t\tinfo.SupportsPidsLimit = dInfo.PidsLimit\n\t\tinfo.SupportsCPUShares = dInfo.CPUShares\n\t}\n\tfor _, o := range dInfo.SecurityOptions {\n\t\t// o is like \"name=seccomp,profile=default\", or \"name=rootless\",\n\t\tcsvReader := csv.NewReader(strings.NewReader(o))\n\t\tsliceSlice, err := csvReader.ReadAll()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, f := range sliceSlice {\n\t\t\tfor _, ff := range f {\n\t\t\t\tif ff == \"name=rootless\" {\n\t\t\t\t\tinfo.Rootless = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn &info, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/provision.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/fs\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// planCreation creates a slice of funcs that will create the containers\nfunc planCreation(cfg *config.Cluster, networkName, binaryName string) (createContainerFuncs []func() error, err error) {\n\t// we need to know all the names for NO_PROXY\n\t// compute the names first before any actual node details\n\tnodeNamer := common.MakeNodeNamer(cfg.Name)\n\tnames := make([]string, len(cfg.Nodes))\n\tfor i, node := range cfg.Nodes {\n\t\tname := nodeNamer(string(node.Role)) // name the node\n\t\tnames[i] = name\n\t}\n\thaveLoadbalancer := config.ClusterHasImplicitLoadBalancer(cfg)\n\tif haveLoadbalancer {\n\t\tnames = append(names, nodeNamer(constants.ExternalLoadBalancerNodeRoleValue))\n\t}\n\n\t// these apply to all container creation\n\tgenericArgs, err := commonArgs(cfg.Name, cfg, networkName, names, binaryName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// only the external LB should reflect the port if we have multiple control planes\n\tapiServerPort := cfg.Networking.APIServerPort\n\tapiServerAddress := cfg.Networking.APIServerAddress\n\tif haveLoadbalancer {\n\t\t// TODO: picking ports locally is less than ideal with remote docker\n\t\t// but this is supposed to be an implementation detail and NOT picking\n\t\t// them breaks host reboot ...\n\t\t// For now remote docker + multi control plane is not supported\n\t\tapiServerPort = 0              // replaced with random ports\n\t\tapiServerAddress = \"127.0.0.1\" // only the LB needs to be non-local\n\t\t// only for IPv6 only clusters\n\t\tif cfg.Networking.IPFamily == config.IPv6Family {\n\t\t\tapiServerAddress = \"::1\" // only the LB needs to be non-local\n\t\t}\n\t\t// plan loadbalancer node\n\t\tname := names[len(names)-1]\n\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\targs, err := runArgsForLoadBalancer(cfg, name, genericArgs)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn createContainer(name, args, binaryName)\n\t\t})\n\t}\n\n\t// plan normal nodes\n\tfor i, node := range cfg.Nodes {\n\t\tnode := node.DeepCopy() // copy so we can modify\n\t\tname := names[i]\n\n\t\t// fixup relative paths, docker can only handle absolute paths\n\t\tfor m := range node.ExtraMounts {\n\t\t\thostPath := node.ExtraMounts[m].HostPath\n\t\t\tif !fs.IsAbs(hostPath) {\n\t\t\t\tabsHostPath, err := filepath.Abs(hostPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.Wrapf(err, \"unable to resolve absolute path for hostPath: %q\", hostPath)\n\t\t\t\t}\n\t\t\t\tnode.ExtraMounts[m].HostPath = absHostPath\n\t\t\t}\n\t\t}\n\n\t\t// plan actual creation based on role\n\t\tswitch node.Role {\n\t\tcase config.ControlPlaneRole:\n\t\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\t\tnode.ExtraPortMappings = append(node.ExtraPortMappings,\n\t\t\t\t\tconfig.PortMapping{\n\t\t\t\t\t\tListenAddress: apiServerAddress,\n\t\t\t\t\t\tHostPort:      apiServerPort,\n\t\t\t\t\t\tContainerPort: common.APIServerInternalPort,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\targs, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args, binaryName)\n\t\t\t})\n\t\tcase config.WorkerRole:\n\t\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\t\targs, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args, binaryName)\n\t\t\t})\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"unknown node role: %q\", node.Role)\n\t\t}\n\t}\n\treturn createContainerFuncs, nil\n}\n\n// commonArgs computes static arguments that apply to all containers\nfunc commonArgs(cluster string, cfg *config.Cluster, networkName string, nodeNames []string, binaryName string) ([]string, error) {\n\t// standard arguments all nodes containers need, computed once\n\targs := []string{\n\t\t\"--detach\", // run the container detached\n\t\t\"--tty\",    // allocate a tty for entrypoint logs\n\t\t// label the node with the cluster ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", clusterLabelKey, cluster),\n\t\t// user a user defined network so we get embedded DNS\n\t\t\"--net\", networkName,\n\t\t// containerd supports the following restart modes:\n\t\t// - no\n\t\t// - on-failure[:max-retries]\n\t\t// - unless-stopped\n\t\t// - always\n\t\t//\n\t\t// What we desire is:\n\t\t// - restart on host / container runtime reboot\n\t\t// - don't restart for any other reason\n\t\t//\n\t\t\"--restart=on-failure:1\",\n\t\t// this can be enabled by default in docker daemon.json, so we explicitly\n\t\t// disable it, we want our entrypoint to be PID1, not docker-init / tini\n\t\t\"--init=false\",\n\t}\n\n\t// enable IPv6 if necessary\n\tif config.ClusterHasIPv6(cfg) {\n\t\targs = append(args, \"--sysctl=net.ipv6.conf.all.disable_ipv6=0\", \"--sysctl=net.ipv6.conf.all.forwarding=1\")\n\t}\n\n\t// pass proxy environment variables\n\tproxyEnv, err := getProxyEnv(cfg, networkName, nodeNames, binaryName)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"proxy setup error\")\n\t}\n\tfor key, val := range proxyEnv {\n\t\targs = append(args, \"-e\", fmt.Sprintf(\"%s=%s\", key, val))\n\t}\n\n\t// enable /dev/fuse explicitly for fuse-overlayfs\n\t// (Rootless Docker does not automatically mount /dev/fuse with --privileged)\n\tif mountFuse(binaryName) {\n\t\targs = append(args, \"--device\", \"/dev/fuse\")\n\t}\n\n\tif cfg.Networking.DNSSearch != nil {\n\t\targs = append(args, \"-e\", \"KIND_DNS_SEARCH=\"+strings.Join(*cfg.Networking.DNSSearch, \" \"))\n\t}\n\n\treturn args, nil\n}\n\nfunc runArgsForNode(node *config.Node, clusterIPFamily config.ClusterIPFamily, name string, args []string) ([]string, error) {\n\targs = append([]string{\n\t\t\"--hostname\", name, // make hostname match container name\n\t\t// label the node with the role ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", nodeRoleLabelKey, node.Role),\n\t\t// running containers in a container requires privileged\n\t\t// NOTE: we could try to replicate this with --cap-add, and use less\n\t\t// privileges, but this flag also changes some mounts that are necessary\n\t\t// including some ones docker would otherwise do by default.\n\t\t// for now this is what we want. in the future we may revisit this.\n\t\t\"--privileged\",\n\t\t\"--security-opt\", \"seccomp=unconfined\", // also ignore seccomp\n\t\t\"--security-opt\", \"apparmor=unconfined\", // also ignore apparmor\n\t\t// runtime temporary storage\n\t\t\"--tmpfs\", \"/tmp\", // various things depend on working /tmp\n\t\t\"--tmpfs\", \"/run\", // systemd wants a writable /run\n\t\t// runtime persistent storage\n\t\t// this ensures that E.G. pods, logs etc. are not on the container\n\t\t// filesystem, which is not only better for performance, but allows\n\t\t// running kind in kind for \"party tricks\"\n\t\t// (please don't depend on doing this though!)\n\t\t\"--volume\", \"/var\",\n\t\t// some k8s things want to read /lib/modules\n\t\t\"--volume\", \"/lib/modules:/lib/modules:ro\",\n\t\t// propagate KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER to the entrypoint script\n\t\t\"-e\", \"KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER\",\n\t},\n\t\targs...,\n\t)\n\n\t// convert mounts and port mappings to container run args\n\targs = append(args, generateMountBindings(node.ExtraMounts...)...)\n\tmappingArgs, err := generatePortMappings(clusterIPFamily, node.ExtraPortMappings...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, mappingArgs...)\n\n\tswitch node.Role {\n\tcase config.ControlPlaneRole:\n\t\targs = append(args, \"-e\", \"KUBECONFIG=/etc/kubernetes/admin.conf\")\n\t}\n\n\t// finally, specify the image to run\n\treturn append(args, node.Image), nil\n}\n\nfunc runArgsForLoadBalancer(cfg *config.Cluster, name string, args []string) ([]string, error) {\n\targs = append([]string{\n\t\t\"--hostname\", name, // make hostname match container name\n\t\t// label the node with the role ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", nodeRoleLabelKey, constants.ExternalLoadBalancerNodeRoleValue),\n\t},\n\t\targs...,\n\t)\n\n\t// load balancer port mapping\n\tmappingArgs, err := generatePortMappings(cfg.Networking.IPFamily,\n\t\tconfig.PortMapping{\n\t\t\tListenAddress: cfg.Networking.APIServerAddress,\n\t\t\tHostPort:      cfg.Networking.APIServerPort,\n\t\t\tContainerPort: common.APIServerInternalPort,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, mappingArgs...)\n\n\t// finally, specify the image to run\n\treturn append(args, loadbalancer.Image), nil\n}\n\nfunc getProxyEnv(cfg *config.Cluster, networkName string, nodeNames []string, binaryName string) (map[string]string, error) {\n\tenvs := common.GetProxyEnvs(cfg)\n\t// Specifically add the docker network subnets to NO_PROXY if we are using a proxy\n\tif len(envs) > 0 {\n\t\tsubnets, err := getSubnets(networkName, binaryName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnoProxyList := append(subnets, envs[common.NOProxy])\n\t\tnoProxyList = append(noProxyList, nodeNames...)\n\t\t// Add pod and service dns names to no_proxy to allow in cluster\n\t\t// Note: this is best effort based on the default CoreDNS spec\n\t\t// https://github.com/kubernetes/dns/blob/master/docs/specification.md\n\t\t// Any user created pod/service hostnames, namespaces, custom DNS services\n\t\t// are expected to be no-proxied by the user explicitly.\n\t\tnoProxyList = append(noProxyList, \".svc\", \".svc.cluster\", \".svc.cluster.local\")\n\t\tnoProxyJoined := strings.Join(noProxyList, \",\")\n\t\tenvs[common.NOProxy] = noProxyJoined\n\t\tenvs[strings.ToLower(common.NOProxy)] = noProxyJoined\n\t}\n\treturn envs, nil\n}\n\nfunc getSubnets(networkName, binaryName string) ([]string, error) {\n\tformat := `{{range (index (index . \"IPAM\") \"Config\")}}{{index . \"Subnet\"}} {{end}}`\n\tcmd := exec.Command(binaryName, \"network\", \"inspect\", \"-f\", format, networkName)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get subnets\")\n\t}\n\treturn strings.Split(strings.TrimSpace(lines[0]), \" \"), nil\n}\n\n// generateMountBindings converts the mount list to a list of args for docker\n// '<HostPath>:<ContainerPath>[:options]', where 'options'\n// is a comma-separated list of the following strings:\n// 'ro', if the path is read only\n// 'Z', if the volume requires SELinux relabeling\nfunc generateMountBindings(mounts ...config.Mount) []string {\n\targs := make([]string, 0, len(mounts))\n\tfor _, m := range mounts {\n\t\tbind := fmt.Sprintf(\"%s:%s\", m.HostPath, m.ContainerPath)\n\t\tvar attrs []string\n\t\tif m.Readonly {\n\t\t\tattrs = append(attrs, \"ro\")\n\t\t}\n\t\t// Only request relabeling if the pod provides an SELinux context. If the pod\n\t\t// does not provide an SELinux context relabeling will label the volume with\n\t\t// the container's randomly allocated MCS label. This would restrict access\n\t\t// to the volume to the container which mounts it first.\n\t\tif m.SelinuxRelabel {\n\t\t\tattrs = append(attrs, \"Z\")\n\t\t}\n\t\tswitch m.Propagation {\n\t\tcase config.MountPropagationNone:\n\t\t\t// noop, private is default\n\t\tcase config.MountPropagationBidirectional:\n\t\t\tattrs = append(attrs, \"rshared\")\n\t\tcase config.MountPropagationHostToContainer:\n\t\t\tattrs = append(attrs, \"rslave\")\n\t\tdefault: // Falls back to \"private\"\n\t\t}\n\t\tif len(attrs) > 0 {\n\t\t\tbind = fmt.Sprintf(\"%s:%s\", bind, strings.Join(attrs, \",\"))\n\t\t}\n\t\targs = append(args, fmt.Sprintf(\"--volume=%s\", bind))\n\t}\n\treturn args\n}\n\n// generatePortMappings converts the portMappings list to a list of args for docker\nfunc generatePortMappings(clusterIPFamily config.ClusterIPFamily, portMappings ...config.PortMapping) ([]string, error) {\n\targs := make([]string, 0, len(portMappings))\n\tfor _, pm := range portMappings {\n\t\t// do provider internal defaulting\n\t\t// in a future API revision we will handle this at the API level and remove this\n\t\tif pm.ListenAddress == \"\" {\n\t\t\tswitch clusterIPFamily {\n\t\t\tcase config.IPv4Family, config.DualStackFamily:\n\t\t\t\tpm.ListenAddress = \"0.0.0.0\" // this is the docker default anyhow\n\t\t\tcase config.IPv6Family:\n\t\t\t\tpm.ListenAddress = \"::\"\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.Errorf(\"unknown cluster IP family: %v\", clusterIPFamily)\n\t\t\t}\n\t\t}\n\t\tif string(pm.Protocol) == \"\" {\n\t\t\tpm.Protocol = config.PortMappingProtocolTCP // TCP is the default\n\t\t}\n\n\t\t// validate that the provider can handle this binding\n\t\tswitch pm.Protocol {\n\t\tcase config.PortMappingProtocolTCP:\n\t\tcase config.PortMappingProtocolUDP:\n\t\tcase config.PortMappingProtocolSCTP:\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"unknown port mapping protocol: %v\", pm.Protocol)\n\t\t}\n\n\t\t// get a random port if necessary (port = 0)\n\t\thostPort, releaseHostPortFn, err := common.PortOrGetFreePort(pm.HostPort, pm.ListenAddress)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"failed to get random host port for port mapping\")\n\t\t}\n\t\tif releaseHostPortFn != nil {\n\t\t\tdefer releaseHostPortFn()\n\t\t}\n\n\t\t// generate the actual mapping arg\n\t\tprotocol := string(pm.Protocol)\n\t\thostPortBinding := net.JoinHostPort(pm.ListenAddress, fmt.Sprintf(\"%d\", hostPort))\n\t\targs = append(args, fmt.Sprintf(\"--publish=%s:%d/%s\", hostPortBinding, pm.ContainerPort, protocol))\n\t}\n\treturn args, nil\n}\n\nfunc createContainer(name string, args []string, binaryName string) error {\n\treturn exec.Command(binaryName, append([]string{\"run\", \"--name\", name}, args...)...).Run()\n}\n\nfunc createContainerWithWaitUntilSystemdReachesMultiUserSystem(name string, args []string, binaryName string) error {\n\tif err := exec.Command(binaryName, append([]string{\"run\", \"--name\", name}, args...)...).Run(); err != nil {\n\t\treturn err\n\t}\n\n\tlogCtx, logCancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tlogCmd := exec.CommandContext(logCtx, binaryName, \"logs\", \"-f\", name)\n\tdefer logCancel()\n\treturn common.WaitUntilLogRegexpMatches(logCtx, logCmd, common.NodeReachedCgroupsReadyRegexp())\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/nerdctl/util.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nerdctl\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// IsAvailable checks if nerdctl (or finch) is available in the system\nfunc IsAvailable() bool {\n\tcmd := exec.Command(\"nerdctl\", \"-v\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\t// check finch\n\t\tcmd = exec.Command(\"finch\", \"-v\")\n\t\tlines, err = exec.OutputLines(cmd)\n\t\tif err != nil || len(lines) != 1 {\n\t\t\treturn false\n\t\t}\n\t\treturn strings.HasPrefix(lines[0], \"finch version\")\n\t}\n\treturn strings.HasPrefix(lines[0], \"nerdctl version\")\n}\n\n// rootless: use fuse-overlayfs by default\n// https://github.com/kubernetes-sigs/kind/issues/2275\nfunc mountFuse(binaryName string) bool {\n\ti, err := info(binaryName)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif i != nil && i.Rootless {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/OWNERS",
    "content": "# See the OWNERS docs at https://go.k8s.io/owners\n\nreviewers:\n  - aojea\n  - BenTheElder\napprovers:\n  - aojea\n  - BenTheElder\nemeritus_approvers:\n  - amwat\n\nlabels:\n  - area/provider/podman\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/constants.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package podman implements the podman kind provider.\npackage podman\n\n// clusterLabelKey is applied to each \"node\" podman container for identification\nconst clusterLabelKey = \"io.x-k8s.kind.cluster\"\n\n// nodeRoleLabelKey is applied to each \"node\" podman container for categorization\n// of nodes by role\nconst nodeRoleLabelKey = \"io.x-k8s.kind.role\"\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/images.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n)\n\n// ensureNodeImages ensures that the node images used by the create\n// configuration are present\nfunc ensureNodeImages(logger log.Logger, status *cli.Status, cfg *config.Cluster) error {\n\t// pull each required image\n\tfor _, image := range common.RequiredNodeImages(cfg).List() {\n\t\t// prints user friendly message\n\t\tfriendlyImageName, image := sanitizeImage(image)\n\t\tstatus.Start(fmt.Sprintf(\"Ensuring node image (%s) 🖼\", friendlyImageName))\n\t\tif _, err := pullIfNotPresent(logger, image, 4); err != nil {\n\t\t\tstatus.End(false)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// pullIfNotPresent will pull an image if it is not present locally\n// retrying up to retries times\n// it returns true if it attempted to pull, and any errors from pulling\nfunc pullIfNotPresent(logger log.Logger, image string, retries int) (pulled bool, err error) {\n\t// TODO(bentheelder): switch most (all) of the logging here to debug level\n\t// once we have configurable log levels\n\t// if this did not return an error, then the image exists locally\n\tcmd := exec.Command(\"podman\", \"inspect\", \"--type=image\", image)\n\tif err := cmd.Run(); err == nil {\n\t\tlogger.V(1).Infof(\"Image: %s present locally\", image)\n\t\treturn false, nil\n\t}\n\t// otherwise try to pull it\n\treturn true, pull(logger, image, retries)\n}\n\n// pull pulls an image, retrying up to retries times\nfunc pull(logger log.Logger, image string, retries int) error {\n\tlogger.V(1).Infof(\"Pulling image: %s ...\", image)\n\terr := exec.Command(\"podman\", \"pull\", image).Run()\n\t// retry pulling up to retries times if necessary\n\tif err != nil {\n\t\tfor i := 0; i < retries; i++ {\n\t\t\ttime.Sleep(time.Second * time.Duration(i+1))\n\t\t\tlogger.V(1).Infof(\"Trying again to pull image: %q ... %v\", image, err)\n\t\t\t// TODO(bentheelder): add some backoff / sleep?\n\t\t\terr = exec.Command(\"podman\", \"pull\", image).Run()\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.Wrapf(err, \"failed to pull image %q\", image)\n}\n\n// sanitizeImage is a helper to return human readable image name and\n// the podman pullable image name from the provided image\nfunc sanitizeImage(image string) (friendlyImageName, pullImageName string) {\n\tconst (\n\t\tdefaultDomain    = \"docker.io/\"\n\t\tofficialRepoName = \"library\"\n\t)\n\n\tvar remainder string\n\n\tif strings.Contains(image, \"@sha256:\") {\n\t\tsplits := strings.Split(image, \"@sha256:\")\n\t\tfriendlyImageName = splits[0]\n\t\tremainder = strings.Split(splits[0], \":\")[0] + \"@sha256:\" + splits[1]\n\t} else {\n\t\tfriendlyImageName = image\n\t\tremainder = image\n\t}\n\n\tif !strings.ContainsRune(remainder, '/') {\n\t\tremainder = officialRepoName + \"/\" + remainder\n\t}\n\n\ti := strings.IndexRune(friendlyImageName, '/')\n\tif i == -1 || (!strings.ContainsAny(friendlyImageName[:i], \".:\") && friendlyImageName[:i] != \"localhost\") {\n\t\tpullImageName = defaultDomain + remainder\n\t} else {\n\t\tpullImageName = remainder\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/images_test.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"testing\"\n)\n\nfunc Test_sanitizeImage(t *testing.T) {\n\n\tcases := []struct {\n\t\timage             string\n\t\tfriendlyImageName string\n\t\tpullImageName     string\n\t}{\n\t\t{\n\t\t\timage:             \"kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"kindest/node:v1.21.1\",\n\t\t\tpullImageName:     \"docker.io/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"kindest/node:v1.21.1\",\n\t\t\tfriendlyImageName: \"kindest/node:v1.21.1\",\n\t\t\tpullImageName:     \"docker.io/kindest/node:v1.21.1\",\n\t\t},\n\t\t{\n\t\t\timage:             \"kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"kindest/node\",\n\t\t\tpullImageName:     \"docker.io/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"foo.bar/kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"foo.bar/kindest/node:v1.21.1\",\n\t\t\tpullImageName:     \"foo.bar/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"foo.bar/kindest/node:v1.21.1\",\n\t\t\tfriendlyImageName: \"foo.bar/kindest/node:v1.21.1\",\n\t\t\tpullImageName:     \"foo.bar/kindest/node:v1.21.1\",\n\t\t},\n\t\t{\n\t\t\timage:             \"foo.bar/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"foo.bar/kindest/node\",\n\t\t\tpullImageName:     \"foo.bar/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"foo.bar/baz:quux@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"foo.bar/baz:quux\",\n\t\t\tpullImageName:     \"foo.bar/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"foo.bar/baz:quux\",\n\t\t\tfriendlyImageName: \"foo.bar/baz:quux\",\n\t\t\tpullImageName:     \"foo.bar/baz:quux\",\n\t\t},\n\t\t{\n\t\t\timage:             \"foo.bar/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"foo.bar/baz\",\n\t\t\tpullImageName:     \"foo.bar/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"baz:quux@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"baz:quux\",\n\t\t\tpullImageName:     \"docker.io/library/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t\t{\n\t\t\timage:             \"baz:quux\",\n\t\t\tfriendlyImageName: \"baz:quux\",\n\t\t\tpullImageName:     \"docker.io/library/baz:quux\",\n\t\t},\n\t\t{\n\t\t\timage:             \"baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t\tfriendlyImageName: \"baz\",\n\t\t\tpullImageName:     \"docker.io/library/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc // capture variable\n\t\tt.Run(tc.image, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tfriendlyImageName, pullImageName := sanitizeImage(tc.image)\n\t\t\tif friendlyImageName != tc.friendlyImageName {\n\t\t\t\tt.Errorf(\"Wrong friendlyImageName from %v: expected %v, received %v\", tc.image, tc.friendlyImageName, friendlyImageName)\n\t\t\t}\n\t\t\tif pullImageName != tc.pullImageName {\n\t\t\t\tt.Errorf(\"Wrong pullImageName from %v: expected %v, received %v\", tc.image, tc.pullImageName, pullImageName)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/network.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"crypto/sha1\"\n\t\"encoding/binary\"\n\t\"net\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// This may be overridden by KIND_EXPERIMENTAL_PODMAN_NETWORK env,\n// experimentally...\n//\n// By default currently picking a single network is equivalent to the previous\n// behavior *except* that we moved from the default bridge to a user defined\n// network because the default bridge is actually special versus any other\n// docker network and lacks the embedded DNS\n//\n// For now this also makes it easier for apps to join the same network, and\n// leaves users with complex networking desires to create and manage their own\n// networks.\nconst fixedNetworkName = \"kind\"\n\n// ensureNetwork creates a new network\n// podman only creates IPv6 networks for versions >= 2.2.0\nfunc ensureNetwork(name string) error {\n\t// network already exists\n\tif checkIfNetworkExists(name) {\n\t\treturn nil\n\t}\n\n\t// generate unique subnet per network based on the name\n\t// obtained from the ULA fc00::/8 range\n\t// Make N attempts with \"probing\" in case we happen to collide\n\tsubnet := generateULASubnetFromName(name, 0)\n\terr := createNetwork(name, subnet)\n\tif err == nil {\n\t\t// Success!\n\t\treturn nil\n\t}\n\n\tif isUnknownIPv6FlagError(err) ||\n\t\tisIPv6DisabledError(err) {\n\t\treturn createNetwork(name, \"\")\n\t}\n\n\t// Only continue if the error is because of the subnet range\n\t// is already allocated\n\tif !isPoolOverlapError(err) {\n\t\treturn err\n\t}\n\n\t// keep trying for ipv6 subnets\n\tconst maxAttempts = 5\n\tfor attempt := int32(1); attempt < maxAttempts; attempt++ {\n\t\tsubnet := generateULASubnetFromName(name, attempt)\n\t\terr = createNetwork(name, subnet)\n\t\tif err == nil {\n\t\t\t// success!\n\t\t\treturn nil\n\t\t} else if !isPoolOverlapError(err) {\n\t\t\t// unknown error ...\n\t\t\treturn err\n\t\t}\n\t}\n\treturn errors.New(\"exhausted attempts trying to find a non-overlapping subnet\")\n\n}\n\nfunc createNetwork(name, ipv6Subnet string) error {\n\tif ipv6Subnet == \"\" {\n\t\treturn exec.Command(\"podman\", \"network\", \"create\", \"-d=bridge\", name).Run()\n\t}\n\treturn exec.Command(\"podman\", \"network\", \"create\", \"-d=bridge\",\n\t\t\"--ipv6\", \"--subnet\", ipv6Subnet, name).Run()\n}\n\nfunc checkIfNetworkExists(name string) bool {\n\t_, err := exec.Output(exec.Command(\n\t\t\"podman\", \"network\", \"inspect\",\n\t\tregexp.QuoteMeta(name),\n\t))\n\treturn err == nil\n}\n\nfunc isUnknownIPv6FlagError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\treturn rerr != nil &&\n\t\tstrings.Contains(string(rerr.Output), \"unknown flag: --ipv6\")\n}\n\nfunc isIPv6DisabledError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\treturn rerr != nil &&\n\t\tstrings.Contains(string(rerr.Output), \"is ipv6 enabled in the kernel\")\n}\n\nfunc isPoolOverlapError(err error) bool {\n\trerr := exec.RunErrorForError(err)\n\tif rerr == nil {\n\t\treturn false\n\t}\n\toutput := string(rerr.Output)\n\treturn strings.Contains(output, \"is already used on the host or by another config\") ||\n\t\tstrings.Contains(output, \"is being used by a network interface\") ||\n\t\tstrings.Contains(output, \"is already being used by a cni configuration\")\n}\n\n// generateULASubnetFromName generate an IPv6 subnet based on the\n// name and Nth probing attempt\nfunc generateULASubnetFromName(name string, attempt int32) string {\n\tip := make([]byte, 16)\n\tip[0] = 0xfc\n\tip[1] = 0x00\n\th := sha1.New()\n\t_, _ = h.Write([]byte(name))\n\t_ = binary.Write(h, binary.LittleEndian, attempt)\n\tbs := h.Sum(nil)\n\tfor i := 2; i < 8; i++ {\n\t\tip[i] = bs[i]\n\t}\n\tsubnet := &net.IPNet{\n\t\tIP:   net.IP(ip),\n\t\tMask: net.CIDRMask(64, 128),\n\t}\n\treturn subnet.String()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/node.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// nodes.Node implementation for the podman provider\ntype node struct {\n\tname string\n}\n\nfunc (n *node) String() string {\n\treturn n.name\n}\n\nfunc (n *node) Role() (string, error) {\n\tcmd := exec.Command(\"podman\", \"inspect\",\n\t\t\"--format\", fmt.Sprintf(`{{ index .Config.Labels \"%s\"}}`, nodeRoleLabelKey),\n\t\tn.name,\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get role for node\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"failed to get role for node: output lines %d != 1\", len(lines))\n\t}\n\treturn lines[0], nil\n}\n\nfunc (n *node) IP() (ipv4 string, ipv6 string, err error) {\n\t// retrieve the IP address of the node using podman inspect\n\tcmd := exec.Command(\"podman\", \"inspect\",\n\t\t\"-f\", \"{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}\",\n\t\tn.name, // ... against the \"node\" container\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"failed to get container details\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", \"\", errors.Errorf(\"file should only be one line, got %d lines\", len(lines))\n\t}\n\tips := strings.Split(lines[0], \",\")\n\tif len(ips) != 2 {\n\t\treturn \"\", \"\", errors.Errorf(\"container addresses should have 2 values, got %d values\", len(ips))\n\t}\n\treturn ips[0], ips[1], nil\n}\n\nfunc (n *node) Command(command string, args ...string) exec.Cmd {\n\treturn &nodeCmd{\n\t\tnameOrID: n.name,\n\t\tcommand:  command,\n\t\targs:     args,\n\t}\n}\n\nfunc (n *node) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd {\n\treturn &nodeCmd{\n\t\tnameOrID: n.name,\n\t\tcommand:  command,\n\t\targs:     args,\n\t\tctx:      ctx,\n\t}\n}\n\n// nodeCmd implements exec.Cmd for podman nodes\ntype nodeCmd struct {\n\tnameOrID string // the container name or ID\n\tcommand  string\n\targs     []string\n\tenv      []string\n\tstdin    io.Reader\n\tstdout   io.Writer\n\tstderr   io.Writer\n\tctx      context.Context\n}\n\nfunc (c *nodeCmd) Run() error {\n\targs := []string{\n\t\t\"exec\",\n\t\t// run with privileges so we can remount etc..\n\t\t// this might not make sense in the most general sense, but it is\n\t\t// important to many kind commands\n\t\t\"--privileged\",\n\t}\n\tif c.stdin != nil {\n\t\targs = append(args,\n\t\t\t\"-i\", // interactive so we can supply input\n\t\t)\n\t}\n\t// set env\n\tfor _, env := range c.env {\n\t\targs = append(args, \"-e\", env)\n\t}\n\t// specify the container and command, after this everything will be\n\t// args the command in the container rather than to podman\n\targs = append(\n\t\targs,\n\t\tc.nameOrID, // ... against the container\n\t\tc.command,  // with the command specified\n\t)\n\targs = append(\n\t\targs,\n\t\t// finally, with the caller args\n\t\tc.args...,\n\t)\n\tvar cmd exec.Cmd\n\tif c.ctx != nil {\n\t\tcmd = exec.CommandContext(c.ctx, \"podman\", args...)\n\t} else {\n\t\tcmd = exec.Command(\"podman\", args...)\n\t}\n\tif c.stdin != nil {\n\t\tcmd.SetStdin(c.stdin)\n\t}\n\tif c.stderr != nil {\n\t\tcmd.SetStderr(c.stderr)\n\t}\n\tif c.stdout != nil {\n\t\tcmd.SetStdout(c.stdout)\n\t}\n\treturn cmd.Run()\n}\n\nfunc (c *nodeCmd) SetEnv(env ...string) exec.Cmd {\n\tc.env = env\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStdin(r io.Reader) exec.Cmd {\n\tc.stdin = r\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStdout(w io.Writer) exec.Cmd {\n\tc.stdout = w\n\treturn c\n}\n\nfunc (c *nodeCmd) SetStderr(w io.Writer) exec.Cmd {\n\tc.stderr = w\n\treturn c\n}\n\nfunc (n *node) SerialLogs(w io.Writer) error {\n\treturn exec.Command(\"podman\", \"logs\", n.name).SetStdout(w).SetStderr(w).Run()\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/provider.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\n// NewProvider returns a new provider based on executing `podman ...`\nfunc NewProvider(logger log.Logger) providers.Provider {\n\tlogger.Warn(\"enabling experimental podman provider\")\n\treturn &provider{\n\t\tlogger: logger,\n\t}\n}\n\n// Provider implements provider.Provider\n// see NewProvider\ntype provider struct {\n\tlogger log.Logger\n\tinfo   *providers.ProviderInfo\n}\n\n// String implements fmt.Stringer\n// NOTE: the value of this should not currently be relied upon for anything!\n// This is only used for setting the Node's providerID\nfunc (p *provider) String() string {\n\treturn \"podman\"\n}\n\n// Provision is part of the providers.Provider interface\nfunc (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) {\n\tif err := ensureMinVersion(); err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: validate cfg\n\t// ensure node images are pulled before actually provisioning\n\tif err := ensureNodeImages(p.logger, status, cfg); err != nil {\n\t\treturn err\n\t}\n\n\t// ensure the pre-requisite network exists\n\tnetworkName := fixedNetworkName\n\tif n := os.Getenv(\"KIND_EXPERIMENTAL_PODMAN_NETWORK\"); n != \"\" {\n\t\tp.logger.Warn(\"WARNING: Overriding podman network due to KIND_EXPERIMENTAL_PODMAN_NETWORK\")\n\t\tp.logger.Warn(\"WARNING: Here be dragons! This is not supported currently.\")\n\t\tnetworkName = n\n\t}\n\tif err := ensureNetwork(networkName); err != nil {\n\t\treturn errors.Wrap(err, \"failed to ensure podman network\")\n\t}\n\n\t// actually provision the cluster\n\ticons := strings.Repeat(\"📦 \", len(cfg.Nodes))\n\tstatus.Start(fmt.Sprintf(\"Preparing nodes %s\", icons))\n\tdefer func() { status.End(err == nil) }()\n\n\t// plan creating the containers\n\tcreateContainerFuncs, err := planCreation(cfg, networkName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// actually create nodes\n\treturn errors.UntilErrorConcurrent(createContainerFuncs)\n}\n\n// ListClusters is part of the providers.Provider interface\nfunc (p *provider) ListClusters() ([]string, error) {\n\tcmd := exec.Command(\"podman\",\n\t\t\"ps\",\n\t\t\"-a\", // show stopped nodes\n\t\t// filter for nodes with the cluster label\n\t\t\"--filter\", \"label=\"+clusterLabelKey,\n\t\t// format to include the cluster name\n\t\t\"--format\", fmt.Sprintf(`{{index .Labels \"%s\"}}`, clusterLabelKey),\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to list clusters\")\n\t}\n\treturn sets.NewString(lines...).List(), nil\n}\n\n// ListNodes is part of the providers.Provider interface\nfunc (p *provider) ListNodes(cluster string) ([]nodes.Node, error) {\n\tcmd := exec.Command(\"podman\",\n\t\t\"ps\",\n\t\t\"-a\", // show stopped nodes\n\t\t// filter for nodes with the cluster label\n\t\t\"--filter\", fmt.Sprintf(\"label=%s=%s\", clusterLabelKey, cluster),\n\t\t// format to include the cluster name\n\t\t\"--format\", `{{.Names}}`,\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to list nodes\")\n\t}\n\t// convert names to node handles\n\tret := make([]nodes.Node, 0, len(lines))\n\tfor _, name := range lines {\n\t\tret = append(ret, p.node(name))\n\t}\n\treturn ret, nil\n}\n\n// DeleteNodes is part of the providers.Provider interface\nfunc (p *provider) DeleteNodes(n []nodes.Node) error {\n\tif len(n) == 0 {\n\t\treturn nil\n\t}\n\tconst command = \"podman\"\n\targs := make([]string, 0, len(n)+3) // allocate once\n\targs = append(args,\n\t\t\"rm\",\n\t\t\"-f\", // force the container to be delete now\n\t\t\"-v\", // delete volumes\n\t)\n\tfor _, node := range n {\n\t\targs = append(args, node.String())\n\t}\n\tif err := exec.Command(command, args...).Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to delete nodes\")\n\t}\n\tvar nodeVolumes []string\n\tfor _, node := range n {\n\t\tvolumes, err := getVolumes(node.String())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnodeVolumes = append(nodeVolumes, volumes...)\n\t}\n\tif len(nodeVolumes) == 0 {\n\t\treturn nil\n\t}\n\treturn deleteVolumes(nodeVolumes)\n}\n\n// getHostIPOrDefault defaults HostIP to localhost if is not set\n// xref: https://github.com/kubernetes-sigs/kind/issues/3777\nfunc getHostIPOrDefault(hostIP string) string {\n\tif hostIP == \"\" {\n\t\treturn \"127.0.0.1\"\n\t}\n\treturn hostIP\n}\n\n// GetAPIServerEndpoint is part of the providers.Provider interface\nfunc (p *provider) GetAPIServerEndpoint(cluster string) (string, error) {\n\t// locate the node that hosts this\n\tallNodes, err := p.ListNodes(cluster)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tn, err := nodeutils.APIServerEndpointNode(allNodes)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server endpoint\")\n\t}\n\n\t// TODO: get rid of this once podman settles on how to get the port mapping using podman inspect\n\t// This is only used to get the Kubeconfig server field\n\tv, err := getPodmanVersion()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to check podman version\")\n\t}\n\t// podman inspect was broken between 2.2.0 and 3.0.0\n\t// https://github.com/containers/podman/issues/8444\n\tif v.AtLeast(version.MustParseSemantic(\"2.2.0\")) &&\n\t\tv.LessThan(version.MustParseSemantic(\"3.0.0\")) {\n\t\tp.logger.Warnf(\"WARNING: podman version %s not fully supported, please use versions 3.0.0+\")\n\n\t\tcmd := exec.Command(\n\t\t\t\"podman\", \"inspect\",\n\t\t\t\"--format\",\n\t\t\t\"{{range .NetworkSettings.Ports }}{{range .}}{{.HostIP}}/{{.HostPort}}{{end}}{{end}}\",\n\t\t\tn.String(),\n\t\t)\n\n\t\tlines, err := exec.OutputLines(cmd)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"failed to get api server port\")\n\t\t}\n\t\tif len(lines) != 1 {\n\t\t\treturn \"\", errors.Errorf(\"network details should only be one line, got %d lines\", len(lines))\n\t\t}\n\t\t// output is in the format IP/Port\n\t\tparts := strings.Split(strings.TrimSpace(lines[0]), \"/\")\n\t\tif len(parts) != 2 {\n\t\t\treturn \"\", errors.Errorf(\"network details should be in the format IP/Port, received: %s\", parts)\n\t\t}\n\t\thost := parts[0]\n\t\tport, err := strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Errorf(\"network port not an integer: %v\", err)\n\t\t}\n\n\t\treturn net.JoinHostPort(host, strconv.Itoa(port)), nil\n\t}\n\n\tcmd := exec.Command(\n\t\t\"podman\", \"inspect\",\n\t\t\"--format\",\n\t\t\"{{ json .NetworkSettings.Ports }}\",\n\t\tn.String(),\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get api server port\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"network details should only be one line, got %d lines\", len(lines))\n\t}\n\n\t// portMapping19 maps to the standard CNI portmapping capability used in podman 1.9\n\t// see: https://github.com/containernetworking/cni/blob/spec-v0.4.0/CONVENTIONS.md\n\ttype portMapping19 struct {\n\t\tHostPort      int32  `json:\"hostPort\"`\n\t\tContainerPort int32  `json:\"containerPort\"`\n\t\tProtocol      string `json:\"protocol\"`\n\t\tHostIP        string `json:\"hostIP\"`\n\t}\n\t// portMapping20 maps to the podman 2.0 portmap type\n\t// see: https://github.com/containers/podman/blob/05988fc74fc25f2ad2256d6e011dfb7ad0b9a4eb/libpod/define/container_inspect.go#L134-L143\n\ttype portMapping20 struct {\n\t\tHostPort string `json:\"HostPort\"`\n\t\tHostIP   string `json:\"HostIp\"`\n\t}\n\n\tportMappings20 := make(map[string][]portMapping20)\n\tif err := json.Unmarshal([]byte(lines[0]), &portMappings20); err == nil {\n\t\tfor k, v := range portMappings20 {\n\t\t\tprotocol := \"tcp\"\n\t\t\tparts := strings.Split(k, \"/\")\n\t\t\tif len(parts) == 2 {\n\t\t\t\tprotocol = strings.ToLower(parts[1])\n\t\t\t}\n\t\t\tcontainerPort, err := strconv.Atoi(parts[0])\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tfor _, pm := range v {\n\t\t\t\tif containerPort == common.APIServerInternalPort && protocol == \"tcp\" {\n\t\t\t\t\treturn net.JoinHostPort(getHostIPOrDefault(pm.HostIP), pm.HostPort), nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvar portMappings19 []portMapping19\n\tif err := json.Unmarshal([]byte(lines[0]), &portMappings19); err != nil {\n\t\treturn \"\", errors.Errorf(\"invalid network details: %v\", err)\n\t}\n\tfor _, pm := range portMappings19 {\n\t\tif pm.ContainerPort == common.APIServerInternalPort && pm.Protocol == \"tcp\" {\n\t\t\treturn net.JoinHostPort(getHostIPOrDefault(pm.HostIP), strconv.Itoa(int(pm.HostPort))), nil\n\t\t}\n\t}\n\n\treturn \"\", errors.Errorf(\"failed to get api server port\")\n}\n\n// GetAPIServerInternalEndpoint is part of the providers.Provider interface\nfunc (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) {\n\t// locate the node that hosts this\n\tallNodes, err := p.ListNodes(cluster)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to list nodes\")\n\t}\n\tn, err := nodeutils.APIServerEndpointNode(allNodes)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get apiserver endpoint\")\n\t}\n\t// NOTE: we're using the nodes's hostnames which are their names\n\treturn net.JoinHostPort(n.String(), fmt.Sprintf(\"%d\", common.APIServerInternalPort)), nil\n}\n\n// node returns a new node handle for this provider\nfunc (p *provider) node(name string) nodes.Node {\n\treturn &node{\n\t\tname: name,\n\t}\n}\n\n// CollectLogs will populate dir with cluster logs and other debug files\nfunc (p *provider) CollectLogs(dir string, nodes []nodes.Node) error {\n\texecToPathFn := func(cmd exec.Cmd, path string) func() error {\n\t\treturn func() error {\n\t\t\tf, err := common.FileOnHost(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\treturn cmd.SetStdout(f).SetStderr(f).Run()\n\t\t}\n\t}\n\t// construct a slice of methods to collect logs\n\tfns := []func() error{\n\t\t// record info about the host podman\n\t\texecToPathFn(\n\t\t\texec.Command(\"podman\", \"info\"),\n\t\t\tfilepath.Join(dir, \"podman-info.txt\"),\n\t\t),\n\t}\n\t// inspect each node\n\tfor _, n := range nodes {\n\t\tnode := n // https://golang.org/doc/faq#closures_and_goroutines\n\t\tname := node.String()\n\t\tpath := filepath.Join(dir, name)\n\t\tfns = append(fns,\n\t\t\texecToPathFn(exec.Command(\"podman\", \"inspect\", name), filepath.Join(path, \"inspect.json\")),\n\t\t)\n\t}\n\t// run and collect up all errors\n\treturn errors.AggregateConcurrent(fns)\n}\n\n// Info returns the provider info.\n// The info is cached on the first time of the execution.\nfunc (p *provider) Info() (*providers.ProviderInfo, error) {\n\tif p.info == nil {\n\t\tvar err error\n\t\tp.info, err = info(p.logger)\n\t\tif err != nil {\n\t\t\treturn p.info, err\n\t\t}\n\t}\n\treturn p.info, nil\n}\n\n// podmanInfo corresponds to `podman info --format 'json`.\n// The structure is different from `docker info --format '{{json .}}'`,\n// and lacks information about the availability of the cgroup controllers.\ntype podmanInfo struct {\n\tHost struct {\n\t\tCgroupVersion     string   `json:\"cgroupVersion,omitempty\"` // \"v2\"\n\t\tCgroupControllers []string `json:\"cgroupControllers,omitempty\"`\n\t\tSecurity          struct {\n\t\t\tRootless bool `json:\"rootless,omitempty\"`\n\t\t} `json:\"security\"`\n\t} `json:\"host\"`\n}\n\n// info detects ProviderInfo by executing `podman info --format json`.\nfunc info(logger log.Logger) (*providers.ProviderInfo, error) {\n\tconst podman = \"podman\"\n\targs := []string{\"info\", \"--format\", \"json\"}\n\tcmd := exec.Command(podman, args...)\n\tout, err := exec.Output(cmd)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to get podman info (%s %s): %q\",\n\t\t\tpodman, strings.Join(args, \" \"), string(out))\n\t}\n\tvar pInfo podmanInfo\n\tif err := json.Unmarshal(out, &pInfo); err != nil {\n\t\treturn nil, err\n\t}\n\tstringSliceContains := func(s []string, str string) bool {\n\t\tfor _, v := range s {\n\t\t\tif v == str {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\t// Since Podman version before v4.0.0 does not gives controller info.\n\t// We assume all the cgroup controllers to be available.\n\t// For rootless, this assumption is not always correct,\n\t// so we print the warning below.\n\tcgroupSupportsMemoryLimit := true\n\tcgroupSupportsPidsLimit := true\n\tcgroupSupportsCPUShares := true\n\n\tv, err := getPodmanVersion()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to check podman version\")\n\t}\n\t// Info for controllers must be available after v4.0.0\n\t// via https://github.com/containers/podman/pull/10387\n\tif v.AtLeast(version.MustParseSemantic(\"4.0.0\")) {\n\t\tcgroupSupportsMemoryLimit = stringSliceContains(pInfo.Host.CgroupControllers, \"memory\")\n\t\tcgroupSupportsPidsLimit = stringSliceContains(pInfo.Host.CgroupControllers, \"pids\")\n\t\tcgroupSupportsCPUShares = stringSliceContains(pInfo.Host.CgroupControllers, \"cpu\")\n\t}\n\n\tinfo := &providers.ProviderInfo{\n\t\tRootless:            pInfo.Host.Security.Rootless,\n\t\tCgroup2:             pInfo.Host.CgroupVersion == \"v2\",\n\t\tSupportsMemoryLimit: cgroupSupportsMemoryLimit,\n\t\tSupportsPidsLimit:   cgroupSupportsPidsLimit,\n\t\tSupportsCPUShares:   cgroupSupportsCPUShares,\n\t}\n\tif info.Rootless && !v.AtLeast(version.MustParseSemantic(\"4.0.0\")) {\n\t\tif logger != nil {\n\t\t\tlogger.Warn(\"Cgroup controller detection is not implemented for Podman. \" +\n\t\t\t\t\"If you see cgroup-related errors, you might need to set systemd property \\\"Delegate=yes\\\", see https://kind.sigs.k8s.io/docs/user/rootless/\")\n\t\t}\n\t}\n\treturn info, nil\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/provision.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// planCreation creates a slice of funcs that will create the containers\nfunc planCreation(cfg *config.Cluster, networkName string) (createContainerFuncs []func() error, err error) {\n\t// these apply to all container creation\n\tnodeNamer := common.MakeNodeNamer(cfg.Name)\n\tnames := make([]string, len(cfg.Nodes))\n\tfor i, node := range cfg.Nodes {\n\t\tname := nodeNamer(string(node.Role)) // name the node\n\t\tnames[i] = name\n\t}\n\thaveLoadbalancer := config.ClusterHasImplicitLoadBalancer(cfg)\n\tif haveLoadbalancer {\n\t\tnames = append(names, nodeNamer(constants.ExternalLoadBalancerNodeRoleValue))\n\t}\n\tgenericArgs, err := commonArgs(cfg, networkName, names)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// only the external LB should reflect the port if we have multiple control planes\n\tapiServerPort := cfg.Networking.APIServerPort\n\tapiServerAddress := cfg.Networking.APIServerAddress\n\tif config.ClusterHasImplicitLoadBalancer(cfg) {\n\t\t// TODO: picking ports locally is less than ideal with a remote runtime\n\t\t// (does podman have this?)\n\t\t// but this is supposed to be an implementation detail and NOT picking\n\t\t// them breaks host reboot ...\n\t\t// For now remote podman + multi control plane is not supported\n\t\tapiServerPort = 0              // replaced with random ports\n\t\tapiServerAddress = \"127.0.0.1\" // only the LB needs to be non-local\n\t\t// only for IPv6 only clusters\n\t\tif cfg.Networking.IPFamily == config.IPv6Family {\n\t\t\tapiServerAddress = \"::1\" // only the LB needs to be non-local\n\t\t}\n\t\t// plan loadbalancer node\n\t\tname := names[len(names)-1]\n\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\targs, err := runArgsForLoadBalancer(cfg, name, genericArgs)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn createContainer(name, args)\n\t\t})\n\t}\n\n\t// plan normal nodes\n\tfor i, node := range cfg.Nodes {\n\t\tnode := node.DeepCopy() // copy so we can modify\n\t\tname := names[i]\n\n\t\t// fixup relative paths, podman can only handle absolute paths\n\t\tfor i := range node.ExtraMounts {\n\t\t\thostPath := node.ExtraMounts[i].HostPath\n\t\t\tabsHostPath, err := filepath.Abs(hostPath)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.Wrapf(err, \"unable to resolve absolute path for hostPath: %q\", hostPath)\n\t\t\t}\n\t\t\tnode.ExtraMounts[i].HostPath = absHostPath\n\t\t}\n\n\t\t// plan actual creation based on role\n\t\tswitch node.Role {\n\t\tcase config.ControlPlaneRole:\n\t\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\t\tnode.ExtraPortMappings = append(node.ExtraPortMappings,\n\t\t\t\t\tconfig.PortMapping{\n\t\t\t\t\t\tListenAddress: apiServerAddress,\n\t\t\t\t\t\tHostPort:      apiServerPort,\n\t\t\t\t\t\tContainerPort: common.APIServerInternalPort,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\targs, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args)\n\t\t\t})\n\t\tcase config.WorkerRole:\n\t\t\tcreateContainerFuncs = append(createContainerFuncs, func() error {\n\t\t\t\targs, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args)\n\t\t\t})\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"unknown node role: %q\", node.Role)\n\t\t}\n\t}\n\treturn createContainerFuncs, nil\n}\n\n// commonArgs computes static arguments that apply to all containers\nfunc commonArgs(cfg *config.Cluster, networkName string, nodeNames []string) ([]string, error) {\n\t// standard arguments all nodes containers need, computed once\n\targs := []string{\n\t\t\"--detach\",           // run the container detached\n\t\t\"--tty\",              // allocate a tty for entrypoint logs\n\t\t\"--net\", networkName, // attach to its own network\n\t\t// label the node with the cluster ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", clusterLabelKey, cfg.Name),\n\t\t// specify container implementation to systemd\n\t\t\"-e\", \"container=podman\",\n\t\t// this is the default in cgroupsv2 but not in v1\n\t\t\"--cgroupns=private\",\n\t}\n\n\t// enable IPv6 if necessary\n\tif config.ClusterHasIPv6(cfg) {\n\t\targs = append(args, \"--sysctl=net.ipv6.conf.all.disable_ipv6=0\", \"--sysctl=net.ipv6.conf.all.forwarding=1\")\n\t}\n\n\t// pass proxy environment variables\n\tproxyEnv, err := getProxyEnv(cfg, networkName, nodeNames)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"proxy setup error\")\n\t}\n\tfor key, val := range proxyEnv {\n\t\targs = append(args, \"-e\", fmt.Sprintf(\"%s=%s\", key, val))\n\t}\n\n\t// handle Podman on Btrfs or ZFS same as we do with Docker\n\t// https://github.com/kubernetes-sigs/kind/issues/1416#issuecomment-606514724\n\tif mountDevMapper() {\n\t\targs = append(args, \"--volume\", \"/dev/mapper:/dev/mapper\")\n\t}\n\n\t// rootless: use fuse-overlayfs by default\n\t// https://github.com/kubernetes-sigs/kind/issues/2275\n\tif mountFuse() {\n\t\targs = append(args, \"--device\", \"/dev/fuse\")\n\t}\n\n\tif cfg.Networking.DNSSearch != nil {\n\t\targs = append(args, \"-e\", \"KIND_DNS_SEARCH=\"+strings.Join(*cfg.Networking.DNSSearch, \" \"))\n\t}\n\n\treturn args, nil\n}\n\nfunc runArgsForNode(node *config.Node, clusterIPFamily config.ClusterIPFamily, name string, args []string) ([]string, error) {\n\t// Pre-create anonymous volumes to enable specifying mount options\n\t// during container run time\n\tvarVolume, err := createAnonymousVolume(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\targs = append([]string{\n\t\t\"--hostname\", name, // make hostname match container name\n\t\t// label the node with the role ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", nodeRoleLabelKey, node.Role),\n\t\t// running containers in a container requires privileged\n\t\t// NOTE: we could try to replicate this with --cap-add, and use less\n\t\t// privileges, but this flag also changes some mounts that are necessary\n\t\t// including some ones podman would otherwise do by default.\n\t\t// for now this is what we want. in the future we may revisit this.\n\t\t\"--privileged\",\n\t\t// runtime temporary storage\n\t\t\"--tmpfs\", \"/tmp\", // various things depend on working /tmp\n\t\t\"--tmpfs\", \"/run\", // systemd wants a writable /run\n\t\t// runtime persistent storage\n\t\t// this ensures that E.G. pods, logs etc. are not on the container\n\t\t// filesystem, which is not only better for performance, but allows\n\t\t// running kind in kind for \"party tricks\"\n\t\t// (please don't depend on doing this though!)\n\t\t// also enable default docker volume options\n\t\t// suid: SUID applications on the volume will be able to change their privilege\n\t\t// exec: executables on the volume will be able to executed within the container\n\t\t// dev: devices on the volume will be able to be used by processes within the container\n\t\t\"--volume\", fmt.Sprintf(\"%s:/var:suid,exec,dev\", varVolume),\n\t\t// some k8s things want to read /lib/modules\n\t\t\"--volume\", \"/lib/modules:/lib/modules:ro\",\n\t\t// propagate KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER to the entrypoint script\n\t\t\"-e\", \"KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER\",\n\t},\n\t\targs...,\n\t)\n\n\t// convert mounts and port mappings to container run args\n\targs = append(args, generateMountBindings(node.ExtraMounts...)...)\n\tmappingArgs, err := generatePortMappings(clusterIPFamily, node.ExtraPortMappings...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, mappingArgs...)\n\n\tswitch node.Role {\n\tcase config.ControlPlaneRole:\n\t\targs = append(args, \"-e\", \"KUBECONFIG=/etc/kubernetes/admin.conf\")\n\t}\n\n\t// finally, specify the image to run\n\t_, image := sanitizeImage(node.Image)\n\treturn append(args, image), nil\n}\n\nfunc runArgsForLoadBalancer(cfg *config.Cluster, name string, args []string) ([]string, error) {\n\targs = append([]string{\n\t\t\"--hostname\", name, // make hostname match container name\n\t\t// label the node with the role ID\n\t\t\"--label\", fmt.Sprintf(\"%s=%s\", nodeRoleLabelKey, constants.ExternalLoadBalancerNodeRoleValue),\n\t},\n\t\targs...,\n\t)\n\n\t// load balancer port mapping\n\tmappingArgs, err := generatePortMappings(cfg.Networking.IPFamily,\n\t\tconfig.PortMapping{\n\t\t\tListenAddress: cfg.Networking.APIServerAddress,\n\t\t\tHostPort:      cfg.Networking.APIServerPort,\n\t\t\tContainerPort: common.APIServerInternalPort,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\targs = append(args, mappingArgs...)\n\n\t// finally, specify the image to run\n\t_, image := sanitizeImage(loadbalancer.Image)\n\treturn append(args, image), nil\n}\n\nfunc getProxyEnv(cfg *config.Cluster, networkName string, nodeNames []string) (map[string]string, error) {\n\tenvs := common.GetProxyEnvs(cfg)\n\t// Specifically add the podman network subnets to NO_PROXY if we are using a proxy\n\tif len(envs) > 0 {\n\t\t// kind default bridge is \"kind\"\n\t\tsubnets, err := getSubnets(networkName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnoProxyList := append(subnets, envs[common.NOProxy])\n\t\tnoProxyList = append(noProxyList, nodeNames...)\n\t\t// Add pod,service and all the cluster nodes' dns names to no_proxy to allow in cluster\n\t\t// Note: this is best effort based on the default CoreDNS spec\n\t\t// https://github.com/kubernetes/dns/blob/master/docs/specification.md\n\t\t// Any user created pod/service hostnames, namespaces, custom DNS services\n\t\t// are expected to be no-proxied by the user explicitly.\n\n\t\tnoProxyList = append(noProxyList, \".svc\", \".svc.cluster\", \".svc.cluster.local\")\n\t\tnoProxyJoined := strings.Join(noProxyList, \",\")\n\t\tenvs[common.NOProxy] = noProxyJoined\n\t\tenvs[strings.ToLower(common.NOProxy)] = noProxyJoined\n\t}\n\treturn envs, nil\n}\n\ntype podmanNetworks []struct {\n\t// v4+\n\tSubnets []struct {\n\t\tSubnet  string `json:\"subnet\"`\n\t\tGateway string `json:\"gateway\"`\n\t} `json:\"subnets\"`\n\t// v3 and anything still using CNI/IPAM\n\tPlugins []struct {\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} `json:\"ipam,omitempty\"`\n\t} `json:\"plugins\"`\n}\n\nfunc getSubnets(networkName string) ([]string, error) {\n\tcmd := exec.Command(\"podman\", \"network\", \"inspect\", networkName)\n\tout, err := exec.Output(cmd)\n\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to get subnets\")\n\t}\n\n\tnetworks := podmanNetworks{}\n\tjsonErr := json.Unmarshal([]byte(out), &networks)\n\tif jsonErr != nil {\n\t\treturn nil, errors.Wrap(jsonErr, \"failed to get subnets\")\n\t}\n\tsubnets := []string{}\n\tfor _, network := range networks {\n\t\tif len(network.Subnets) > 0 {\n\t\t\tfor _, subnet := range network.Subnets {\n\t\t\t\tsubnets = append(subnets, subnet.Subnet)\n\t\t\t}\n\t\t}\n\t\tif len(network.Plugins) > 0 {\n\t\t\tfor _, plugin := range network.Plugins {\n\t\t\t\tfor _, r := range plugin.Ipam.Ranges {\n\t\t\t\t\tfor _, rr := range r {\n\t\t\t\t\t\tsubnets = append(subnets, rr.Subnet)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn subnets, nil\n}\n\n// generateMountBindings converts the mount list to a list of args for podman\n// '<HostPath>:<ContainerPath>[:options]', where 'options'\n// is a comma-separated list of the following strings:\n// 'ro', if the path is read only\n// 'Z', if the volume requires SELinux relabeling\nfunc generateMountBindings(mounts ...config.Mount) []string {\n\targs := make([]string, 0, len(mounts))\n\tfor _, m := range mounts {\n\t\tbind := fmt.Sprintf(\"%s:%s\", m.HostPath, m.ContainerPath)\n\t\tvar attrs []string\n\t\tif m.Readonly {\n\t\t\tattrs = append(attrs, \"ro\")\n\t\t}\n\t\t// Only request relabeling if the pod provides an SELinux context. If the pod\n\t\t// does not provide an SELinux context relabeling will label the volume with\n\t\t// the container's randomly allocated MCS label. This would restrict access\n\t\t// to the volume to the container which mounts it first.\n\t\tif m.SelinuxRelabel {\n\t\t\tattrs = append(attrs, \"Z\")\n\t\t}\n\t\tswitch m.Propagation {\n\t\tcase config.MountPropagationNone:\n\t\t\t// noop, private is default\n\t\tcase config.MountPropagationBidirectional:\n\t\t\tattrs = append(attrs, \"rshared\")\n\t\tcase config.MountPropagationHostToContainer:\n\t\t\tattrs = append(attrs, \"rslave\")\n\t\tdefault: // Falls back to \"private\"\n\t\t}\n\t\tif len(attrs) > 0 {\n\t\t\tbind = fmt.Sprintf(\"%s:%s\", bind, strings.Join(attrs, \",\"))\n\t\t}\n\t\targs = append(args, fmt.Sprintf(\"--volume=%s\", bind))\n\t}\n\treturn args\n}\n\n// generatePortMappings converts the portMappings list to a list of args for podman\nfunc generatePortMappings(clusterIPFamily config.ClusterIPFamily, portMappings ...config.PortMapping) ([]string, error) {\n\targs := make([]string, 0, len(portMappings))\n\tfor _, pm := range portMappings {\n\t\t// do provider internal defaulting\n\t\t// in a future API revision we will handle this at the API level and remove this\n\t\tif pm.ListenAddress == \"\" {\n\t\t\tswitch clusterIPFamily {\n\t\t\tcase config.IPv4Family, config.DualStackFamily:\n\t\t\t\tpm.ListenAddress = \"0.0.0.0\"\n\t\t\tcase config.IPv6Family:\n\t\t\t\tpm.ListenAddress = \"::\"\n\t\t\tdefault:\n\t\t\t\treturn nil, errors.Errorf(\"unknown cluster IP family: %v\", clusterIPFamily)\n\t\t\t}\n\t\t}\n\t\tif string(pm.Protocol) == \"\" {\n\t\t\tpm.Protocol = config.PortMappingProtocolTCP // TCP is the default\n\t\t}\n\n\t\t// validate that the provider can handle this binding\n\t\tswitch pm.Protocol {\n\t\tcase config.PortMappingProtocolTCP:\n\t\tcase config.PortMappingProtocolUDP:\n\t\tcase config.PortMappingProtocolSCTP:\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"unknown port mapping protocol: %v\", pm.Protocol)\n\t\t}\n\n\t\t// get a random port if necessary (port = 0)\n\t\thostPort, releaseHostPortFn, err := common.PortOrGetFreePort(pm.HostPort, pm.ListenAddress)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"failed to get random host port for port mapping\")\n\t\t}\n\t\tif releaseHostPortFn != nil {\n\t\t\tdefer releaseHostPortFn()\n\t\t}\n\n\t\t// generate the actual mapping arg\n\t\tprotocol := string(pm.Protocol)\n\t\thostPortBinding := net.JoinHostPort(pm.ListenAddress, fmt.Sprintf(\"%d\", hostPort))\n\t\t// Podman expects empty string instead of 0 to assign a random port\n\t\t// https://github.com/containers/libpod/blob/master/pkg/spec/ports.go#L68-L69\n\t\tif strings.HasSuffix(hostPortBinding, \":0\") {\n\t\t\thostPortBinding = strings.TrimSuffix(hostPortBinding, \"0\")\n\t\t}\n\t\targs = append(args, fmt.Sprintf(\"--publish=%s:%d/%s\", hostPortBinding, pm.ContainerPort, strings.ToLower(protocol)))\n\t}\n\treturn args, nil\n}\n\nfunc createContainer(name string, args []string) error {\n\treturn exec.Command(\"podman\", append([]string{\"run\", \"--name\", name}, args...)...).Run()\n}\n\nfunc createContainerWithWaitUntilSystemdReachesMultiUserSystem(name string, args []string) error {\n\tif err := exec.Command(\"podman\", append([]string{\"run\", \"--name\", name}, args...)...).Run(); err != nil {\n\t\treturn err\n\t}\n\n\tlogCtx, logCancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer logCancel()\n\tlogCmd := exec.CommandContext(logCtx, \"podman\", \"logs\", \"-f\", name)\n\treturn common.WaitUntilLogRegexpMatches(logCtx, logCmd, common.NodeReachedCgroupsReadyRegexp())\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/podman/util.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage podman\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/version\"\n)\n\n// IsAvailable checks if podman is available in the system\nfunc IsAvailable() bool {\n\tcmd := exec.Command(\"podman\", \"-v\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil || len(lines) != 1 {\n\t\treturn false\n\t}\n\treturn strings.HasPrefix(lines[0], \"podman version\")\n}\n\nfunc getPodmanVersion() (*version.Version, error) {\n\tcmd := exec.Command(\"podman\", \"--version\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// output is like `podman version 1.7.1-dev`\n\tif len(lines) != 1 {\n\t\treturn nil, errors.Errorf(\"podman version should only be one line, got %d\", len(lines))\n\t}\n\tparts := strings.Split(lines[0], \" \")\n\tif len(parts) != 3 {\n\t\treturn nil, errors.Errorf(\"podman --version contents should have 3 parts, got %q\", lines[0])\n\t}\n\treturn version.ParseSemantic(parts[2])\n}\n\nconst (\n\tminSupportedVersion = \"1.8.0\"\n)\n\nfunc ensureMinVersion() error {\n\t// ensure that podman version is a compatible version\n\tv, err := getPodmanVersion()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to check podman version\")\n\t}\n\tif !v.AtLeast(version.MustParseSemantic(minSupportedVersion)) {\n\t\treturn errors.Errorf(\"podman version %q is too old, please upgrade to %q or later\", v, minSupportedVersion)\n\t}\n\treturn nil\n}\n\n// createAnonymousVolume creates a new anonymous volume\n// with the specified label=true\n// returns the name of the volume created\nfunc createAnonymousVolume(label string) (string, error) {\n\tcmd := exec.Command(\"podman\",\n\t\t\"volume\",\n\t\t\"create\",\n\t\t// podman only support filter on key during list\n\t\t// so we use the unique id as key\n\t\t\"--label\", fmt.Sprintf(\"%s=true\", label))\n\tname, err := exec.Output(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn strings.TrimSuffix(string(name), \"\\n\"), nil\n}\n\n// getVolumes gets volume names filtered on specified label\nfunc getVolumes(label string) ([]string, error) {\n\tcmd := exec.Command(\"podman\",\n\t\t\"volume\",\n\t\t\"ls\",\n\t\t\"--filter\", fmt.Sprintf(\"label=%s\", label),\n\t\t\"--quiet\")\n\t// `output` from the above command is names of all volumes each followed by `\\n`.\n\toutput, err := exec.Output(cmd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif string(output) == \"\" {\n\t\t// no volumes\n\t\treturn nil, nil\n\t}\n\t// Trim away the last `\\n`.\n\ttrimmedOutput := strings.TrimSuffix(string(output), \"\\n\")\n\t// Get names of all volumes by splitting via `\\n`.\n\treturn strings.Split(string(trimmedOutput), \"\\n\"), nil\n}\n\nfunc deleteVolumes(names []string) error {\n\targs := []string{\n\t\t\"volume\",\n\t\t\"rm\",\n\t\t\"--force\",\n\t}\n\targs = append(args, names...)\n\tcmd := exec.Command(\"podman\", args...)\n\treturn cmd.Run()\n}\n\n// mountDevMapper checks if the podman storage driver is Btrfs or ZFS\nfunc mountDevMapper() bool {\n\tcmd := exec.Command(\"podman\", \"info\", \"--format\", \"json\")\n\tout, err := exec.Output(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tvar pInfo podmanStorageInfo\n\tif err := json.Unmarshal(out, &pInfo); err != nil {\n\t\treturn false\n\t}\n\n\t// match docker logic pkg/cluster/internal/providers/docker/util.go\n\tif pInfo.Store.GraphDriverName == \"btrfs\" ||\n\t\tpInfo.Store.GraphDriverName == \"zfs\" ||\n\t\tpInfo.Store.GraphDriverName == \"devicemapper\" ||\n\t\tpInfo.Store.GraphStatus.BackingFilesystem == \"btrfs\" ||\n\t\tpInfo.Store.GraphStatus.BackingFilesystem == \"xfs\" ||\n\t\tpInfo.Store.GraphStatus.BackingFilesystem == \"zfs\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\ntype podmanStorageInfo struct {\n\tStore struct {\n\t\tGraphDriverName string `json:\"graphDriverName,omitempty\"`\n\t\tGraphStatus     struct {\n\t\t\tBackingFilesystem string `json:\"Backing Filesystem,omitempty\"` // \"v2\"\n\t\t} `json:\"graphStatus\"`\n\t} `json:\"store\"`\n}\n\n// rootless: use fuse-overlayfs by default\n// https://github.com/kubernetes-sigs/kind/issues/2275\nfunc mountFuse() bool {\n\ti, err := info(nil)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif i != nil && i.Rootless {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/cluster/internal/providers/provider.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package providers contains the standard interface implemented by\n// different kind providers.\npackage providers\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n)\n\n// Provider represents a provider of cluster / node infrastructure\n// This is an alpha-grade internal API\ntype Provider interface {\n\t// Provision should create and start the nodes, just short of\n\t// actually starting up Kubernetes, based on the given cluster config\n\tProvision(status *cli.Status, cfg *config.Cluster) error\n\t// ListClusters discovers the clusters that currently have resources\n\t// under this providers\n\tListClusters() ([]string, error)\n\t// ListNodes returns the nodes under this provider for the given\n\t// cluster name, they may or may not be running correctly\n\tListNodes(cluster string) ([]nodes.Node, error)\n\t// DeleteNodes deletes the provided list of nodes\n\t// These should be from results previously returned by this provider\n\t// E.G. by ListNodes()\n\tDeleteNodes([]nodes.Node) error\n\t// GetAPIServerEndpoint returns the host endpoint for the cluster's API server\n\tGetAPIServerEndpoint(cluster string) (string, error)\n\t// GetAPIServerInternalEndpoint returns the internal network endpoint for the cluster's API server\n\tGetAPIServerInternalEndpoint(cluster string) (string, error)\n\t// CollectLogs will populate dir with cluster logs and other debug files\n\tCollectLogs(dir string, nodes []nodes.Node) error\n\t// Info returns the provider info\n\tInfo() (*ProviderInfo, error)\n}\n\n// ProviderInfo is the info of the provider\ntype ProviderInfo struct {\n\tRootless            bool\n\tCgroup2             bool\n\tSupportsMemoryLimit bool\n\tSupportsPidsLimit   bool\n\tSupportsCPUShares   bool\n}\n"
  },
  {
    "path": "pkg/cluster/nodes/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package nodes provides a kind specific definition of a cluster node\npackage nodes\n"
  },
  {
    "path": "pkg/cluster/nodes/types.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodes\n\nimport (\n\t\"io\"\n\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// Node represents a kind cluster node\ntype Node interface {\n\t// The node should implement exec.Cmder for running commands against the node\n\t// see: sigs.k8s.io/kind/pkg/exec\n\texec.Cmder\n\t// String should return the node name\n\tString() string // see also: fmt.Stringer\n\t// Role should return the node's role\n\tRole() (string, error) // see also: pkg/cluster/constants\n\t// TODO(bentheelder): should return node addresses more generally\n\t// Possibly remove this method in favor of obtaining this detail with\n\t// exec or from the provider\n\tIP() (ipv4 string, ipv6 string, err error)\n\t// SerialLogs collects the \"node\" container logs\n\tSerialLogs(writer io.Writer) error\n}\n"
  },
  {
    "path": "pkg/cluster/nodeutils/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package nodeutils contains functionality for Kubernetes-in-Docker nodes\n// It mostly exists to break up functionality from sigs.k8s.io/kind/pkg/cluster\npackage nodeutils\n"
  },
  {
    "path": "pkg/cluster/nodeutils/roles.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeutils\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// SelectNodesByRole returns a list of nodes with the matching role\n// TODO(bentheelder): remove this in favor of specific role select methods\n// and avoid the unnecessary error handling\nfunc SelectNodesByRole(allNodes []nodes.Node, role string) ([]nodes.Node, error) {\n\tout := []nodes.Node{}\n\tfor _, node := range allNodes {\n\t\tnodeRole, err := node.Role()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif nodeRole == role {\n\t\t\tout = append(out, node)\n\t\t}\n\t}\n\treturn out, nil\n}\n\n// InternalNodes returns the list of container IDs for the \"nodes\" in the cluster\n// that are ~Kubernetes nodes, as opposed to e.g. the external loadbalancer for HA\nfunc InternalNodes(allNodes []nodes.Node) ([]nodes.Node, error) {\n\tselectedNodes := []nodes.Node{}\n\tfor _, node := range allNodes {\n\t\tnodeRole, err := node.Role()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif nodeRole == constants.WorkerNodeRoleValue || nodeRole == constants.ControlPlaneNodeRoleValue {\n\t\t\tselectedNodes = append(selectedNodes, node)\n\t\t}\n\t}\n\treturn selectedNodes, nil\n}\n\n// ExternalLoadBalancerNode returns a node handle for the external control plane\n// loadbalancer node or nil if there isn't one\nfunc ExternalLoadBalancerNode(allNodes []nodes.Node) (nodes.Node, error) {\n\t// identify and validate external load balancer node\n\tloadBalancerNodes, err := SelectNodesByRole(\n\t\tallNodes,\n\t\tconstants.ExternalLoadBalancerNodeRoleValue,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(loadBalancerNodes) < 1 {\n\t\treturn nil, nil\n\t}\n\tif len(loadBalancerNodes) > 1 {\n\t\treturn nil, errors.Errorf(\n\t\t\t\"unexpected number of %s nodes %d\",\n\t\t\tconstants.ExternalLoadBalancerNodeRoleValue,\n\t\t\tlen(loadBalancerNodes),\n\t\t)\n\t}\n\treturn loadBalancerNodes[0], nil\n}\n\n// APIServerEndpointNode selects the node from allNodes which hosts the API Server endpoint\n// This should be the control plane node if there is one control plane node, or a LoadBalancer otherwise.\n// It returns an error if the node list is invalid (E.G. two control planes and no load balancer)\nfunc APIServerEndpointNode(allNodes []nodes.Node) (nodes.Node, error) {\n\tif n, err := ExternalLoadBalancerNode(allNodes); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to find api-server endpoint node\")\n\t} else if n != nil {\n\t\treturn n, nil\n\t}\n\tn, err := ControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to find api-server endpoint node\")\n\t}\n\tif len(n) != 1 {\n\t\treturn nil, errors.Errorf(\"expected one control plane node or a load balancer, not %d and none\", len(n))\n\t}\n\treturn n[0], nil\n}\n\n// ControlPlaneNodes returns all control plane nodes such that the first entry\n// is the bootstrap control plane node\nfunc ControlPlaneNodes(allNodes []nodes.Node) ([]nodes.Node, error) {\n\tcontrolPlaneNodes, err := SelectNodesByRole(\n\t\tallNodes,\n\t\tconstants.ControlPlaneNodeRoleValue,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// pick the first by sorting\n\t// TODO(bentheelder): perhaps in the future we should mark this node\n\t// specially at container creation time\n\tsort.Slice(controlPlaneNodes, func(i, j int) bool {\n\t\treturn strings.Compare(controlPlaneNodes[i].String(), controlPlaneNodes[j].String()) < 0\n\t})\n\treturn controlPlaneNodes, nil\n}\n\n// BootstrapControlPlaneNode returns a handle to the bootstrap control plane node\n// TODO(bentheelder): remove this. This node shouldn't be special (fix that first)\nfunc BootstrapControlPlaneNode(allNodes []nodes.Node) (nodes.Node, error) {\n\tcontrolPlaneNodes, err := ControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(controlPlaneNodes) < 1 {\n\t\treturn nil, errors.Errorf(\n\t\t\t\"expected at least one %s node\",\n\t\t\tconstants.ControlPlaneNodeRoleValue,\n\t\t)\n\t}\n\treturn controlPlaneNodes[0], nil\n}\n\n// SecondaryControlPlaneNodes returns handles to the secondary\n// control plane nodes and NOT the bootstrap control plane node\nfunc SecondaryControlPlaneNodes(allNodes []nodes.Node) ([]nodes.Node, error) {\n\tcontrolPlaneNodes, err := ControlPlaneNodes(allNodes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(controlPlaneNodes) < 1 {\n\t\treturn nil, errors.Errorf(\n\t\t\t\"expected at least one %s node\",\n\t\t\tconstants.ControlPlaneNodeRoleValue,\n\t\t)\n\t}\n\treturn controlPlaneNodes[1:], nil\n}\n"
  },
  {
    "path": "pkg/cluster/nodeutils/util.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeutils\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/pelletier/go-toml\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n)\n\n// KubeVersion returns the Kubernetes version installed on the node\nfunc KubeVersion(n nodes.Node) (version string, err error) {\n\t// grab kubernetes version from the node image\n\tcmd := n.Command(\"cat\", \"/kind/version\")\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get file\")\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"file should only be one line, got %d lines\", len(lines))\n\t}\n\treturn lines[0], nil\n}\n\n// WriteFile writes content to dest on the node\nfunc WriteFile(n nodes.Node, dest, content string) error {\n\t// create destination directory\n\terr := n.Command(\"mkdir\", \"-p\", path.Dir(dest)).Run()\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to create directory %s\", path.Dir(dest))\n\t}\n\n\treturn n.Command(\"cp\", \"/dev/stdin\", dest).SetStdin(strings.NewReader(content)).Run()\n}\n\n// CopyNodeToNode copies file from a to b\nfunc CopyNodeToNode(a, b nodes.Node, file string) error {\n\t// create destination directory\n\terr := b.Command(\"mkdir\", \"-p\", path.Dir(file)).Run()\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to create directory %q\", path.Dir(file))\n\t}\n\n\t// TODO: experiment with streaming instead to avoid the copy\n\t// for now we only use this for small files so it's not worth the complexity\n\tvar buff bytes.Buffer\n\tif err := a.Command(\"cat\", file).SetStdout(&buff).Run(); err != nil {\n\t\treturn errors.Wrapf(err, \"failed to read %q from node\", file)\n\t}\n\tif err := b.Command(\"cp\", \"/dev/stdin\", file).SetStdin(&buff).Run(); err != nil {\n\t\treturn errors.Wrapf(err, \"failed to write %q to node\", file)\n\t}\n\n\treturn nil\n}\n\n// LoadImageArchive loads image onto the node, where image is a Reader over an image archive\nfunc LoadImageArchive(n nodes.Node, image io.Reader) error {\n\tsnapshotter, err := getSnapshotter(n)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd := n.Command(\"ctr\", \"--namespace=k8s.io\", \"images\", \"import\", \"--all-platforms\", \"--digests\", \"--snapshotter=\"+snapshotter, \"-\").SetStdin(image)\n\tif err := cmd.Run(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to load image\")\n\t}\n\treturn nil\n}\n\nfunc getSnapshotter(n nodes.Node) (string, error) {\n\tout, err := exec.Output(n.Command(\"containerd\", \"config\", \"dump\"))\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to detect containerd snapshotter\")\n\t}\n\treturn parseSnapshotter(string(out))\n}\n\nfunc parseSnapshotter(config string) (string, error) {\n\tparsed, err := toml.Load(config)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to detect containerd snapshotter\")\n\t}\n\tconfigVersion, ok := parsed.Get(\"version\").(int64)\n\tif !ok {\n\t\treturn \"\", errors.New(\"failed to detect containerd config version\")\n\t}\n\tvar snapshotter string\n\tswitch configVersion {\n\tcase 2: // Introduced in containerd v1.3. Still supported in containerd v2.\n\t\tsnapshotter, ok = parsed.GetPath([]string{\"plugins\", \"io.containerd.grpc.v1.cri\", \"containerd\", \"snapshotter\"}).(string)\n\t\tif !ok {\n\t\t\treturn \"\", errors.New(\"failed to detect containerd snapshotter (config version 2)\")\n\t\t}\n\tcase 3: // Introduced in containerd v2.0.\n\t\tsnapshotter, ok = parsed.GetPath([]string{\"plugins\", \"io.containerd.cri.v1.images\", \"snapshotter\"}).(string)\n\t\tif !ok {\n\t\t\treturn \"\", errors.New(\"failed to detect containerd snapshotter (config version 3)\")\n\t\t}\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unknown containerd config version: %d (supported versions: 2 and 3)\", configVersion)\n\t}\n\treturn snapshotter, nil\n}\n\n// ImageID returns ID of image on the node with the given image name if present\nfunc ImageID(n nodes.Node, image string) (string, error) {\n\tvar out bytes.Buffer\n\tif err := n.Command(\"crictl\", \"inspecti\", image).SetStdout(&out).Run(); err != nil {\n\t\treturn \"\", err\n\t}\n\t// we only care about the image ID\n\tcrictlOut := struct {\n\t\tStatus struct {\n\t\t\tID string `json:\"id\"`\n\t\t} `json:\"status\"`\n\t}{}\n\tif err := json.Unmarshal(out.Bytes(), &crictlOut); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn crictlOut.Status.ID, nil\n}\n\n// ImageTags is used to perform a reverse lookup of the ImageID to list set of available\n// RepoTags corresponding to the ImageID in question\nfunc ImageTags(n nodes.Node, imageID string) (map[string]bool, error) {\n\tvar out bytes.Buffer\n\ttags := make(map[string]bool, 0)\n\tif err := n.Command(\"crictl\", \"inspecti\", imageID).SetStdout(&out).Run(); err != nil {\n\t\treturn tags, err\n\t}\n\tcrictlOut := struct {\n\t\tStatus struct {\n\t\t\tRepoTags []string `json:\"repoTags\"`\n\t\t} `json:\"status\"`\n\t}{}\n\tif err := json.Unmarshal(out.Bytes(), &crictlOut); err != nil {\n\t\treturn tags, err\n\t}\n\tfor _, tag := range crictlOut.Status.RepoTags {\n\t\ttags[tag] = true\n\t}\n\treturn tags, nil\n}\n\n// ReTagImage is used to tag an ImageID with a custom tag specified by imageName parameter\nfunc ReTagImage(n nodes.Node, imageID, imageName string) error {\n\tvar out bytes.Buffer\n\treturn n.Command(\"ctr\", \"--namespace=k8s.io\", \"images\", \"tag\", \"--force\", imageID, imageName).SetStdout(&out).Run()\n}\n"
  },
  {
    "path": "pkg/cluster/nodeutils/util_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage nodeutils\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParseSnapshotter(t *testing.T) {\n\t// a real, known kind node containerd config\n\tconfig := `disabled_plugins = []\n\timports = [\"/etc/containerd/config.toml\"]\n\toom_score = 0\n\tplugin_dir = \"\"\n\trequired_plugins = []\n\troot = \"/var/lib/containerd\"\n\tstate = \"/run/containerd\"\n\tversion = 2\n\t\n\t[cgroup]\n\t  path = \"\"\n\t\n\t[debug]\n\t  address = \"\"\n\t  format = \"\"\n\t  gid = 0\n\t  level = \"\"\n\t  uid = 0\n\t\n\t[grpc]\n\t  address = \"/run/containerd/containerd.sock\"\n\t  gid = 0\n\t  max_recv_message_size = 16777216\n\t  max_send_message_size = 16777216\n\t  tcp_address = \"\"\n\t  tcp_tls_cert = \"\"\n\t  tcp_tls_key = \"\"\n\t  uid = 0\n\t\n\t[metrics]\n\t  address = \"\"\n\t  grpc_histogram = false\n\t\n\t[plugins]\n\t\n\t  [plugins.\"io.containerd.gc.v1.scheduler\"]\n\t\tdeletion_threshold = 0\n\t\tmutation_threshold = 100\n\t\tpause_threshold = 0.02\n\t\tschedule_delay = \"0s\"\n\t\tstartup_delay = \"100ms\"\n\t\n\t  [plugins.\"io.containerd.grpc.v1.cri\"]\n\t\tdisable_apparmor = false\n\t\tdisable_cgroup = false\n\t\tdisable_hugetlb_controller = true\n\t\tdisable_proc_mount = false\n\t\tdisable_tcp_service = true\n\t\tenable_selinux = false\n\t\tenable_tls_streaming = false\n\t\tignore_image_defined_volumes = false\n\t\tmax_concurrent_downloads = 3\n\t\tmax_container_log_line_size = 16384\n\t\tnetns_mounts_under_state_dir = false\n\t\trestrict_oom_score_adj = false\n\t\tsandbox_image = \"registry.k8s.io/pause:3.7\"\n\t\tselinux_category_range = 1024\n\t\tstats_collect_period = 10\n\t\tstream_idle_timeout = \"4h0m0s\"\n\t\tstream_server_address = \"127.0.0.1\"\n\t\tstream_server_port = \"0\"\n\t\tsystemd_cgroup = false\n\t\ttolerate_missing_hugetlb_controller = true\n\t\tunset_seccomp_profile = \"\"\n\t\n\t\t[plugins.\"io.containerd.grpc.v1.cri\".cni]\n\t\t  bin_dir = \"/opt/cni/bin\"\n\t\t  conf_dir = \"/etc/cni/net.d\"\n\t\t  conf_template = \"\"\n\t\t  max_conf_num = 1\n\t\n\t\t[plugins.\"io.containerd.grpc.v1.cri\".containerd]\n\t\t  default_runtime_name = \"runc\"\n\t\t  disable_snapshot_annotations = true\n\t\t  discard_unpacked_layers = true\n\t\t  no_pivot = false\n\t\t  snapshotter = \"overlayfs\"\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".containerd.default_runtime]\n\t\t\tbase_runtime_spec = \"\"\n\t\t\tcontainer_annotations = []\n\t\t\tpod_annotations = []\n\t\t\tprivileged_without_host_devices = false\n\t\t\truntime_engine = \"\"\n\t\t\truntime_root = \"\"\n\t\t\truntime_type = \"\"\n\t\n\t\t\t[plugins.\"io.containerd.grpc.v1.cri\".containerd.default_runtime.options]\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes]\n\t\n\t\t\t[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc]\n\t\t\t  base_runtime_spec = \"/etc/containerd/cri-base.json\"\n\t\t\t  container_annotations = []\n\t\t\t  pod_annotations = []\n\t\t\t  privileged_without_host_devices = false\n\t\t\t  runtime_engine = \"\"\n\t\t\t  runtime_root = \"\"\n\t\t\t  runtime_type = \"io.containerd.runc.v2\"\n\t\n\t\t\t  [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.runc.options]\n\t\n\t\t\t[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.test-handler]\n\t\t\t  base_runtime_spec = \"\"\n\t\t\t  container_annotations = []\n\t\t\t  pod_annotations = []\n\t\t\t  privileged_without_host_devices = false\n\t\t\t  runtime_engine = \"\"\n\t\t\t  runtime_root = \"\"\n\t\t\t  runtime_type = \"io.containerd.runc.v2\"\n\t\n\t\t\t  [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.test-handler.options]\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".containerd.untrusted_workload_runtime]\n\t\t\tbase_runtime_spec = \"\"\n\t\t\tcontainer_annotations = []\n\t\t\tpod_annotations = []\n\t\t\tprivileged_without_host_devices = false\n\t\t\truntime_engine = \"\"\n\t\t\truntime_root = \"\"\n\t\t\truntime_type = \"\"\n\t\n\t\t\t[plugins.\"io.containerd.grpc.v1.cri\".containerd.untrusted_workload_runtime.options]\n\t\n\t\t[plugins.\"io.containerd.grpc.v1.cri\".image_decryption]\n\t\t  key_model = \"node\"\n\t\n\t\t[plugins.\"io.containerd.grpc.v1.cri\".registry]\n\t\t  config_path = \"\"\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".registry.auths]\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".registry.configs]\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".registry.headers]\n\t\n\t\t  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n\t\n\t\t[plugins.\"io.containerd.grpc.v1.cri\".x509_key_pair_streaming]\n\t\t  tls_cert_file = \"\"\n\t\t  tls_key_file = \"\"\n\t\n\t  [plugins.\"io.containerd.internal.v1.opt\"]\n\t\tpath = \"/opt/containerd\"\n\t\n\t  [plugins.\"io.containerd.internal.v1.restart\"]\n\t\tinterval = \"10s\"\n\t\n\t  [plugins.\"io.containerd.metadata.v1.bolt\"]\n\t\tcontent_sharing_policy = \"shared\"\n\t\n\t  [plugins.\"io.containerd.monitor.v1.cgroups\"]\n\t\tno_prometheus = false\n\t\n\t  [plugins.\"io.containerd.runtime.v1.linux\"]\n\t\tno_shim = false\n\t\truntime = \"runc\"\n\t\truntime_root = \"\"\n\t\tshim = \"containerd-shim\"\n\t\tshim_debug = false\n\t\n\t  [plugins.\"io.containerd.runtime.v2.task\"]\n\t\tplatforms = [\"linux/amd64\"]\n\t\n\t  [plugins.\"io.containerd.service.v1.diff-service\"]\n\t\tdefault = [\"walking\"]\n\t\n\t  [plugins.\"io.containerd.snapshotter.v1.aufs\"]\n\t\troot_path = \"\"\n\t\n\t  [plugins.\"io.containerd.snapshotter.v1.btrfs\"]\n\t\troot_path = \"\"\n\t\n\t  [plugins.\"io.containerd.snapshotter.v1.devmapper\"]\n\t\tasync_remove = false\n\t\tbase_image_size = \"\"\n\t\tpool_name = \"\"\n\t\troot_path = \"\"\n\t\n\t  [plugins.\"io.containerd.snapshotter.v1.native\"]\n\t\troot_path = \"\"\n\t\n\t  [plugins.\"io.containerd.snapshotter.v1.overlayfs\"]\n\t\troot_path = \"\"\n\t\n\t  [plugins.\"io.containerd.snapshotter.v1.zfs\"]\n\t\troot_path = \"\"\n\t\n\t[proxy_plugins]\n\t\n\t  [proxy_plugins.fuse-overlayfs]\n\t\taddress = \"/run/containerd-fuse-overlayfs.sock\"\n\t\ttype = \"snapshot\"\n\t\n\t[stream_processors]\n\t\n\t  [stream_processors.\"io.containerd.ocicrypt.decoder.v1.tar\"]\n\t\taccepts = [\"application/vnd.oci.image.layer.v1.tar+encrypted\"]\n\t\targs = [\"--decryption-keys-path\", \"/etc/containerd/ocicrypt/keys\"]\n\t\tenv = [\"OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf\"]\n\t\tpath = \"ctd-decoder\"\n\t\treturns = \"application/vnd.oci.image.layer.v1.tar\"\n\t\n\t  [stream_processors.\"io.containerd.ocicrypt.decoder.v1.tar.gzip\"]\n\t\taccepts = [\"application/vnd.oci.image.layer.v1.tar+gzip+encrypted\"]\n\t\targs = [\"--decryption-keys-path\", \"/etc/containerd/ocicrypt/keys\"]\n\t\tenv = [\"OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf\"]\n\t\tpath = \"ctd-decoder\"\n\t\treturns = \"application/vnd.oci.image.layer.v1.tar+gzip\"\n\t\n\t[timeouts]\n\t  \"io.containerd.timeout.shim.cleanup\" = \"5s\"\n\t  \"io.containerd.timeout.shim.load\" = \"5s\"\n\t  \"io.containerd.timeout.shim.shutdown\" = \"3s\"\n\t  \"io.containerd.timeout.task.state\" = \"2s\"\n\t\n\t[ttrpc]\n\t  address = \"\"\n\t  gid = 0\n\t  uid = 0`\n\tsnapshotter, err := parseSnapshotter(config)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error parsing config: %v\", err)\n\t}\n\tif snapshotter != \"overlayfs\" {\n\t\tt.Fatalf(`unexpected parsed snapshotter: %q, expected \"overlayfs\"`, snapshotter)\n\t}\n\n\t// sanity check parsing an empty config\n\t_, err = parseSnapshotter(\"\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error parsing empty config\")\n\t}\n\n\t// sanity check parsing invalid toml\n\t_, err = parseSnapshotter(\"aaaa\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error parsing invalid config\")\n\t}\n}\n"
  },
  {
    "path": "pkg/cluster/provider.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cluster\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/version\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\tinternalcreate \"sigs.k8s.io/kind/pkg/cluster/internal/create\"\n\tinternaldelete \"sigs.k8s.io/kind/pkg/cluster/internal/delete\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig\"\n\tinternallogs \"sigs.k8s.io/kind/pkg/cluster/internal/logs\"\n\tinternalproviders \"sigs.k8s.io/kind/pkg/cluster/internal/providers\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/common\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/docker\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/nerdctl\"\n\t\"sigs.k8s.io/kind/pkg/cluster/internal/providers/podman\"\n)\n\n// DefaultName is the default cluster name\nconst DefaultName = constants.DefaultClusterName\n\n// defaultName is a helper that given a name defaults it if unset\nfunc defaultName(name string) string {\n\tif name == \"\" {\n\t\tname = DefaultName\n\t}\n\treturn name\n}\n\n// Provider is used to perform cluster operations\ntype Provider struct {\n\tprovider internalproviders.Provider\n\tlogger   log.Logger\n}\n\n// NewProvider returns a new provider based on the supplied options\nfunc NewProvider(options ...ProviderOption) *Provider {\n\tp := &Provider{\n\t\tlogger: log.NoopLogger{},\n\t}\n\t// Ensure we apply the logger options first, while maintaining the order\n\t// otherwise. This way we can trivially init the internal provider with\n\t// the logger.\n\tsort.SliceStable(options, func(i, j int) bool {\n\t\t_, iIsLogger := options[i].(providerLoggerOption)\n\t\t_, jIsLogger := options[j].(providerLoggerOption)\n\t\treturn iIsLogger && !jIsLogger\n\t})\n\tfor _, o := range options {\n\t\tif o != nil {\n\t\t\to.apply(p)\n\t\t}\n\t}\n\n\t// ensure a provider if none was set\n\t// NOTE: depends on logger being set (see sorting above)\n\tif p.provider == nil {\n\t\t// DetectNodeProvider does not fallback to allow callers to determine\n\t\t// this behavior\n\t\t// However for compatibility if the caller of NewProvider supplied no\n\t\t// option and we autodetect internally, we default to the docker provider\n\t\t// for fallback, to avoid a breaking change for now.\n\t\t// This may change in the future.\n\t\t// TODO: consider breaking this API for earlier errors.\n\t\tproviderOpt, _ := DetectNodeProvider()\n\t\tif providerOpt == nil {\n\t\t\tproviderOpt = ProviderWithDocker()\n\t\t}\n\t\tproviderOpt.apply(p)\n\t}\n\treturn p\n}\n\n// NoNodeProviderDetectedError indicates that we could not autolocate an available\n// NodeProvider backend on the host\nvar NoNodeProviderDetectedError = errors.NewWithoutStack(\"failed to detect any supported node provider\")\n\n// DetectNodeProvider allows callers to autodetect the node provider\n// *without* fallback to the default.\n//\n// Pass the returned ProviderOption to NewProvider to pass the auto-detect Docker\n// or Podman option explicitly (in the future there will be more options)\n//\n// NOTE: The kind *cli* also checks `KIND_EXPERIMENTAL_PROVIDER` for \"podman\",\n// \"nerctl\" or \"docker\" currently and does not auto-detect / respects this if set.\n//\n// This will be replaced with some other mechanism in the future (likely when\n// podman support is GA), in the meantime though your tool may wish to match this.\n//\n// In the future when this is not considered experimental,\n// that logic will be in a public API as well.\nfunc DetectNodeProvider() (ProviderOption, error) {\n\t// auto-detect based on each node provider's IsAvailable() function\n\tif docker.IsAvailable() {\n\t\treturn ProviderWithDocker(), nil\n\t}\n\tif nerdctl.IsAvailable() {\n\t\treturn ProviderWithNerdctl(\"\"), nil\n\t}\n\tif podman.IsAvailable() {\n\t\treturn ProviderWithPodman(), nil\n\t}\n\treturn nil, errors.WithStack(NoNodeProviderDetectedError)\n}\n\n// ProviderOption is an option for configuring a provider\ntype ProviderOption interface {\n\tapply(p *Provider)\n}\n\n// providerLoggerOption is a trivial ProviderOption adapter\n// we use a type specific to logging options so we can handle them first\ntype providerLoggerOption func(p *Provider)\n\nfunc (a providerLoggerOption) apply(p *Provider) {\n\ta(p)\n}\n\nvar _ ProviderOption = providerLoggerOption(nil)\n\n// ProviderWithLogger configures the provider to use Logger logger\nfunc ProviderWithLogger(logger log.Logger) ProviderOption {\n\treturn providerLoggerOption(func(p *Provider) {\n\t\tp.logger = logger\n\t})\n}\n\n// providerRuntimeOption is a trivial ProviderOption adapter\n// we use a type specific to logging options so we can handle them first\ntype providerRuntimeOption func(p *Provider)\n\nfunc (a providerRuntimeOption) apply(p *Provider) {\n\ta(p)\n}\n\nvar _ ProviderOption = providerRuntimeOption(nil)\n\n// ProviderWithDocker configures the provider to use docker runtime\nfunc ProviderWithDocker() ProviderOption {\n\treturn providerRuntimeOption(func(p *Provider) {\n\t\tp.provider = docker.NewProvider(p.logger)\n\t})\n}\n\n// ProviderWithPodman configures the provider to use podman runtime\nfunc ProviderWithPodman() ProviderOption {\n\treturn providerRuntimeOption(func(p *Provider) {\n\t\tp.provider = podman.NewProvider(p.logger)\n\t})\n}\n\n// ProviderWithNerdctl configures the provider to use the nerdctl runtime\nfunc ProviderWithNerdctl(binaryName string) ProviderOption {\n\treturn providerRuntimeOption(func(p *Provider) {\n\t\tp.provider = nerdctl.NewProvider(p.logger, binaryName)\n\t})\n}\n\n// Create provisions and starts a kubernetes-in-docker cluster\nfunc (p *Provider) Create(name string, options ...CreateOption) error {\n\t// apply options\n\topts := &internalcreate.ClusterOptions{\n\t\tNameOverride: name,\n\t}\n\tfor _, o := range options {\n\t\tif err := o.apply(opts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn internalcreate.Cluster(p.logger, p.provider, opts)\n}\n\n// Delete tears down a kubernetes-in-docker cluster\nfunc (p *Provider) Delete(name, explicitKubeconfigPath string) error {\n\treturn internaldelete.Cluster(p.logger, p.provider, defaultName(name), explicitKubeconfigPath)\n}\n\n// List returns a list of clusters for which nodes exist\nfunc (p *Provider) List() ([]string, error) {\n\treturn p.provider.ListClusters()\n}\n\n// KubeConfig returns the KUBECONFIG for the cluster\n// If internal is true, this will contain the internal IP etc.\n// If internal is false, this will contain the host IP etc.\nfunc (p *Provider) KubeConfig(name string, internal bool) (string, error) {\n\treturn kubeconfig.Get(p.provider, defaultName(name), !internal)\n}\n\n// ExportKubeConfig exports the KUBECONFIG for the cluster, merging\n// it into the selected file, following the rules from\n// https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#config\n// where explicitPath is the --kubeconfig value.\nfunc (p *Provider) ExportKubeConfig(name string, explicitPath string, internal bool) error {\n\treturn kubeconfig.Export(p.provider, defaultName(name), explicitPath, !internal)\n}\n\n// ListNodes returns the list of container IDs for the \"nodes\" in the cluster\nfunc (p *Provider) ListNodes(name string) ([]nodes.Node, error) {\n\treturn p.provider.ListNodes(defaultName(name))\n}\n\n// ListInternalNodes returns the list of container IDs for the \"nodes\" in the cluster\n// that are not external\nfunc (p *Provider) ListInternalNodes(name string) ([]nodes.Node, error) {\n\tn, err := p.provider.ListNodes(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn nodeutils.InternalNodes(n)\n}\n\n// CollectLogs will populate dir with cluster logs and other debug files\nfunc (p *Provider) CollectLogs(name, dir string) error {\n\t// TODO: should use ListNodes and Collect should handle nodes differently\n\t// based on role ...\n\tinternalNodes, err := p.ListInternalNodes(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// ensure directory\n\tif err := os.MkdirAll(dir, os.ModePerm); err != nil {\n\t\treturn errors.Wrap(err, \"failed to create logs directory\")\n\t}\n\n\t// write kind version\n\tif err := os.WriteFile(\n\t\tfilepath.Join(dir, \"kind-version.txt\"),\n\t\t[]byte(version.DisplayVersion()),\n\t\t0666, // match os.Create\n\t); err != nil {\n\t\treturn errors.Wrap(err, \"failed to write kind-version.txt\")\n\t}\n\n\t// common portable log collection for the nodes\n\tfns := []func() error{}\n\tvar errs []error\n\tfor _, n := range internalNodes {\n\t\tnode := n // https://golang.org/doc/faq#closures_and_goroutines\n\t\tname := node.String()\n\t\tpath := filepath.Join(dir, name)\n\t\tfns = append(fns,\n\t\t\tfunc() error { return collectNodeLogs(p.logger, node, path) },\n\t\t)\n\t}\n\terrs = append(errs, errors.AggregateConcurrent(fns))\n\n\t// additional, provider specific log collection\n\terrs = append(errs, p.provider.CollectLogs(dir, internalNodes))\n\n\t// flatten\n\treturn errors.NewAggregate(errs)\n}\n\nfunc collectNodeLogs(logger log.Logger, n nodes.Node, dir string) error {\n\texecToPathFn := func(cmd exec.Cmd, path string) func() error {\n\t\treturn func() error {\n\t\t\tf, err := common.FileOnHost(filepath.Join(dir, path))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\treturn cmd.SetStdout(f).SetStderr(f).Run()\n\t\t}\n\t}\n\n\treturn errors.AggregateConcurrent([]func() error{\n\t\tfunc() error {\n\t\t\tf, err := common.FileOnHost(filepath.Join(dir, \"serial.log\"))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\treturn n.SerialLogs(f)\n\t\t},\n\t\tfunc() error {\n\t\t\treturn internallogs.DumpDir(logger, n, \"/var/log\", dir)\n\t\t},\n\t\t// record info about the node container\n\t\texecToPathFn(\n\t\t\tn.Command(\"cat\", \"/kind/version\"),\n\t\t\t\"kubernetes-version.txt\",\n\t\t),\n\t\texecToPathFn(\n\t\t\tn.Command(\"journalctl\", \"--no-pager\"),\n\t\t\t\"journal.log\",\n\t\t),\n\t\texecToPathFn(\n\t\t\tn.Command(\"journalctl\", \"--no-pager\", \"-u\", \"kubelet.service\"),\n\t\t\t\"kubelet.log\",\n\t\t),\n\t\texecToPathFn(\n\t\t\tn.Command(\"journalctl\", \"--no-pager\", \"-u\", \"containerd.service\"),\n\t\t\t\"containerd.log\",\n\t\t),\n\t\texecToPathFn(\n\t\t\tn.Command(\"crictl\", \"images\"),\n\t\t\t\"images.log\",\n\t\t),\n\t})\n}\n"
  },
  {
    "path": "pkg/cmd/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cmd provides helpers used by kind's commands / cli\npackage cmd\n"
  },
  {
    "path": "pkg/cmd/iostreams.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"io\"\n\t\"os\"\n)\n\n// IOStreams provides the standard names for iostreams.\n// This is useful for embedding and for unit testing.\n// Inconsistent and different names make it hard to read and review code\n// This is based on cli-runtime, but just the nice type without the dependency\ntype IOStreams struct {\n\t// In think, os.Stdin\n\tIn io.Reader\n\t// Out think, os.Stdout\n\tOut io.Writer\n\t// ErrOut think, os.Stderr\n\tErrOut io.Writer\n}\n\n// StandardIOStreams returns an IOStreams from os.Stdin, os.Stdout\nfunc StandardIOStreams() IOStreams {\n\treturn IOStreams{\n\t\tIn:     os.Stdin,\n\t\tOut:    os.Stdout,\n\t\tErrOut: os.Stderr,\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/kind/build/build.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package build implements the `build` command\npackage build\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/build/nodeimage\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for building\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"build\",\n\t\tShort: \"Build one of [node-image]\",\n\t\tLong:  \"Build one of [node-image]\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\t// add subcommands\n\tcmd.AddCommand(nodeimage.NewCommand(logger, streams))\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/build/nodeimage/nodeimage.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package nodeimage implements the nodeimage CLI subcommand.\npackage nodeimage\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/build/nodeimage\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\ntype flagpole struct {\n\tSource    string\n\tBuildType string\n\tImage     string\n\tBaseImage string\n\tArch      string\n}\n\n// NewCommand returns a new cobra.Command for building the node image\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs: cobra.MaximumNArgs(1),\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"node-image [kubernetes-source]\",\n\t\tShort: \"Build the node image\",\n\t\tLong:  \"Build the node image which contains Kubernetes build artifacts and other kind requirements\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn runE(logger, flags, args)\n\t\t},\n\t}\n\tcmd.Flags().StringVar(\n\t\t&flags.BuildType,\n\t\t\"type\",\n\t\t\"\",\n\t\t\"optionally specify one of 'url', 'file', 'release' or 'source' as the type of build\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.Image,\n\t\t\"image\",\n\t\tnodeimage.DefaultImage,\n\t\t\"name:tag of the resulting image to be built\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.BaseImage,\n\t\t\"base-image\",\n\t\tnodeimage.DefaultBaseImage,\n\t\t\"name:tag of the base image to use for the build\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.Arch,\n\t\t\"arch\",\n\t\t\"\",\n\t\t\"architecture to build for, defaults to the host architecture\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, flags *flagpole, args []string) error {\n\tsourceSpec := \"\"\n\tif len(args) > 0 {\n\t\tsourceSpec = args[0]\n\t}\n\tif err := nodeimage.Build(\n\t\tnodeimage.WithImage(flags.Image),\n\t\tnodeimage.WithBaseImage(flags.BaseImage),\n\t\tnodeimage.WithKubeParam(sourceSpec),\n\t\tnodeimage.WithLogger(logger),\n\t\tnodeimage.WithArch(flags.Arch),\n\t\tnodeimage.WithBuildType(flags.BuildType),\n\t); err != nil {\n\t\treturn errors.Wrap(err, \"error building node image\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/completion/bash/bash.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package bash implements the `bash` command\npackage bash\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"bash\",\n\t\tShort: \"Output shell completions for bash\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn cmd.Parent().Parent().GenBashCompletion(streams.Out)\n\t\t},\n\t}\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/completion/completion.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package completion implements the `completion` command\npackage completion\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/completion/bash\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/completion/fish\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/completion/powershell\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/completion/zsh\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"completion\",\n\t\tShort: \"Output shell completion code for the specified shell (bash, zsh or fish)\",\n\t\tLong:  longDescription,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\tcmd.AddCommand(zsh.NewCommand(logger, streams))\n\tcmd.AddCommand(bash.NewCommand(logger, streams))\n\tcmd.AddCommand(fish.NewCommand(logger, streams))\n\tcmd.AddCommand(powershell.NewCommand(logger, streams))\n\treturn cmd\n}\n\nconst longDescription = `\nOutputs kind shell completion for the given shell (bash, fish, powershell, or zsh)\nThis depends on the bash-completion binary.  Example installation instructions:\n# for bash users\n\t$ kind completion bash > ~/.kind-completion\n\t$ source ~/.kind-completion\n\n# for zsh users\n\t% kind completion zsh > /usr/local/share/zsh/site-functions/_kind\n\t% autoload -U compinit && compinit\n# or if zsh-completion is installed via homebrew\n    % kind completion zsh > \"${fpath[1]}/_kind\"\n# or if you use oh-my-zsh (needs zsh-completions plugin)\n\t% mkdir $ZSH/completions/\n\t% kind completion zsh > $ZSH/completions/_kind\n\n# for fish users\n\t% kind completion fish > ~/.config/fish/completions/kind.fish\n\n# for powershell users\n\tPS> kind completion powershell | Out-String | Invoke-Expression\n\nAdditionally, you may want to output the completion to a file and source in your .bashrc\nNote for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2\n`\n"
  },
  {
    "path": "pkg/cmd/kind/completion/fish/fish.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package fish implements the `fish` command\npackage fish\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"fish\",\n\t\tShort: \"Output shell completions for fish\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn cmd.Parent().Parent().GenFishCompletion(streams.Out, true)\n\t\t},\n\t}\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/completion/powershell/powershell.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package powershell implements the `powershell` command\npackage powershell\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"powershell\",\n\t\tShort: \"Output shell completions for powershell\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn cmd.Parent().Parent().GenPowerShellCompletion(streams.Out)\n\t\t},\n\t}\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/completion/zsh/zsh.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package zsh implements the `zsh` command\npackage zsh\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"zsh\",\n\t\tShort: \"Output shell completions for zsh\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn cmd.Parent().Parent().GenZshCompletion(streams.Out)\n\t\t},\n\t}\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/create/cluster/createcluster.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cluster implements the `create cluster` command\npackage cluster\n\nimport (\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName       string\n\tConfig     string\n\tImageName  string\n\tRetain     bool\n\tWait       time.Duration\n\tKubeconfig string\n}\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"cluster\",\n\t\tShort: \"Creates a local Kubernetes cluster\",\n\t\tLong:  \"Creates a local Kubernetes cluster using Docker container 'nodes'\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, streams, flags)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\t\"\",\n\t\t\"cluster name, overrides KIND_CLUSTER_NAME, config (default kind)\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.Config,\n\t\t\"config\",\n\t\t\"\",\n\t\t\"path to a kind config file\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.ImageName,\n\t\t\"image\",\n\t\t\"\",\n\t\t\"node docker image to use for booting the cluster\",\n\t)\n\tcmd.Flags().BoolVar(\n\t\t&flags.Retain,\n\t\t\"retain\",\n\t\tfalse,\n\t\t\"retain nodes for debugging when cluster creation fails\",\n\t)\n\tcmd.Flags().DurationVar(\n\t\t&flags.Wait,\n\t\t\"wait\",\n\t\ttime.Duration(0),\n\t\t\"wait for control plane node to be ready (default 0s)\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.Kubeconfig,\n\t\t\"kubeconfig\",\n\t\t\"\",\n\t\t\"sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\n\t// handle config flag, we might need to read from stdin\n\twithConfig, err := configOption(flags.Config, streams.In)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// create the cluster\n\tif err = provider.Create(\n\t\tflags.Name,\n\t\twithConfig,\n\t\tcluster.CreateWithNodeImage(flags.ImageName),\n\t\tcluster.CreateWithRetain(flags.Retain),\n\t\tcluster.CreateWithWaitForReady(flags.Wait),\n\t\tcluster.CreateWithKubeconfigPath(flags.Kubeconfig),\n\t\tcluster.CreateWithDisplayUsage(true),\n\t\tcluster.CreateWithDisplaySalutation(true),\n\t); err != nil {\n\t\treturn errors.Wrap(err, \"failed to create cluster\")\n\t}\n\n\treturn nil\n}\n\n// configOption converts the raw --config flag value to a cluster creation\n// option matching it. it will read from stdin if the flag value is `-`\nfunc configOption(rawConfigFlag string, stdin io.Reader) (cluster.CreateOption, error) {\n\t// if not - then we are using a real file\n\tif rawConfigFlag != \"-\" {\n\t\treturn cluster.CreateWithConfigFile(rawConfigFlag), nil\n\t}\n\t// otherwise read from stdin\n\traw, err := io.ReadAll(stdin)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error reading config from stdin\")\n\t}\n\treturn cluster.CreateWithRawConfig(raw), nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/create/create.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package create implements the `create` command\npackage create\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\tcreatecluster \"sigs.k8s.io/kind/pkg/cmd/kind/create/cluster\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster creation\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"create\",\n\t\tShort: \"Creates one of [cluster]\",\n\t\tLong:  \"Creates one of local Kubernetes cluster (cluster)\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\tcmd.AddCommand(createcluster.NewCommand(logger, streams))\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/delete/cluster/deletecluster.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cluster implements the `delete` command\npackage cluster\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName       string\n\tKubeconfig string\n}\n\n// NewCommand returns a new cobra.Command for cluster deletion\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"cluster\",\n\t\tShort: \"Deletes a cluster\",\n\t\tLong: `Deletes a Kind cluster from the system.\n\nThis is an idempotent operation, meaning it may be called multiple times without\nfailing (like \"rm -f\"). If the cluster resources exist they will be deleted, and\nif the cluster is already gone it will just return success.\n\nErrors will only occur if the cluster resources exist and are not able to be deleted.\n`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn deleteCluster(logger, flags)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster name\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.Kubeconfig,\n\t\t\"kubeconfig\",\n\t\t\"\",\n\t\t\"sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config\",\n\t)\n\treturn cmd\n}\n\nfunc deleteCluster(logger log.Logger, flags *flagpole) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\t// Delete individual cluster\n\tlogger.V(0).Infof(\"Deleting cluster %q ...\", flags.Name)\n\tif err := provider.Delete(flags.Name, flags.Kubeconfig); err != nil {\n\t\treturn errors.Wrapf(err, \"failed to delete cluster %q\", flags.Name)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/delete/clusters/deleteclusters.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clusters implements the `delete` command for multiple clusters\npackage clusters\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tKubeconfig string\n\tAll        bool\n}\n\n// NewCommand returns a new cobra.Command for cluster deletion\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.MinimumNArgs(0),\n\t\tUse:   \"clusters\",\n\t\tShort: \"Deletes one or more clusters\",\n\t\tLong: `Deletes one or more Kind clusters from the system.\n\nThis is an idempotent operation, meaning it may be called multiple times without\nfailing (like \"rm -f\"). If the cluster resources exist they will be deleted, and\nif the cluster is already gone it will just return success.\n\nErrors will only occur if the cluster resources exist and are not able to be deleted.\n`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif !flags.All && len(args) == 0 {\n\t\t\t\treturn errors.New(\"no cluster names provided\")\n\t\t\t}\n\n\t\t\treturn deleteClusters(logger, flags, args)\n\t\t},\n\t}\n\tcmd.Flags().StringVar(\n\t\t&flags.Kubeconfig,\n\t\t\"kubeconfig\",\n\t\t\"\",\n\t\t\"sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config\",\n\t)\n\tcmd.Flags().BoolVarP(\n\t\t&flags.All,\n\t\t\"all\",\n\t\t\"A\",\n\t\tfalse,\n\t\t\"delete all clusters\",\n\t)\n\treturn cmd\n}\n\nfunc deleteClusters(logger log.Logger, flags *flagpole, clusters []string) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\tvar err error\n\tif flags.All {\n\t\t//Delete all clusters\n\t\tif clusters, err = provider.List(); err != nil {\n\t\t\treturn errors.Wrap(err, \"failed listing clusters for delete\")\n\t\t}\n\t}\n\tvar success []string\n\tfor _, cluster := range clusters {\n\t\tif err = provider.Delete(cluster, flags.Kubeconfig); err != nil {\n\t\t\tlogger.V(0).Infof(\"%s\\n\", errors.Wrapf(err, \"failed to delete cluster %q\", cluster))\n\t\t\tcontinue\n\t\t}\n\t\tsuccess = append(success, cluster)\n\t}\n\tlogger.V(0).Infof(\"Deleted clusters: %q\", success)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/delete/delete.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package delete implements the `delete` command\npackage delete\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\tdeletecluster \"sigs.k8s.io/kind/pkg/cmd/kind/delete/cluster\"\n\tdeleteclusters \"sigs.k8s.io/kind/pkg/cmd/kind/delete/clusters\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for cluster deletion\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"delete\",\n\t\tShort: \"Deletes one of [cluster]\",\n\t\tLong:  \"Deletes one of [cluster]\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\tcmd.AddCommand(deletecluster.NewCommand(logger, streams))\n\tcmd.AddCommand(deleteclusters.NewCommand(logger, streams))\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/export/export.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package export implements the `export` command\npackage export\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/export/kubeconfig\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/export/logs\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for export\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"export\",\n\t\tShort: \"Exports one of [kubeconfig, logs]\",\n\t\tLong:  \"Exports one of [kubeconfig, logs]\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\t// add subcommands\n\tcmd.AddCommand(logs.NewCommand(logger, streams))\n\tcmd.AddCommand(kubeconfig.NewCommand(logger, streams))\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/export/kubeconfig/kubeconfig.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeconfig implements the `kubeconfig` command\npackage kubeconfig\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName       string\n\tKubeconfig string\n\tInternal   bool\n}\n\n// NewCommand returns a new cobra.Command for exporting the kubeconfig\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"kubeconfig\",\n\t\tShort: \"Exports cluster kubeconfig\",\n\t\tLong:  \"Exports cluster kubeconfig\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, flags)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster context name\",\n\t)\n\tcmd.Flags().StringVar(\n\t\t&flags.Kubeconfig,\n\t\t\"kubeconfig\",\n\t\t\"\",\n\t\t\"sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config\",\n\t)\n\tcmd.Flags().BoolVar(\n\t\t&flags.Internal,\n\t\t\"internal\",\n\t\tfalse,\n\t\t\"use internal address instead of external\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, flags *flagpole) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\tif err := provider.ExportKubeConfig(flags.Name, flags.Kubeconfig, flags.Internal); err != nil {\n\t\treturn err\n\t}\n\t// TODO: get kind-name from a method? OTOH we probably want to keep this\n\t// naming scheme stable anyhow...\n\tlogger.V(0).Infof(`Set kubectl context to \"kind-%s\"`, flags.Name)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/export/logs/logs.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package logs implements the `logs` command\npackage logs\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/fs\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName string\n}\n\n// NewCommand returns a new cobra.Command for getting the cluster logs\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs: cobra.MaximumNArgs(1),\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"logs [output-dir]\",\n\t\tShort: \"Exports logs to a tempdir or [output-dir] if specified\",\n\t\tLong:  \"Exports logs to a tempdir or [output-dir] if specified\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, streams, flags, args)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster context name\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole, args []string) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\n\t// Check if the cluster has any running nodes\n\tnodes, err := provider.ListNodes(flags.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn fmt.Errorf(\"unknown cluster %q\", flags.Name)\n\t}\n\n\t// get the optional directory argument, or create a tempdir\n\tvar dir string\n\tif len(args) == 0 {\n\t\tt, err := fs.TempDir(\"\", \"\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdir = t\n\t} else {\n\t\tdir = args[0]\n\t}\n\n\t// NOTE: the path is the output of this command to be captured by calling tools\n\t// whereas \"Exporting logs...\" is info / debug (stderr)\n\tlogger.V(0).Infof(\"Exporting logs for cluster %q to:\", flags.Name)\n\tfmt.Fprintln(streams.Out, dir)\n\n\t// collect the logs\n\treturn provider.CollectLogs(flags.Name, dir)\n}\n"
  },
  {
    "path": "pkg/cmd/kind/get/clusters/clusters.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package clusters implements the `clusters` command\npackage clusters\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\n// NewCommand returns a new cobra.Command for getting the list of clusters\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tArgs: cobra.NoArgs,\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"clusters\",\n\t\tShort: \"Lists existing kind clusters by their name\",\n\t\tLong:  \"Lists existing kind clusters by their name\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn runE(logger, streams)\n\t\t},\n\t}\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, streams cmd.IOStreams) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\tclusters, err := provider.List()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(clusters) == 0 {\n\t\tlogger.V(0).Info(\"No kind clusters found.\")\n\t\treturn nil\n\t}\n\tfor _, cluster := range clusters {\n\t\tfmt.Fprintln(streams.Out, cluster)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/get/get.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package get implements the `get` command\npackage get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/get/clusters\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/get/kubeconfig\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/get/nodes\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for get\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\t// TODO(bentheelder): more detailed usage\n\t\tUse:   \"get\",\n\t\tShort: \"Gets one of [clusters, nodes, kubeconfig]\",\n\t\tLong:  \"Gets one of [clusters, nodes, kubeconfig]\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\t// add subcommands\n\tcmd.AddCommand(clusters.NewCommand(logger, streams))\n\tcmd.AddCommand(nodes.NewCommand(logger, streams))\n\tcmd.AddCommand(kubeconfig.NewCommand(logger, streams))\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/get/kubeconfig/kubeconfig.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kubeconfig implements the `kubeconfig` command\npackage kubeconfig\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName     string\n\tInternal bool\n}\n\n// NewCommand returns a new cobra.Command for getting the kubeconfig\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"kubeconfig\",\n\t\tShort: \"Prints cluster kubeconfig\",\n\t\tLong:  \"Prints cluster kubeconfig\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, streams, flags)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster context name\",\n\t)\n\tcmd.Flags().BoolVar(\n\t\t&flags.Internal,\n\t\t\"internal\",\n\t\tfalse,\n\t\t\"use internal address instead of external\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\tcfg, err := provider.KubeConfig(flags.Name, flags.Internal)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Fprintln(streams.Out, cfg)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/get/nodes/nodes.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package nodes implements the `nodes` command\npackage nodes\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName        string\n\tAllClusters bool\n}\n\n// NewCommand returns a new cobra.Command for getting the list of nodes for a given cluster\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"nodes\",\n\t\tShort: \"Lists existing kind nodes by their name\",\n\t\tLong:  \"Lists existing kind nodes by their name\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, streams, flags)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster context name\",\n\t)\n\tcmd.Flags().BoolVarP(\n\t\t&flags.AllClusters,\n\t\t\"all-clusters\",\n\t\t\"A\",\n\t\tfalse,\n\t\t\"If present, list all the available nodes across all cluster contexts. Current context is ignored even if specified with --name.\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error {\n\t// List nodes by cluster context name\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\n\tvar nodes []nodes.Node\n\tvar err error\n\tif flags.AllClusters {\n\t\tclusters, err := provider.List()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, clusterName := range clusters {\n\t\t\tclusterNodes, err := provider.ListNodes(clusterName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tnodes = append(nodes, clusterNodes...)\n\t\t}\n\t\tif len(nodes) == 0 {\n\t\t\tlogger.V(0).Infof(\"No kind nodes for any cluster.\")\n\t\t\treturn nil\n\t\t}\n\t} else {\n\t\tnodes, err = provider.ListNodes(flags.Name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(nodes) == 0 {\n\t\t\tlogger.V(0).Infof(\"No kind nodes found for cluster %q.\", flags.Name)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tfor _, node := range nodes {\n\t\tfmt.Fprintln(streams.Out, node.String())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cmd/kind/load/docker-image/docker-image.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package load implements the `load` command\npackage load\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/exec\"\n\t\"sigs.k8s.io/kind/pkg/fs\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype (\n\timageTagFetcher func(nodes.Node, string) (map[string]bool, error)\n)\n\ntype flagpole struct {\n\tName  string\n\tNodes []string\n}\n\n// NewCommand returns a new cobra.Command for loading an image into a cluster\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs: func(cmd *cobra.Command, args []string) error {\n\t\t\tif len(args) < 1 {\n\t\t\t\treturn errors.New(\"a list of image names is required\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tUse:   \"docker-image <IMAGE> [IMAGE...]\",\n\t\tShort: \"Loads docker images from host into nodes\",\n\t\tLong:  \"Loads docker images from host into all or specified nodes by name\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, flags, args)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster context name\",\n\t)\n\tcmd.Flags().StringSliceVar(\n\t\t&flags.Nodes,\n\t\t\"nodes\",\n\t\tnil,\n\t\t\"comma separated list of nodes to load images into\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, flags *flagpole, args []string) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\n\t// Check that the image exists locally and gets its ID, if not return error\n\timageNames := removeDuplicates(args)\n\tvar imageIDs []string\n\tfor _, imageName := range imageNames {\n\t\timageID, err := imageID(imageName)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"image: %q not present locally\", imageName)\n\t\t}\n\t\timageIDs = append(imageIDs, imageID)\n\t}\n\n\t// Check if the cluster nodes exist\n\tnodeList, err := provider.ListInternalNodes(flags.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(nodeList) == 0 {\n\t\treturn fmt.Errorf(\"no nodes found for cluster %q\", flags.Name)\n\t}\n\n\t// map cluster nodes by their name\n\tnodesByName := map[string]nodes.Node{}\n\tfor _, node := range nodeList {\n\t\t// TODO(bentheelder): this depends on the fact that ListByCluster()\n\t\t// will have name for nameOrId.\n\t\tnodesByName[node.String()] = node\n\t}\n\n\t// pick only the user selected nodes and ensure they exist\n\t// the default is all nodes unless flags.Nodes is set\n\tcandidateNodes := nodeList\n\tif len(flags.Nodes) > 0 {\n\t\tcandidateNodes = []nodes.Node{}\n\t\tfor _, name := range flags.Nodes {\n\t\t\tnode, ok := nodesByName[name]\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"unknown node: %q\", name)\n\t\t\t}\n\t\t\tcandidateNodes = append(candidateNodes, node)\n\t\t}\n\t}\n\n\t// pick only the nodes that don't have the image\n\tselectedNodes := map[string]nodes.Node{}\n\tfns := []func() error{}\n\tfor i, imageName := range imageNames {\n\t\timageID := imageIDs[i]\n\t\tprocessed := false\n\t\tfor _, node := range candidateNodes {\n\t\t\texists, reTagRequired, sanitizedImageName := checkIfImageReTagRequired(node, imageID, imageName, nodeutils.ImageTags)\n\t\t\tif exists && !reTagRequired {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif reTagRequired {\n\t\t\t\t// We will try to re-tag the image. If the re-tag fails, we will fall back to the default behavior of loading\n\t\t\t\t// the images into the nodes again\n\t\t\t\tlogger.V(0).Infof(\"Image with ID: %s already present on the node %s but is missing the tag %s. re-tagging...\", imageID, node.String(), sanitizedImageName)\n\t\t\t\tif err := nodeutils.ReTagImage(node, imageID, sanitizedImageName); err != nil {\n\t\t\t\t\tlogger.Errorf(\"failed to re-tag image on the node %s due to an error %s. Will load it instead...\", node.String(), err)\n\t\t\t\t\tselectedNodes[node.String()] = node\n\t\t\t\t} else {\n\t\t\t\t\tprocessed = true\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tid, err := nodeutils.ImageID(node, imageName)\n\t\t\tif err != nil || id != imageID {\n\t\t\t\tselectedNodes[node.String()] = node\n\t\t\t\tlogger.V(0).Infof(\"Image: %q with ID %q not yet present on node %q, loading...\", imageName, imageID, node.String())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif len(selectedNodes) == 0 && !processed {\n\t\t\tlogger.V(0).Infof(\"Image: %q with ID %q found to be already present on all nodes.\", imageName, imageID)\n\t\t}\n\t}\n\n\t// return early if no node needs the image\n\tif len(selectedNodes) == 0 {\n\t\treturn nil\n\t}\n\n\t// Setup the tar path where the images will be saved\n\tdir, err := fs.TempDir(\"\", \"images-tar\")\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to create tempdir\")\n\t}\n\tdefer os.RemoveAll(dir)\n\timagesTarPath := filepath.Join(dir, \"images.tar\")\n\t// Save the images into a tar\n\terr = save(imageNames, imagesTarPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Load the images on the selected nodes\n\tfor _, selectedNode := range selectedNodes {\n\t\tselectedNode := selectedNode // capture loop variable\n\t\tfns = append(fns, func() error {\n\t\t\treturn loadImage(imagesTarPath, selectedNode)\n\t\t})\n\t}\n\treturn errors.UntilErrorConcurrent(fns)\n}\n\n// TODO: we should consider having a cluster method to load images\n\n// loads an image tarball onto a node\nfunc loadImage(imageTarName string, node nodes.Node) error {\n\tf, err := os.Open(imageTarName)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to open image\")\n\t}\n\tdefer f.Close()\n\treturn nodeutils.LoadImageArchive(node, f)\n}\n\n// save saves images to dest, as in `docker save`\nfunc save(images []string, dest string) error {\n\tcommandArgs := append([]string{\"save\", \"-o\", dest}, images...)\n\treturn exec.Command(\"docker\", commandArgs...).Run()\n}\n\n// imageID return the Id of the container image\nfunc imageID(containerNameOrID string) (string, error) {\n\tcmd := exec.Command(\"docker\", \"image\", \"inspect\",\n\t\t\"-f\", \"{{ .Id }}\",\n\t\tcontainerNameOrID, // ... against the container\n\t)\n\tlines, err := exec.OutputLines(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(lines) != 1 {\n\t\treturn \"\", errors.Errorf(\"Docker image ID should only be one line, got %d lines\", len(lines))\n\t}\n\treturn lines[0], nil\n}\n\n// removeDuplicates removes duplicates from a string slice\nfunc removeDuplicates(slice []string) []string {\n\tresult := []string{}\n\tseenKeys := make(map[string]struct{})\n\tfor _, k := range slice {\n\t\tif _, seen := seenKeys[k]; !seen {\n\t\t\tresult = append(result, k)\n\t\t\tseenKeys[k] = struct{}{}\n\t\t}\n\t}\n\treturn result\n}\n\n// checkIfImageExists makes sure we only perform the reverse lookup of the ImageID to tag map\nfunc checkIfImageReTagRequired(node nodes.Node, imageID, imageName string, tagFetcher imageTagFetcher) (exists, reTagRequired bool, sanitizedImage string) {\n\ttags, err := tagFetcher(node, imageID)\n\tif len(tags) == 0 || err != nil {\n\t\texists = false\n\t\treturn\n\t}\n\texists = true\n\tsanitizedImage = sanitizeImage(imageName)\n\tif ok := tags[sanitizedImage]; ok {\n\t\treTagRequired = false\n\t\treturn\n\t}\n\treTagRequired = true\n\treturn\n}\n\n// sanitizeImage is a helper to return human readable image name\n// This is a modified version of the same function found under providers/podman/images.go\nfunc sanitizeImage(image string) (sanitizedName string) {\n\tconst (\n\t\tdefaultDomain    = \"docker.io/\"\n\t\tofficialRepoName = \"library\"\n\t)\n\tsanitizedName = image\n\n\tif !strings.ContainsRune(image, '/') {\n\t\tsanitizedName = officialRepoName + \"/\" + image\n\t}\n\n\ti := strings.IndexRune(sanitizedName, '/')\n\tif i == -1 || (!strings.ContainsAny(sanitizedName[:i], \".:\") && sanitizedName[:i] != \"localhost\") {\n\t\tsanitizedName = defaultDomain + sanitizedName\n\t}\n\n\ti = strings.IndexRune(sanitizedName, ':')\n\tif i == -1 {\n\t\tsanitizedName += \":latest\"\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/cmd/kind/load/docker-image/docker-image_test.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage load\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n)\n\nfunc Test_removeDuplicates(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tslice []string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"empty\",\n\t\t\tslice: []string{},\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"all different\",\n\t\t\tslice: []string{\"one\", \"two\"},\n\t\t\twant:  []string{\"one\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"one dup\",\n\t\t\tslice: []string{\"one\", \"two\", \"two\"},\n\t\t\twant:  []string{\"one\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"two dup\",\n\t\t\tslice: []string{\"one\", \"two\", \"two\", \"one\"},\n\t\t\twant:  []string{\"one\", \"two\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := removeDuplicates(tt.slice)\n\t\t\tsort.Strings(got)\n\t\t\tsort.Strings(tt.want)\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"removeDuplicates() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_sanitizeImage(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\timage          string\n\t\tsanitizedImage string\n\t}{\n\t\t{\n\t\t\timage:          \"ubuntu:18.04\",\n\t\t\tsanitizedImage: \"docker.io/library/ubuntu:18.04\",\n\t\t},\n\t\t{\n\t\t\timage:          \"custom/ubuntu:18.04\",\n\t\t\tsanitizedImage: \"docker.io/custom/ubuntu:18.04\",\n\t\t},\n\t\t{\n\t\t\timage:          \"registry.k8s.io/kindest/node:latest\",\n\t\t\tsanitizedImage: \"registry.k8s.io/kindest/node:latest\",\n\t\t},\n\t\t{\n\t\t\timage:          \"registry.k8s.io/pause:3.6\",\n\t\t\tsanitizedImage: \"registry.k8s.io/pause:3.6\",\n\t\t},\n\t\t{\n\t\t\timage:          \"baz\",\n\t\t\tsanitizedImage: \"docker.io/library/baz:latest\",\n\t\t},\n\t\t{\n\t\t\timage:          \"other-registry/baz\",\n\t\t\tsanitizedImage: \"docker.io/other-registry/baz:latest\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := sanitizeImage(tt.image)\n\t\t\tif got != tt.sanitizedImage {\n\t\t\t\tt.Errorf(\"sanitizeImage(%s) = %s, want %s\", tt.image, got, tt.sanitizedImage)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_checkIfImageReTagRequired(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\timageTags struct {\n\t\t\ttags map[string]bool\n\t\t\terr  error\n\t\t}\n\t\timageID        string\n\t\timageName      string\n\t\treturnValues   []bool\n\t\tsanitizedImage string\n\t}{\n\t\t{\n\t\t\tname: \"image is already present\",\n\t\t\timageTags: struct {\n\t\t\t\ttags map[string]bool\n\t\t\t\terr  error\n\t\t\t}{\n\t\t\t\tmap[string]bool{\n\t\t\t\t\t\"docker.io/library/image1:tag1\": true,\n\t\t\t\t\t\"k8s.io/image1:tag1\":            true,\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t},\n\t\t\timageID:        \"sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a\",\n\t\t\timageName:      \"k8s.io/image1:tag1\",\n\t\t\treturnValues:   []bool{true, false},\n\t\t\tsanitizedImage: \"k8s.io/image1:tag1\",\n\t\t},\n\t\t{\n\t\t\tname: \"re-tag is required\",\n\t\t\timageTags: struct {\n\t\t\t\ttags map[string]bool\n\t\t\t\terr  error\n\t\t\t}{\n\t\t\t\tmap[string]bool{\n\t\t\t\t\t\"docker.io/library/image1:tag1\": true,\n\t\t\t\t\t\"k8s.io/image1:tag1\":            true,\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t},\n\t\t\timageID:        \"sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a\",\n\t\t\timageName:      \"k8s.io/image1:tag2\",\n\t\t\treturnValues:   []bool{true, true},\n\t\t\tsanitizedImage: \"k8s.io/image1:tag2\",\n\t\t},\n\t\t{\n\t\t\tname: \"re-tag is required with docker.io prefix\",\n\t\t\timageTags: struct {\n\t\t\t\ttags map[string]bool\n\t\t\t\terr  error\n\t\t\t}{\n\t\t\t\tmap[string]bool{\n\t\t\t\t\t\"docker.io/foo/image1:tag1\": true,\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t},\n\t\t\timageID:        \"sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a\",\n\t\t\timageName:      \"foo/image1:tag2\",\n\t\t\treturnValues:   []bool{true, true},\n\t\t\tsanitizedImage: \"docker.io/foo/image1:tag2\",\n\t\t},\n\t\t{\n\t\t\tname: \"image tag fetch failed\",\n\t\t\timageTags: struct {\n\t\t\t\ttags map[string]bool\n\t\t\t\terr  error\n\t\t\t}{\n\t\t\t\tmap[string]bool{},\n\t\t\t\terrors.New(\"some runtime error\"),\n\t\t\t},\n\t\t\timageID:        \"sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a\",\n\t\t\timageName:      \"k8s.io/image1:tag2\",\n\t\t\treturnValues:   []bool{false, false},\n\t\t\tsanitizedImage: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// checkIfImageReTagRequired doesn't use the `nodes.Node` type for anything. So\n\t\t\t// passing a nil value here should be fine as the other two functions that use the\n\t\t\t// nodes.Node has been stubbed out already\n\t\t\texists, reTagRequired, sanitizedImage := checkIfImageReTagRequired(nil, tc.imageID, tc.imageName, func(n nodes.Node, s string) (map[string]bool, error) {\n\t\t\t\treturn tc.imageTags.tags, tc.imageTags.err\n\t\t\t})\n\t\t\tif exists != tc.returnValues[0] || reTagRequired != tc.returnValues[1] || sanitizedImage != tc.sanitizedImage {\n\t\t\t\tt.Errorf(\"checkIfImageReTagRequired failed. Expected: [%v,%v,%v], got: [%v, %v, %v]\", tc.returnValues[0], tc.returnValues[1], tc.sanitizedImage, exists, reTagRequired, sanitizedImage)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/kind/load/image-archive/image-archive.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package load implements the `load` command\npackage load\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodes\"\n\t\"sigs.k8s.io/kind/pkg/cluster/nodeutils\"\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/runtime\"\n)\n\ntype flagpole struct {\n\tName  string\n\tNodes []string\n}\n\n// NewCommand returns a new cobra.Command for loading an image into a cluster\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tArgs: func(cmd *cobra.Command, args []string) error {\n\t\t\tif len(args) < 1 {\n\t\t\t\treturn fmt.Errorf(\"name of image archive is required\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tPreRun: func(cmd *cobra.Command, args []string) {\n\t\t\tif len(args) > 1 {\n\t\t\t\tlogger.Warn(\"It is suggested that you save multiple images into a common archive and load that instead of loading multiple archives for better performance\")\n\t\t\t}\n\t\t},\n\t\tUse:   \"image-archive <IMAGE.tar>\",\n\t\tShort: \"Loads docker image from archive into nodes\",\n\t\tLong:  \"Loads docker image from archive into all or specified nodes by name\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcli.OverrideDefaultName(cmd.Flags())\n\t\t\treturn runE(logger, flags, args)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(\n\t\t&flags.Name,\n\t\t\"name\",\n\t\t\"n\",\n\t\tcluster.DefaultName,\n\t\t\"the cluster context name\",\n\t)\n\tcmd.Flags().StringSliceVar(\n\t\t&flags.Nodes,\n\t\t\"nodes\",\n\t\tnil,\n\t\t\"comma separated list of nodes to load images into\",\n\t)\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, flags *flagpole, args []string) error {\n\tprovider := cluster.NewProvider(\n\t\tcluster.ProviderWithLogger(logger),\n\t\truntime.GetDefault(logger),\n\t)\n\n\tfor _, imageTarPath := range args {\n\t\tif _, err := os.Stat(imageTarPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor _, imageTarPath := range args {\n\t\tif err := loadArchiveToNodes(logger, provider, flags.Name, flags.Nodes, imageTarPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc loadArchiveToNodes(logger log.Logger, provider *cluster.Provider, clusterName string, nodeNames []string, imageArchivePath string) error {\n\t// Check if the cluster nodes exist\n\tnodeList, err := provider.ListInternalNodes(clusterName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(nodeList) == 0 {\n\t\treturn fmt.Errorf(\"no nodes found for cluster %q\", clusterName)\n\t}\n\n\t// map cluster nodes by their name\n\tnodesByName := map[string]nodes.Node{}\n\tfor _, node := range nodeList {\n\t\t// TODO(bentheelder): this depends on the fact that ListByCluster()\n\t\t// will have name for nameOrId.\n\t\tnodesByName[node.String()] = node\n\t}\n\n\t// pick only the user selected nodes and ensure they exist\n\t// the default is all nodes unless flags.Nodes is set\n\tselectedNodes := nodeList\n\tif len(nodeNames) > 0 {\n\t\tselectedNodes = []nodes.Node{}\n\t\tfor _, name := range nodeNames {\n\t\t\tnode, ok := nodesByName[name]\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"unknown node: %s\", name)\n\t\t\t}\n\t\t\tselectedNodes = append(selectedNodes, node)\n\t\t}\n\t}\n\n\t// Load the image on the selected nodes\n\tfns := []func() error{}\n\tfor _, selectedNode := range selectedNodes {\n\t\tselectedNode := selectedNode // capture loop variable\n\t\tfns = append(fns, func() error {\n\t\t\treturn loadImage(logger, imageArchivePath, selectedNode)\n\t\t})\n\t}\n\treturn errors.UntilErrorConcurrent(fns)\n}\n\n// loads an image tarball onto a node\nfunc loadImage(logger log.Logger, imageTarName string, node nodes.Node) error {\n\tf, err := os.Open(imageTarName)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to open image\")\n\t}\n\tdefer f.Close()\n\tlogger.V(2).Infof(\"Loading Docker Image from archive %s to node %s\", imageTarName, node.String())\n\treturn nodeutils.LoadImageArchive(node, f)\n}\n"
  },
  {
    "path": "pkg/cmd/kind/load/load.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package load implements the `load` command\npackage load\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\tdockerimage \"sigs.k8s.io/kind/pkg/cmd/kind/load/docker-image\"\n\timagearchive \"sigs.k8s.io/kind/pkg/cmd/kind/load/image-archive\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// NewCommand returns a new cobra.Command for get\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tArgs:  cobra.NoArgs,\n\t\tUse:   \"load\",\n\t\tShort: \"Loads images into nodes\",\n\t\tLong:  \"Loads images into node from an archive or image on host\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := cmd.Help()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn errors.New(\"Subcommand is required\")\n\t\t},\n\t}\n\t// add subcommands\n\tcmd.AddCommand(dockerimage.NewCommand(logger, streams))\n\tcmd.AddCommand(imagearchive.NewCommand(logger, streams))\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cmd/kind/root.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package kind implements the root kind cobra command, and the cli Main()\npackage kind\n\nimport (\n\t\"io\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/build\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/completion\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/create\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/delete\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/export\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/get\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/load\"\n\t\"sigs.k8s.io/kind/pkg/cmd/kind/version\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\ntype flagpole struct {\n\tVerbosity int32\n\tQuiet     bool\n}\n\n// NewCommand returns a new cobra.Command implementing the root command for kind\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tflags := &flagpole{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"kind\",\n\t\tShort: \"kind is a tool for managing local Kubernetes clusters\",\n\t\tLong:  \"kind creates and manages local Kubernetes clusters using Docker container 'nodes'\",\n\t\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn runE(logger, flags)\n\t\t},\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t\tVersion:       version.Version(),\n\t}\n\tcmd.SetOut(streams.Out)\n\tcmd.SetErr(streams.ErrOut)\n\tcmd.PersistentFlags().Int32VarP(\n\t\t&flags.Verbosity,\n\t\t\"verbosity\",\n\t\t\"v\",\n\t\t0,\n\t\t\"info log verbosity, higher value produces more output\",\n\t)\n\tcmd.PersistentFlags().BoolVarP(\n\t\t&flags.Quiet,\n\t\t\"quiet\",\n\t\t\"q\",\n\t\tfalse,\n\t\t\"silence all stderr output\",\n\t)\n\t// add all top level subcommands\n\tcmd.AddCommand(build.NewCommand(logger, streams))\n\tcmd.AddCommand(completion.NewCommand(logger, streams))\n\tcmd.AddCommand(create.NewCommand(logger, streams))\n\tcmd.AddCommand(delete.NewCommand(logger, streams))\n\tcmd.AddCommand(export.NewCommand(logger, streams))\n\tcmd.AddCommand(get.NewCommand(logger, streams))\n\tcmd.AddCommand(version.NewCommand(logger, streams))\n\tcmd.AddCommand(load.NewCommand(logger, streams))\n\treturn cmd\n}\n\nfunc runE(logger log.Logger, flags *flagpole) error {\n\t// normal logger setup\n\tif flags.Quiet {\n\t\t// NOTE: if we are coming from app.Run handling this flag is\n\t\t// redundant, however it doesn't hurt, and this may be called directly.\n\t\tmaybeSetWriter(logger, io.Discard)\n\t}\n\tmaybeSetVerbosity(logger, log.Level(flags.Verbosity))\n\treturn nil\n}\n\n// maybeSetWriter will call logger.SetWriter(w) if logger has a SetWriter method\nfunc maybeSetWriter(logger log.Logger, w io.Writer) {\n\ttype writerSetter interface {\n\t\tSetWriter(io.Writer)\n\t}\n\tv, ok := logger.(writerSetter)\n\tif ok {\n\t\tv.SetWriter(w)\n\t}\n}\n\n// maybeSetVerbosity will call logger.SetVerbosity(verbosity) if logger\n// has a SetVerbosity method\nfunc maybeSetVerbosity(logger log.Logger, verbosity log.Level) {\n\ttype verboser interface {\n\t\tSetVerbosity(log.Level)\n\t}\n\tv, ok := logger.(verboser)\n\tif ok {\n\t\tv.SetVerbosity(verbosity)\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/kind/version/version.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package version implements the `version` command\npackage version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kind/pkg/cmd\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// Version returns the kind CLI Semantic Version\nfunc Version() string {\n\treturn version(versionCore, versionPreRelease, gitCommit, gitCommitCount)\n}\n\nfunc version(core, preRelease, commit, commitCount string) string {\n\tv := core\n\t// add pre-release version info if we have it\n\tif preRelease != \"\" {\n\t\tv += \"-\" + preRelease\n\t\t// If commitCount was set, add to the pre-release version\n\t\tif commitCount != \"\" {\n\t\t\tv += \".\" + commitCount\n\t\t}\n\t\t// if commit was set, add the + <build>\n\t\t// we only do this for pre-release versions\n\t\tif commit != \"\" {\n\t\t\t// NOTE: use 14 character short hash, like Kubernetes\n\t\t\tv += \"+\" + truncate(commit, 14)\n\t\t}\n\t}\n\treturn v\n}\n\n// DisplayVersion is Version() display formatted, this is what the version\n// subcommand prints\nfunc DisplayVersion() string {\n\treturn \"kind v\" + Version() + \" \" + runtime.Version() + \" \" + runtime.GOOS + \"/\" + runtime.GOARCH\n}\n\n// versionCore is the core portion of the kind CLI version per Semantic Versioning 2.0.0\nconst versionCore = \"0.32.0\"\n\n// versionPreRelease is the base pre-release portion of the kind CLI version per\n// Semantic Versioning 2.0.0\nvar versionPreRelease = \"alpha\"\n\n// gitCommitCount count the commits since the last release.\n// It is injected at build time.\nvar gitCommitCount = \"\"\n\n// gitCommit is the commit used to build the kind binary, if available.\n// It is injected at build time.\nvar gitCommit = \"\"\n\n// NewCommand returns a new cobra.Command for version\nfunc NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"version\",\n\t\tShort: \"Prints the kind CLI version\",\n\t\tLong:  \"Prints the kind CLI version\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif logger.V(0).Enabled() {\n\t\t\t\t// if not -q / --quiet, show lots of info\n\t\t\t\tfmt.Fprintln(streams.Out, DisplayVersion())\n\t\t\t} else {\n\t\t\t\t// otherwise only show semver\n\t\t\t\tfmt.Fprintln(streams.Out, Version())\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\treturn cmd\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) < maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen]\n}\n"
  },
  {
    "path": "pkg/cmd/kind/version/version_test.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTruncate(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tValue     string\n\t\tMaxLength int\n\t\tExpected  string\n\t}{\n\t\t{\n\t\t\tValue:     \"A Really Long String\",\n\t\t\tMaxLength: 1,\n\t\t\tExpected:  \"A\",\n\t\t},\n\t\t{\n\t\t\tValue:     \"A Short String\",\n\t\t\tMaxLength: 10,\n\t\t\tExpected:  \"A Short St\",\n\t\t},\n\t\t{\n\t\t\tValue:     \"Under Max Length String\",\n\t\t\tMaxLength: 1000,\n\t\t\tExpected:  \"Under Max Length String\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture range variable\n\t\tt.Run(tc.Value, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tresult := truncate(tc.Value, tc.MaxLength)\n\t\t\t// sanity check length\n\t\t\tif len(result) > tc.MaxLength {\n\t\t\t\tt.Errorf(\"Result %q longer than Max Length %d!\", result, tc.MaxLength)\n\t\t\t}\n\t\t\tif tc.Expected != result {\n\t\t\t\tt.Errorf(\"Strings did not match!\")\n\t\t\t\tt.Errorf(\"Expected: %q\", tc.Expected)\n\t\t\t\tt.Errorf(\"But got: %q\", result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVersion(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\tversion           string\n\t\tversionPreRelease string\n\t\tgitCommit         string\n\t\tgitCommitCount    string\n\t\twant              string\n\t}{\n\t\t{\n\t\t\tname:              \"With git commit count and with commit hash\",\n\t\t\tversion:           \"v0.27.0\",\n\t\t\tversionPreRelease: \"alpha\",\n\t\t\tgitCommit:         \"mocked-hash\",\n\t\t\tgitCommitCount:    \"mocked-count\",\n\t\t\twant:              \"v0.27.0-alpha.mocked-count+mocked-hash\",\n\t\t},\n\t\t{\n\t\t\tname:              \"Without git commit count and and with hash\",\n\t\t\tversion:           \"v0.27.0\",\n\t\t\tversionPreRelease: \"beta\",\n\t\t\tgitCommit:         \"mocked-hash\",\n\t\t\tgitCommitCount:    \"\",\n\t\t\twant:              \"v0.27.0-beta+mocked-hash\",\n\t\t},\n\t\t{\n\t\t\tname:              \"Without git commit hash and with commit count\",\n\t\t\tversion:           \"v0.30.0\",\n\t\t\tversionPreRelease: \"alpha\",\n\t\t\tgitCommit:         \"\",\n\t\t\tgitCommitCount:    \"mocked-count\",\n\t\t\twant:              \"v0.30.0-alpha.mocked-count\",\n\t\t},\n\t\t{\n\t\t\tname:              \"Without git commit hash and without commit count\",\n\t\t\tversion:           \"v0.27.0\",\n\t\t\tversionPreRelease: \"alpha\",\n\t\t\tgitCommit:         \"\",\n\t\t\tgitCommitCount:    \"\",\n\t\t\twant:              \"v0.27.0-alpha\",\n\t\t},\n\t\t{\n\t\t\tname:              \"Without pre release version\",\n\t\t\tversion:           \"v0.27.0\",\n\t\t\tversionPreRelease: \"\",\n\t\t\tgitCommit:         \"\",\n\t\t\tgitCommitCount:    \"\",\n\t\t\twant:              \"v0.27.0\",\n\t\t},\n\t\t{\n\t\t\tname:              \"Without pre release version and with git commit hash and count\",\n\t\t\tversion:           \"v0.27.0\",\n\t\t\tversionPreRelease: \"\",\n\t\t\tgitCommit:         \"mocked-commit\",\n\t\t\tgitCommitCount:    \"mocked-count\",\n\t\t\twant:              \"v0.27.0\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\t// TODO: this won't be necessary when we require go 1.22+\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tif got := version(tt.version, tt.versionPreRelease, tt.gitCommit, tt.gitCommitCount); got != tt.want {\n\t\t\t\tt.Errorf(\"Version() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/logger.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/cli\"\n\t\"sigs.k8s.io/kind/pkg/internal/env\"\n)\n\n// NewLogger returns the standard logger used by the kind CLI\n// This logger writes to os.Stderr\nfunc NewLogger() log.Logger {\n\tvar writer io.Writer = os.Stderr\n\tif env.IsSmartTerminal(writer) {\n\t\twriter = cli.NewSpinner(writer)\n\t}\n\treturn cli.NewLogger(writer, 0)\n}\n\n// ColorEnabled returns true if color is enabled for the logger\n// this should be used to control output\nfunc ColorEnabled(logger log.Logger) bool {\n\ttype maybeColorer interface {\n\t\tColorEnabled() bool\n\t}\n\tv, ok := logger.(maybeColorer)\n\treturn ok && v.ColorEnabled()\n}\n"
  },
  {
    "path": "pkg/errors/aggregate.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\n// NewAggregate is a k8s.io/apimachinery/pkg/util/errors.NewAggregate compatible wrapper\n// note that while it returns a StackTrace wrapped Aggregate\n// That has been Flattened and Reduced\nfunc NewAggregate(errlist []error) error {\n\treturn WithStack(\n\t\treduce(\n\t\t\tflatten(\n\t\t\t\tnewAggregate(errlist),\n\t\t\t),\n\t\t),\n\t)\n}\n\n// Errors returns the deepest Aggregate in a Cause chain\nfunc Errors(err error) []error {\n\tvar errors Aggregate\n\tfor {\n\t\tif v, ok := err.(Aggregate); ok {\n\t\t\terrors = v\n\t\t}\n\t\tif causerErr, ok := err.(Causer); ok {\n\t\t\terr = causerErr.Cause()\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\tif errors != nil {\n\t\treturn errors.Errors()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/errors/aggregate_forked.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\n/*\n   The contents of this file are lightly forked from k8s.io/apimachinery/pkg/util/errors\n   Forking makes kind easier to import, and this code is stable.\n\n   Currently the only source changes are renaming some methods so as to not\n   export them.\n*/\n\n// Aggregate represents an object that contains multiple errors, but does not\n// necessarily have singular semantic meaning.\n// The aggregate can be used with `errors.Is()` to check for the occurrence of\n// a specific error type.\n// Errors.As() is not supported, because the caller presumably cares about a\n// specific error of potentially multiple that match the given type.\n//\n// NOTE: this type is originally from k8s.io/apimachinery/pkg/util/errors.Aggregate\n// Since it is an interface, you can use the implementing types interchangeably\ntype Aggregate interface {\n\terror\n\tErrors() []error\n\tIs(error) bool\n}\n\nfunc newAggregate(errlist []error) Aggregate {\n\tif len(errlist) == 0 {\n\t\treturn nil\n\t}\n\t// In case of input error list contains nil\n\tvar errs []error\n\tfor _, e := range errlist {\n\t\tif e != nil {\n\t\t\terrs = append(errs, e)\n\t\t}\n\t}\n\tif len(errs) == 0 {\n\t\treturn nil\n\t}\n\treturn aggregate(errs)\n}\n\n// flatten takes an Aggregate, which may hold other Aggregates in arbitrary\n// nesting, and flattens them all into a single Aggregate, recursively.\nfunc flatten(agg Aggregate) Aggregate {\n\tresult := []error{}\n\tif agg == nil {\n\t\treturn nil\n\t}\n\tfor _, err := range agg.Errors() {\n\t\tif a, ok := err.(Aggregate); ok {\n\t\t\tr := flatten(a)\n\t\t\tif r != nil {\n\t\t\t\tresult = append(result, r.Errors()...)\n\t\t\t}\n\t\t} else {\n\t\t\tif err != nil {\n\t\t\t\tresult = append(result, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn newAggregate(result)\n}\n\n// reduce will return err or, if err is an Aggregate and only has one item,\n// the first item in the aggregate.\nfunc reduce(err error) error {\n\tif agg, ok := err.(Aggregate); ok && err != nil {\n\t\tswitch len(agg.Errors()) {\n\t\tcase 1:\n\t\t\treturn agg.Errors()[0]\n\t\tcase 0:\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn err\n}\n\n// This helper implements the error and Errors interfaces.  Keeping it private\n// prevents people from making an aggregate of 0 errors, which is not\n// an error, but does satisfy the error interface.\ntype aggregate []error\n\n// Error is part of the error interface.\nfunc (agg aggregate) Error() string {\n\tif len(agg) == 0 {\n\t\t// This should never happen, really.\n\t\treturn \"\"\n\t}\n\tif len(agg) == 1 {\n\t\treturn agg[0].Error()\n\t}\n\tseenerrs := sets.NewString()\n\tresult := \"\"\n\tagg.visit(func(err error) bool {\n\t\tmsg := err.Error()\n\t\tif seenerrs.Has(msg) {\n\t\t\treturn false\n\t\t}\n\t\tseenerrs.Insert(msg)\n\t\tif len(seenerrs) > 1 {\n\t\t\tresult += \", \"\n\t\t}\n\t\tresult += msg\n\t\treturn false\n\t})\n\tif len(seenerrs) == 1 {\n\t\treturn result\n\t}\n\treturn \"[\" + result + \"]\"\n}\n\nfunc (agg aggregate) Is(target error) bool {\n\treturn agg.visit(func(err error) bool {\n\t\treturn errors.Is(err, target)\n\t})\n}\n\nfunc (agg aggregate) visit(f func(err error) bool) bool {\n\tfor _, err := range agg {\n\t\tswitch err := err.(type) {\n\t\tcase aggregate:\n\t\t\tif match := err.visit(f); match {\n\t\t\t\treturn match\n\t\t\t}\n\t\tcase Aggregate:\n\t\t\tfor _, nestedErr := range err.Errors() {\n\t\t\t\tif match := f(nestedErr); match {\n\t\t\t\t\treturn match\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tif match := f(err); match {\n\t\t\t\treturn match\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Errors is part of the Aggregate interface.\nfunc (agg aggregate) Errors() []error {\n\treturn []error(agg)\n}\n"
  },
  {
    "path": "pkg/errors/aggregate_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestErrors(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"wrapped aggregate\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\terrs := []error{New(\"foo\"), Errorf(\"bar\")}\n\t\terr := Wrapf(NewAggregate(errs), \"baz: %s\", \"quux\")\n\t\tresult := Errors(err)\n\t\tassert.DeepEqual(t, errs, result)\n\t})\n\tt.Run(\"nil\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tresult := Errors(nil)\n\t\tvar expected []error\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n}\n"
  },
  {
    "path": "pkg/errors/concurrent.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"sync\"\n)\n\n// UntilErrorConcurrent runs all funcs in separate goroutines, returning the\n// first non-nil error returned from funcs, or nil if all funcs return nil\nfunc UntilErrorConcurrent(funcs []func() error) error {\n\terrCh := make(chan error, len(funcs))\n\tfor _, f := range funcs {\n\t\tf := f // capture f\n\t\tgo func() {\n\t\t\terrCh <- f()\n\t\t}()\n\t}\n\tfor i := 0; i < len(funcs); i++ {\n\t\tif err := <-errCh; err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// AggregateConcurrent runs fns concurrently, returning a NewAggregate if there are > 1 errors\nfunc AggregateConcurrent(funcs []func() error) error {\n\t// run all fns concurrently\n\tch := make(chan error, len(funcs))\n\tvar wg sync.WaitGroup\n\tfor _, f := range funcs {\n\t\tf := f // capture f\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tch <- f()\n\t\t}()\n\t}\n\twg.Wait()\n\tclose(ch)\n\t// collect up and return errors\n\terrs := []error{}\n\tfor err := range ch {\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\tif len(errs) > 1 {\n\t\treturn NewAggregate(errs)\n\t} else if len(errs) == 1 {\n\t\treturn errs[0]\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/errors/concurrent_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestUntilErrorConcurrent(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"first to return error\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t// test that the first function to return an error is returned\n\t\texpected := New(\"first\")\n\t\twait := make(chan bool)\n\t\tresult := UntilErrorConcurrent([]func() error{\n\t\t\tfunc() error {\n\t\t\t\t<-wait\n\t\t\t\treturn New(\"second\")\n\t\t\t},\n\t\t\tfunc() error {\n\t\t\t\treturn expected\n\t\t\t},\n\t\t})\n\t\twait <- true\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n\tt.Run(\"nil\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tresult := UntilErrorConcurrent([]func() error{\n\t\t\tfunc() error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t})\n\t\tvar expected error\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n}\n\nfunc TestAggregateConcurrent(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"all errors returned\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\t// test that the first function to return an error is returned\n\t\tfirst := New(\"first\")\n\t\tsecond := New(\"second\")\n\t\texpected := []error{first, second}\n\t\tresult := AggregateConcurrent([]func() error{\n\t\t\tfunc() error {\n\t\t\t\treturn second\n\t\t\t},\n\t\t\tfunc() error {\n\t\t\t\treturn first\n\t\t\t},\n\t\t})\n\t\tresultErrors := Errors(result)\n\t\t// We just want to check if we aggregate all the errors independent of the order\n\t\tsort.SliceStable(resultErrors, func(i, j int) bool {\n\t\t\treturn resultErrors[i].Error() < resultErrors[j].Error()\n\t\t})\n\t\tassert.DeepEqual(t, expected, resultErrors)\n\t})\n\tt.Run(\"one error\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\texpected := New(\"foo\")\n\t\tresult := AggregateConcurrent([]func() error{\n\t\t\tfunc() error {\n\t\t\t\treturn expected\n\t\t\t},\n\t\t})\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n\tt.Run(\"nil\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tresult := AggregateConcurrent([]func() error{\n\t\t\tfunc() error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t})\n\t\tvar expected error\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n}\n"
  },
  {
    "path": "pkg/errors/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package errors provides common utilities for dealing with errors\npackage errors\n"
  },
  {
    "path": "pkg/errors/errors.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\tstderrors \"errors\"\n\n\tpkgerrors \"github.com/pkg/errors\"\n)\n\n// New returns an error with the supplied message.\n// New also records the stack trace at the point it was called.\nfunc New(message string) error {\n\treturn pkgerrors.New(message)\n}\n\n// NewWithoutStack is like new but does NOT wrap with a stack\n// This is useful for exported errors\nfunc NewWithoutStack(message string) error {\n\treturn stderrors.New(message)\n}\n\n// Errorf formats according to a format specifier and returns the string as a\n// value that satisfies error. Errorf also records the stack trace at the\n// point it was called.\nfunc Errorf(format string, args ...interface{}) error {\n\treturn pkgerrors.Errorf(format, args...)\n}\n\n// Wrap returns an error annotating err with a stack trace at the point Wrap\n// is called, and the supplied message. If err is nil, Wrap returns nil.\nfunc Wrap(err error, message string) error {\n\treturn pkgerrors.Wrap(err, message)\n}\n\n// Wrapf returns an error annotating err with a stack trace at the point Wrapf\n// is called, and the format specifier. If err is nil, Wrapf returns nil.\nfunc Wrapf(err error, format string, args ...interface{}) error {\n\treturn pkgerrors.Wrapf(err, format, args...)\n}\n\n// WithStack annotates err with a stack trace at the point WithStack was called.\n// If err is nil, WithStack returns nil.\nfunc WithStack(err error) error {\n\treturn pkgerrors.WithStack(err)\n}\n\n// Causer is an interface to github.com/pkg/errors error's Cause() wrapping\ntype Causer interface {\n\t// Cause returns the underlying error\n\tCause() error\n}\n\n// StackTracer is an interface to github.com/pkg/errors error's StackTrace()\ntype StackTracer interface {\n\t// StackTrace returns the StackTrace ...\n\t// TODO: return our own type instead?\n\t// https://github.com/pkg/errors#roadmap\n\tStackTrace() pkgerrors.StackTrace\n}\n\n// StackTrace returns the deepest StackTrace in a Cause chain\n// https://github.com/pkg/errors/issues/173\nfunc StackTrace(err error) pkgerrors.StackTrace {\n\tvar stackErr error\n\tfor {\n\t\tif _, ok := err.(StackTracer); ok {\n\t\t\tstackErr = err\n\t\t}\n\t\tif causerErr, ok := err.(Causer); ok {\n\t\t\terr = causerErr.Cause()\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\tif stackErr != nil {\n\t\treturn stackErr.(StackTracer).StackTrace()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/errors/errors_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage errors\n\nimport (\n\t\"testing\"\n\n\tpkgerrors \"github.com/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestStackTrace(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"wrapped chain\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\terr := New(\"foo\")\n\t\texpected := err.(StackTracer).StackTrace()\n\t\tresult := StackTrace(Wrap(Wrap(err, \"bar\"), \"baz\"))\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n\tt.Run(\"nil\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tresult := StackTrace(nil)\n\t\tvar expected pkgerrors.StackTrace\n\t\tassert.DeepEqual(t, expected, result)\n\t})\n}\n"
  },
  {
    "path": "pkg/exec/default.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage exec\n\nimport \"context\"\n\n// DefaultCmder is a LocalCmder instance used for convenience, packages\n// originally using os/exec.Command can instead use pkg/kind/exec.Command\n// which forwards to this instance\n// TODO(bentheelder): swap this for testing\n// TODO(bentheelder): consider not using a global for this :^)\nvar DefaultCmder = &LocalCmder{}\n\n// Command is a convenience wrapper over DefaultCmder.Command\nfunc Command(command string, args ...string) Cmd {\n\treturn DefaultCmder.Command(command, args...)\n}\n\n// CommandContext is a convenience wrapper over DefaultCmder.CommandContext\nfunc CommandContext(ctx context.Context, command string, args ...string) Cmd {\n\treturn DefaultCmder.CommandContext(ctx, command, args...)\n}\n"
  },
  {
    "path": "pkg/exec/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package exec contains an interface for executing commands, along with helpers\n// TODO(bentheelder): add standardized timeout functionality & a default timeout\n// so that commands cannot hang indefinitely (!)\npackage exec\n"
  },
  {
    "path": "pkg/exec/helpers.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage exec\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"al.essio.dev/pkg/shellescape\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// PrettyCommand takes arguments identical to Cmder.Command,\n// it returns a pretty printed command that could be pasted into a shell\nfunc PrettyCommand(name string, args ...string) string {\n\tvar out strings.Builder\n\tout.WriteString(shellescape.Quote(name))\n\tfor _, arg := range args {\n\t\tout.WriteByte(' ')\n\t\tout.WriteString(shellescape.Quote(arg))\n\t}\n\treturn out.String()\n}\n\n// RunErrorForError returns a RunError if the error contains a RunError.\n// Otherwise it returns nil\nfunc RunErrorForError(err error) *RunError {\n\tvar runError *RunError\n\tfor {\n\t\tif rErr, ok := err.(*RunError); ok {\n\t\t\trunError = rErr\n\t\t}\n\t\tif causerErr, ok := err.(errors.Causer); ok {\n\t\t\terr = causerErr.Cause()\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn runError\n}\n\n// CombinedOutputLines is like os/exec's cmd.CombinedOutput(),\n// but over our Cmd interface, and instead of returning the byte buffer of\n// stderr + stdout, it scans these for lines and returns a slice of output lines\nfunc CombinedOutputLines(cmd Cmd) (lines []string, err error) {\n\tvar buff bytes.Buffer\n\tcmd.SetStdout(&buff)\n\tcmd.SetStderr(&buff)\n\terr = cmd.Run()\n\tscanner := bufio.NewScanner(&buff)\n\tfor scanner.Scan() {\n\t\tlines = append(lines, scanner.Text())\n\t}\n\treturn lines, err\n}\n\n// OutputLines is like os/exec's cmd.Output(),\n// but over our Cmd interface, and instead of returning the byte buffer of\n// stdout, it scans these for lines and returns a slice of output lines\nfunc OutputLines(cmd Cmd) (lines []string, err error) {\n\tvar buff bytes.Buffer\n\tcmd.SetStdout(&buff)\n\terr = cmd.Run()\n\tscanner := bufio.NewScanner(&buff)\n\tfor scanner.Scan() {\n\t\tlines = append(lines, scanner.Text())\n\t}\n\treturn lines, err\n}\n\n// Output is like os/exec's cmd.Output, but over our Cmd interface\nfunc Output(cmd Cmd) ([]byte, error) {\n\tvar buff bytes.Buffer\n\tcmd.SetStdout(&buff)\n\terr := cmd.Run()\n\treturn buff.Bytes(), err\n}\n\n// InheritOutput sets cmd's output to write to the current process's stdout and stderr\nfunc InheritOutput(cmd Cmd) Cmd {\n\tcmd.SetStderr(os.Stderr)\n\tcmd.SetStdout(os.Stdout)\n\treturn cmd\n}\n\n// RunWithStdoutReader runs cmd with stdout piped to readerFunc\nfunc RunWithStdoutReader(cmd Cmd, readerFunc func(io.Reader) error) error {\n\tpr, pw, err := os.Pipe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd.SetStdout(pw)\n\n\treturn errors.AggregateConcurrent([]func() error{\n\t\tfunc() error {\n\t\t\tdefer pr.Close()\n\t\t\treturn readerFunc(pr)\n\t\t},\n\t\tfunc() error {\n\t\t\tdefer pw.Close()\n\t\t\treturn cmd.Run()\n\t\t},\n\t})\n}\n\n// RunWithStdinWriter runs cmd with writerFunc piped to stdin\nfunc RunWithStdinWriter(cmd Cmd, writerFunc func(io.Writer) error) error {\n\tpr, pw, err := os.Pipe()\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd.SetStdin(pr)\n\n\treturn errors.AggregateConcurrent([]func() error{\n\t\tfunc() error {\n\t\t\tdefer pw.Close()\n\t\t\treturn writerFunc(pw)\n\t\t},\n\t\tfunc() error {\n\t\t\tdefer pr.Close()\n\t\t\treturn cmd.Run()\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "pkg/exec/local.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage exec\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\tosexec \"os/exec\"\n\t\"sync\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// LocalCmd wraps os/exec.Cmd, implementing the kind/pkg/exec.Cmd interface\ntype LocalCmd struct {\n\t*osexec.Cmd\n}\n\nvar _ Cmd = &LocalCmd{}\n\n// LocalCmder is a factory for LocalCmd, implementing Cmder\ntype LocalCmder struct{}\n\nvar _ Cmder = &LocalCmder{}\n\n// Command returns a new exec.Cmd backed by Cmd\nfunc (c *LocalCmder) Command(name string, arg ...string) Cmd {\n\treturn &LocalCmd{\n\t\tCmd: osexec.Command(name, arg...),\n\t}\n}\n\n// CommandContext is like Command but includes a context\nfunc (c *LocalCmder) CommandContext(ctx context.Context, name string, arg ...string) Cmd {\n\treturn &LocalCmd{\n\t\tCmd: osexec.CommandContext(ctx, name, arg...),\n\t}\n}\n\n// SetEnv sets env\nfunc (cmd *LocalCmd) SetEnv(env ...string) Cmd {\n\tcmd.Env = env\n\treturn cmd\n}\n\n// SetStdin sets stdin\nfunc (cmd *LocalCmd) SetStdin(r io.Reader) Cmd {\n\tcmd.Stdin = r\n\treturn cmd\n}\n\n// SetStdout set stdout\nfunc (cmd *LocalCmd) SetStdout(w io.Writer) Cmd {\n\tcmd.Stdout = w\n\treturn cmd\n}\n\n// SetStderr sets stderr\nfunc (cmd *LocalCmd) SetStderr(w io.Writer) Cmd {\n\tcmd.Stderr = w\n\treturn cmd\n}\n\n// Run runs the command\n// If the returned error is non-nil, it should be of type *RunError\nfunc (cmd *LocalCmd) Run() error {\n\t// Background:\n\t// Go's stdlib will setup and use a shared fd when cmd.Stderr == cmd.Stdout\n\t// In any other case, it will use different fds, which will involve\n\t// two different io.Copy goroutines writing to cmd.Stderr and cmd.Stdout\n\t//\n\t// Given this, we must synchronize capturing the output to a buffer\n\t// IFF ! interfaceEqual(cmd.Sterr, cmd.Stdout)\n\tvar combinedOutput bytes.Buffer\n\tvar combinedOutputWriter io.Writer = &combinedOutput\n\tif cmd.Stdout == nil && cmd.Stderr == nil {\n\t\t// Case 1: If stdout and stderr are nil, we can just use the buffer\n\t\t// The buffer will be == and Go will use one fd / goroutine\n\t\tcmd.Stdout = combinedOutputWriter\n\t\tcmd.Stderr = combinedOutputWriter\n\t} else if interfaceEqual(cmd.Stdout, cmd.Stderr) {\n\t\t// Case 2: If cmd.Stdout == cmd.Stderr go will still share the fd,\n\t\t// but we need to wrap with a MultiWriter to respect the other writer\n\t\t// and our buffer.\n\t\t// The MultiWriter will be == and Go will use one fd / goroutine\n\t\tcmd.Stdout = io.MultiWriter(cmd.Stdout, combinedOutputWriter)\n\t\tcmd.Stderr = cmd.Stdout\n\t} else {\n\t\t// Case 3: If cmd.Stdout != cmd.Stderr, we need to synchronize the\n\t\t// combined output writer.\n\t\t// Go will use different fds / write routines for stdout and stderr\n\t\tcombinedOutputWriter = &mutexWriter{\n\t\t\twriter: &combinedOutput,\n\t\t}\n\t\t// wrap writers if non-nil\n\t\tif cmd.Stdout != nil {\n\t\t\tcmd.Stdout = io.MultiWriter(cmd.Stdout, combinedOutputWriter)\n\t\t} else {\n\t\t\tcmd.Stdout = combinedOutputWriter\n\t\t}\n\t\tif cmd.Stderr != nil {\n\t\t\tcmd.Stderr = io.MultiWriter(cmd.Stderr, combinedOutputWriter)\n\t\t} else {\n\t\t\tcmd.Stderr = combinedOutputWriter\n\t\t}\n\t}\n\t// TODO: should be in the caller or logger should be injected somehow ...\n\tif err := cmd.Cmd.Run(); err != nil {\n\t\treturn errors.WithStack(&RunError{\n\t\t\tCommand: cmd.Args,\n\t\t\tOutput:  combinedOutput.Bytes(),\n\t\t\tInner:   err,\n\t\t})\n\t}\n\treturn nil\n}\n\n// interfaceEqual protects against panics from doing equality tests on\n// two interfaces with non-comparable underlying types.\n// This trivial is borrowed from the go stdlib in os/exec\n// Note that the recover will only happen if a is not comparable to b,\n// in which case we'll return false\n// We've lightly modified this to pass errcheck (explicitly ignoring recover)\nfunc interfaceEqual(a, b interface{}) bool {\n\tdefer func() {\n\t\t_ = recover()\n\t}()\n\treturn a == b\n}\n\n// mutexWriter is a simple synchronized wrapper around an io.Writer\ntype mutexWriter struct {\n\twriter io.Writer\n\tmu     sync.Mutex\n}\n\nfunc (m *mutexWriter) Write(b []byte) (int, error) {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\tn, err := m.writer.Write(b)\n\treturn n, err\n}\n"
  },
  {
    "path": "pkg/exec/types.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage exec\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// Cmd abstracts over running a command somewhere, this is useful for testing\ntype Cmd interface {\n\t// Run executes the command (like os/exec.Cmd.Run), it should return\n\t// a *RunError if there is any error\n\tRun() error\n\t// Each entry should be of the form \"key=value\"\n\tSetEnv(...string) Cmd\n\tSetStdin(io.Reader) Cmd\n\tSetStdout(io.Writer) Cmd\n\tSetStderr(io.Writer) Cmd\n}\n\n// Cmder abstracts over creating commands\ntype Cmder interface {\n\t// command, args..., just like os/exec.Cmd\n\tCommand(string, ...string) Cmd\n\tCommandContext(context.Context, string, ...string) Cmd\n}\n\n// RunError represents an error running a Cmd\ntype RunError struct {\n\tCommand []string // [Name Args...]\n\tOutput  []byte   // Captured Stdout / Stderr of the command\n\tInner   error    // Underlying error if any\n}\n\nvar _ error = &RunError{}\n\nfunc (e *RunError) Error() string {\n\t// TODO(BenTheElder): implement formatter, and show output for %+v ?\n\treturn fmt.Sprintf(\"command \\\"%s\\\" failed with error: %v\", e.PrettyCommand(), e.Inner)\n}\n\n// PrettyCommand pretty prints the command in a way that could be pasted\n// into a shell\nfunc (e *RunError) PrettyCommand() string {\n\treturn PrettyCommand(e.Command[0], e.Command[1:]...)\n}\n\n// Cause mimics github.com/pkg/errors's Cause pattern for errors\nfunc (e *RunError) Cause() error {\n\tif e.Inner != nil {\n\t\treturn e.Inner\n\t}\n\treturn e\n}\n"
  },
  {
    "path": "pkg/fs/fs.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package fs contains utilities for interacting with the host filesystem\n// in a docker friendly way\n// TODO(bentheelder): this should be internal\npackage fs\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n)\n\n// TempDir is like os.MkdirTemp, but more docker friendly\nfunc TempDir(dir, prefix string) (name string, err error) {\n\t// create a tempdir as normal\n\tname, err = os.MkdirTemp(dir, prefix)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// on macOS $TMPDIR is typically /var/..., which is not mountable\n\t// /private/var/... is the mountable equivalent\n\tif runtime.GOOS == \"darwin\" && strings.HasPrefix(name, \"/var/\") {\n\t\tname = filepath.Join(\"/private\", name)\n\t}\n\treturn name, nil\n}\n\n// IsAbs is like filepath.IsAbs but also considering posix absolute paths\n// to be absolute even if filepath.IsAbs would not\n// This fixes the case of Posix paths on Windows\nfunc IsAbs(hostPath string) bool {\n\treturn path.IsAbs(hostPath) || filepath.IsAbs(hostPath)\n}\n\n// Copy recursively directories, symlinks, files copies from src to dst\n// Copy will make dirs as necessary, and keep file modes\n// Symlinks will be dereferenced similar to `cp -r src dst`\nfunc Copy(src, dst string) error {\n\t// get source info\n\tinfo, err := os.Lstat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// make sure dest dir exists\n\tif err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\t// do real copy work\n\treturn copyWithSrcInfo(src, dst, info)\n}\n\nfunc copyWithSrcInfo(src, dst string, info os.FileInfo) error {\n\tif info.Mode()&os.ModeSymlink != 0 {\n\t\treturn copySymlink(src, dst)\n\t}\n\tif info.IsDir() {\n\t\treturn copyDir(src, dst, info)\n\t}\n\treturn copyFile(src, dst, info)\n}\n\n// CopyFile copies a file from src to dst\nfunc CopyFile(src, dst string) (err error) {\n\t// get source information\n\tinfo, err := os.Stat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn copyFile(src, dst, info)\n}\n\nfunc copyFile(src, dst string, info os.FileInfo) error {\n\t// open src for reading\n\tin, err := os.Open(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer in.Close()\n\t// create dst file\n\t// this is like f, err := os.Create(dst); os.Chmod(f.Name(), src.Mode())\n\tout, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())\n\tif err != nil {\n\t\treturn err\n\t}\n\t// make sure we close the file\n\tdefer func() {\n\t\tcloseErr := out.Close()\n\t\t// if we weren't returning an error\n\t\tif err == nil {\n\t\t\terr = closeErr\n\t\t}\n\t}()\n\t// actually copy\n\tif _, err = io.Copy(out, in); err != nil {\n\t\treturn err\n\t}\n\terr = out.Sync()\n\treturn err\n}\n\n// copySymlink dereferences and then copies a symlink\nfunc copySymlink(src, dst string) error {\n\t// read through the symlink\n\trealSrc, err := filepath.EvalSymlinks(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := os.Lstat(realSrc)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// copy the underlying contents\n\treturn copyWithSrcInfo(realSrc, dst, info)\n}\n\nfunc copyDir(src, dst string, info os.FileInfo) error {\n\t// make sure the target dir exists\n\tif err := os.MkdirAll(dst, info.Mode()); err != nil {\n\t\treturn err\n\t}\n\t// copy every source dir entry\n\tentries, err := os.ReadDir(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, entry := range entries {\n\t\tentrySrc := filepath.Join(src, entry.Name())\n\t\tentryDst := filepath.Join(dst, entry.Name())\n\t\tfileInfo, err := entry.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := copyWithSrcInfo(entrySrc, entryDst, fileInfo); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/cluster_util.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\n// ClusterHasIPv6 returns true if the cluster should have IPv6 enabled due to either\n// being IPv6 cluster family or Dual Stack\nfunc ClusterHasIPv6(c *Cluster) bool {\n\treturn c.Networking.IPFamily == IPv6Family || c.Networking.IPFamily == DualStackFamily\n}\n\n// ClusterHasImplicitLoadBalancer returns true if this cluster has an implicit api-server LoadBalancer\nfunc ClusterHasImplicitLoadBalancer(c *Cluster) bool {\n\tcontrolPlanes := 0\n\tfor _, node := range c.Nodes {\n\t\tif node.Role == ControlPlaneRole {\n\t\t\tcontrolPlanes++\n\t\t}\n\t}\n\treturn controlPlanes > 1\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/cluster_util_test.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestClusterHasIPv6(t *testing.T) {\n\tcases := []struct {\n\t\tName     string\n\t\tc        *Cluster\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tName: \"IPv6\",\n\t\t\tc: &Cluster{\n\t\t\t\tNetworking: Networking{\n\t\t\t\t\tIPFamily: IPv6Family,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tName: \"IPv4\",\n\t\t\tc: &Cluster{\n\t\t\t\tNetworking: Networking{\n\t\t\t\t\tIPFamily: IPv4Family,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tName: \"DualStack\",\n\t\t\tc: &Cluster{\n\t\t\t\tNetworking: Networking{\n\t\t\t\t\tIPFamily: DualStackFamily,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture loop var\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tr := ClusterHasIPv6(tc.c)\n\t\t\tassert.BoolEqual(t, tc.expected, r)\n\t\t})\n\t}\n}\n\nfunc TestClusterHasImplicitLoadBalancer(t *testing.T) {\n\tcases := []struct {\n\t\tName     string\n\t\tc        *Cluster\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tName: \"One Node\",\n\t\t\tc: &Cluster{\n\t\t\t\tNodes: []Node{\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tName: \"Two Control Planes\",\n\t\t\tc: &Cluster{\n\t\t\t\tNodes: []Node{\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tName: \"Three Control Planes\",\n\t\t\tc: &Cluster{\n\t\t\t\tNodes: []Node{\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tName: \"One Control Plane, Multiple Workers\",\n\t\t\tc: &Cluster{\n\t\t\t\tNodes: []Node{\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: WorkerRole},\n\t\t\t\t\t{Role: WorkerRole},\n\t\t\t\t\t{Role: WorkerRole},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tName: \"Multiple Control Planes, Multiple Workers\",\n\t\t\tc: &Cluster{\n\t\t\t\tNodes: []Node{\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: ControlPlaneRole},\n\t\t\t\t\t{Role: WorkerRole},\n\t\t\t\t\t{Role: WorkerRole},\n\t\t\t\t\t{Role: WorkerRole},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture loop var\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tr := ClusterHasImplicitLoadBalancer(tc.c)\n\t\t\tassert.BoolEqual(t, tc.expected, r)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/convert_v1alpha4.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\tv1alpha4 \"sigs.k8s.io/kind/pkg/apis/config/v1alpha4\"\n)\n\n// Convertv1alpha4 converts a v1alpha4 cluster to a cluster at the internal API version\nfunc Convertv1alpha4(in *v1alpha4.Cluster) *Cluster {\n\tin = in.DeepCopy() // deep copy first to avoid touching the original\n\tout := &Cluster{\n\t\tName:                            in.Name,\n\t\tNodes:                           make([]Node, len(in.Nodes)),\n\t\tFeatureGates:                    in.FeatureGates,\n\t\tRuntimeConfig:                   in.RuntimeConfig,\n\t\tKubeadmConfigPatches:            in.KubeadmConfigPatches,\n\t\tKubeadmConfigPatchesJSON6902:    make([]PatchJSON6902, len(in.KubeadmConfigPatchesJSON6902)),\n\t\tContainerdConfigPatches:         in.ContainerdConfigPatches,\n\t\tContainerdConfigPatchesJSON6902: in.ContainerdConfigPatchesJSON6902,\n\t}\n\n\tfor i := range in.Nodes {\n\t\tconvertv1alpha4Node(&in.Nodes[i], &out.Nodes[i])\n\t}\n\n\tconvertv1alpha4Networking(&in.Networking, &out.Networking)\n\n\tfor i := range in.KubeadmConfigPatchesJSON6902 {\n\t\tconvertv1alpha4PatchJSON6902(&in.KubeadmConfigPatchesJSON6902[i], &out.KubeadmConfigPatchesJSON6902[i])\n\t}\n\n\treturn out\n}\n\nfunc convertv1alpha4Node(in *v1alpha4.Node, out *Node) {\n\tout.Role = NodeRole(in.Role)\n\tout.Image = in.Image\n\n\tout.Labels = in.Labels\n\tout.KubeadmConfigPatches = in.KubeadmConfigPatches\n\tout.ExtraMounts = make([]Mount, len(in.ExtraMounts))\n\tout.ExtraPortMappings = make([]PortMapping, len(in.ExtraPortMappings))\n\tout.KubeadmConfigPatchesJSON6902 = make([]PatchJSON6902, len(in.KubeadmConfigPatchesJSON6902))\n\n\tfor i := range in.ExtraMounts {\n\t\tconvertv1alpha4Mount(&in.ExtraMounts[i], &out.ExtraMounts[i])\n\t}\n\n\tfor i := range in.ExtraPortMappings {\n\t\tconvertv1alpha4PortMapping(&in.ExtraPortMappings[i], &out.ExtraPortMappings[i])\n\t}\n\n\tfor i := range in.KubeadmConfigPatchesJSON6902 {\n\t\tconvertv1alpha4PatchJSON6902(&in.KubeadmConfigPatchesJSON6902[i], &out.KubeadmConfigPatchesJSON6902[i])\n\t}\n}\n\nfunc convertv1alpha4PatchJSON6902(in *v1alpha4.PatchJSON6902, out *PatchJSON6902) {\n\tout.Group = in.Group\n\tout.Version = in.Version\n\tout.Kind = in.Kind\n\tout.Patch = in.Patch\n}\n\nfunc convertv1alpha4Networking(in *v1alpha4.Networking, out *Networking) {\n\tout.IPFamily = ClusterIPFamily(in.IPFamily)\n\tout.APIServerPort = in.APIServerPort\n\tout.APIServerAddress = in.APIServerAddress\n\tout.PodSubnet = in.PodSubnet\n\tout.KubeProxyMode = ProxyMode(in.KubeProxyMode)\n\tout.ServiceSubnet = in.ServiceSubnet\n\tout.DisableDefaultCNI = in.DisableDefaultCNI\n\tout.DNSSearch = in.DNSSearch\n}\n\nfunc convertv1alpha4Mount(in *v1alpha4.Mount, out *Mount) {\n\tout.ContainerPath = in.ContainerPath\n\tout.HostPath = in.HostPath\n\tout.Readonly = in.Readonly\n\tout.SelinuxRelabel = in.SelinuxRelabel\n\tout.Propagation = MountPropagation(in.Propagation)\n}\n\nfunc convertv1alpha4PortMapping(in *v1alpha4.PortMapping, out *PortMapping) {\n\tout.ContainerPort = in.ContainerPort\n\tout.HostPort = in.HostPort\n\tout.ListenAddress = in.ListenAddress\n\tout.Protocol = PortMappingProtocol(in.Protocol)\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/default.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// this comment makes golint ignore this file, feel free to edit the file.\n// Code generated by not-actually-generated-but-go-away-golint. DO NOT EDIT.\n// https://github.com/kubernetes/code-generator/issues/30\n\npackage config\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/apis/config/defaults\"\n\t\"sigs.k8s.io/kind/pkg/cluster/constants\"\n)\n\n// SetDefaultsCluster sets uninitialized fields to their default value.\nfunc SetDefaultsCluster(obj *Cluster) {\n\t// default cluster name\n\tif obj.Name == \"\" {\n\t\tobj.Name = constants.DefaultClusterName\n\t}\n\n\t// default to a one node cluster\n\tif len(obj.Nodes) == 0 {\n\t\tobj.Nodes = []Node{\n\t\t\t{\n\t\t\t\tImage: defaults.Image,\n\t\t\t\tRole:  ControlPlaneRole,\n\t\t\t},\n\t\t}\n\t}\n\n\t// default nodes\n\tfor i := range obj.Nodes {\n\t\ta := &obj.Nodes[i]\n\t\tSetDefaultsNode(a)\n\t}\n\tif obj.Networking.IPFamily == \"\" {\n\t\tobj.Networking.IPFamily = IPv4Family\n\t}\n\n\t// default to listening on 127.0.0.1:randomPort on ipv4\n\t// and [::1]:randomPort on ipv6\n\tif obj.Networking.APIServerAddress == \"\" {\n\t\tobj.Networking.APIServerAddress = \"127.0.0.1\"\n\t\tif obj.Networking.IPFamily == IPv6Family {\n\t\t\tobj.Networking.APIServerAddress = \"::1\"\n\t\t}\n\t}\n\n\t// default the pod CIDR\n\tif obj.Networking.PodSubnet == \"\" {\n\t\tobj.Networking.PodSubnet = \"10.244.0.0/16\"\n\t\tif obj.Networking.IPFamily == IPv6Family {\n\t\t\t// node-mask cidr default is /64 so we need a larger subnet, we use /56 following best practices\n\t\t\t// xref: https://www.ripe.net/publications/docs/ripe-690#4--size-of-end-user-prefix-assignment---48---56-or-something-else-\n\t\t\tobj.Networking.PodSubnet = \"fd00:10:244::/56\"\n\t\t}\n\t\tif obj.Networking.IPFamily == DualStackFamily {\n\t\t\tobj.Networking.PodSubnet = \"10.244.0.0/16,fd00:10:244::/56\"\n\t\t}\n\t}\n\n\t// default the service CIDR using the kubeadm default\n\t// https://github.com/kubernetes/kubernetes/blob/746404f82a28e55e0b76ffa7e40306fb88eb3317/cmd/kubeadm/app/apis/kubeadm/v1beta2/defaults.go#L32\n\t// Note: kubeadm is using a /12 subnet, that may allocate a 2^20 bitmap in etcd\n\t// we allocate a /16 subnet that allows 65535 services (current Kubernetes tested limit is O(10k) services)\n\tif obj.Networking.ServiceSubnet == \"\" {\n\t\tobj.Networking.ServiceSubnet = \"10.96.0.0/16\"\n\t\tif obj.Networking.IPFamily == IPv6Family {\n\t\t\tobj.Networking.ServiceSubnet = \"fd00:10:96::/112\"\n\t\t}\n\t\tif obj.Networking.IPFamily == DualStackFamily {\n\t\t\tobj.Networking.ServiceSubnet = \"10.96.0.0/16,fd00:10:96::/112\"\n\t\t}\n\t}\n\t// default the KubeProxyMode using iptables as it's already the default\n\tif obj.Networking.KubeProxyMode == \"\" {\n\t\tobj.Networking.KubeProxyMode = IPTablesProxyMode\n\t}\n}\n\n// SetDefaultsNode sets uninitialized fields to their default value.\nfunc SetDefaultsNode(obj *Node) {\n\tif obj.Image == \"\" {\n\t\tobj.Image = defaults.Image\n\t}\n\n\tif obj.Role == \"\" {\n\t\tobj.Role = ControlPlaneRole\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package config implements the current apiVersion of the `kind` Config\n// along with some common abstractions\n//\n// +k8s:deepcopy-gen=package\n// +k8s:conversion-gen=sigs.k8s.io/kind/pkg/internal/apis/config\n// +k8s:defaulter-gen=TypeMeta\npackage config\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/convert.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage encoding\n\nimport (\n\t\"sigs.k8s.io/kind/pkg/apis/config/v1alpha4\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// V1Alpha4ToInternal converts to the internal API version\nfunc V1Alpha4ToInternal(cluster *v1alpha4.Cluster) *config.Cluster {\n\tv1alpha4.SetDefaultsCluster(cluster)\n\treturn config.Convertv1alpha4(cluster)\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/doc.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package encoding implements utilities for decoding from yaml the `kind` Config\npackage encoding\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/load.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage encoding\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\n\tyaml \"go.yaml.in/yaml/v3\"\n\n\t\"sigs.k8s.io/kind/pkg/apis/config/v1alpha4\"\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// Load reads the file at path and attempts to convert into a `kind` Config; the file\n// can be one of the different API versions defined in scheme.\n// If path == \"\" then the default config is returned\n// If path == \"-\" then reads from stdin\nfunc Load(path string) (*config.Cluster, error) {\n\t// special case: empty path -> default config\n\t// TODO(bentheelder): consider removing this\n\tif path == \"\" {\n\t\tout := &config.Cluster{}\n\t\tconfig.SetDefaultsCluster(out)\n\t\treturn out, nil\n\t}\n\n\t// read in file\n\traw, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error reading file\")\n\t}\n\n\treturn Parse(raw)\n}\n\n// Parse parses a cluster config from raw (yaml) bytes\n// It will always return the current internal version after defaulting and\n// conversion from the read version\nfunc Parse(raw []byte) (*config.Cluster, error) {\n\t// get kind & apiVersion\n\ttm := typeMeta{}\n\tif err := yaml.Unmarshal(raw, &tm); err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not determine kind / apiVersion for config\")\n\t}\n\n\t// decode specific (apiVersion, kind)\n\tswitch tm.APIVersion {\n\t// handle v1alpha4\n\tcase \"kind.x-k8s.io/v1alpha4\":\n\t\tif tm.Kind != \"Cluster\" {\n\t\t\treturn nil, errors.Errorf(\"unknown kind %s for apiVersion: %s\", tm.Kind, tm.APIVersion)\n\t\t}\n\t\t// load version\n\t\tcfg := &v1alpha4.Cluster{}\n\t\tif err := yamlUnmarshalStrict(raw, cfg); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"unable to decode config\")\n\t\t}\n\t\t// apply defaults for version and convert\n\t\treturn V1Alpha4ToInternal(cfg), nil\n\t}\n\n\t// unknown apiVersion if we haven't already returned ...\n\treturn nil, errors.Errorf(\"unknown apiVersion: %s\", tm.APIVersion)\n}\n\n// basically metav1.TypeMeta, but with yaml tags\ntype typeMeta struct {\n\tKind       string `yaml:\"kind,omitempty\"`\n\tAPIVersion string `yaml:\"apiVersion,omitempty\"`\n}\n\nfunc yamlUnmarshalStrict(raw []byte, v interface{}) error {\n\td := yaml.NewDecoder(bytes.NewReader(raw))\n\td.KnownFields(true)\n\treturn d.Decode(v)\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/load_test.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage encoding\n\nimport (\n\t\"testing\"\n)\n\nfunc TestLoadCurrent(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tTestName    string\n\t\tPath        string\n\t\tExpectError bool\n\t}{\n\t\t{\n\t\t\tTestName:    \"example config\",\n\t\t\tPath:        \"./../../../../../site/content/docs/user/kind-example-config.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"no config\",\n\t\t\tPath:        \"\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 minimal\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-minimal.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 config with 2 nodes\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-minimal-two-nodes.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 full HA\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-full-ha.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 many fields set\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-many-fields.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 config with patches\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-kind-patches.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 config with workers patches\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-kind-workers-patches.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 config with port mapping and mount\",\n\t\t\tPath:        \"./testdata/v1alpha4/valid-port-and-mount.yaml\",\n\t\t\tExpectError: false,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 non-existent field\",\n\t\t\tPath:        \"./testdata/v1alpha4/invalid-bogus-field.yaml\",\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"v1alpha4 bad indentation\",\n\t\t\tPath:        \"./testdata/v1alpha4/invalid-bad-indent.yaml\",\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"invalid path\",\n\t\t\tPath:        \"./testdata/not-a-file.bogus\",\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"Invalid apiversion\",\n\t\t\tPath:        \"./testdata/invalid-apiversion.yaml\",\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"Invalid kind\",\n\t\t\tPath:        \"./testdata/invalid-kind.yaml\",\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tTestName:    \"Invalid yaml\",\n\t\t\tPath:        \"./testdata/invalid-yaml.yaml\",\n\t\t\tExpectError: true,\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tc := c // capture loop variable\n\t\tt.Run(c.TestName, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\t_, err := Load(c.Path)\n\t\t\t// the error can be:\n\t\t\t// - nil, in which case we should expect no errors or fail\n\t\t\tif err != nil {\n\t\t\t\tif !c.ExpectError {\n\t\t\t\t\tt.Fatalf(\"unexpected error while Loading config: %v\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// - not nil, in which case we should expect errors or fail\n\t\t\tif c.ExpectError {\n\t\t\t\tt.Fatalf(\"unexpected lack or error while Loading config\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/invalid-apiversion.yaml",
    "content": "# this file contains an invalid config api version for testing\nkind: Node\napiVersion: not-valid\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/invalid-kind.yaml",
    "content": "# this file contains an invalid config kind for testing\nkind: not-valid\napiVersion: kind.x-k8s.io/v1alpha4"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/invalid-yaml.yaml",
    "content": "# intentionally invalid yaml file for testing\n\":"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/invalid-bad-indent.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv6\nnodes:\n- role: control-plane\n- role: worker\n  extraMounts:\n  - containerPath: /foo\n    hostPath: /bar\n    readOnly: true\n    selinuxRelabel: false\n    propagation: Bidirectional\n  extraPortMappings:\n  - containerPort: 8080\n    hostPort: 8080\n    protocol: UDP\n  - role: worker\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/invalid-bogus-field.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv6\nnodes:\n- role: control-plane\n- role: worker\n  extraMounts:\n  - containerPath: /foo\n    hostPath: /bar\n    readOnly: true\n    selinuxRelabel: false\n    propagation: Bidirectional\n  extraPortMappings:\n  - containerPort: 8080\n    hostPort: 8080\n    protocol: UDP\n    nOtAReaLFielD: bar\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-full-ha.yaml",
    "content": "# technically valid, config file with a full ha cluster\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: control-plane\n- role: control-plane\n- role: worker\n- role: worker\n- role: worker"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-kind-patches.yaml",
    "content": "# this config file contains all config fields with comments\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n# patch the generated kubeadm config with some extra settings\nkubeadmConfigPatches:\n- |\n  apiVersion: kubeadm.k8s.io/v1beta2\n  kind: ClusterConfiguration\n  metadata:\n    name: config\n  networking:\n    serviceSubnet: 10.0.0.0/16\n# patch it further using a JSON 6902 patch\nkubeadmConfigPatchesJSON6902:\n- group: kubeadm.k8s.io\n  version: v1beta2\n  kind: ClusterConfiguration\n  patch: |\n    - op: add\n      path: /apiServer/certSANs/-\n      value: my-hostname\n# patch containerd config\ncontainerdConfigPatches:\n# configure a local insecure registry\n- |-\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"registry:5000\"]\n# 1 control plane node and 3 workers\nnodes:\n# the control plane node config\n- role: control-plane\n# the three workers\n- role: worker\n- role: worker\n- role: worker\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-kind-workers-patches.yaml",
    "content": "# this config file contains all config fields with comments\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n\n# 1 control plane node and 3 workers\nnodes:\n# the control plane node config\n- role: control-plane\n# the three workers\n- role: worker\n  kubeadmConfigPatches:\n  - |\n    apiVersion: kubeadm.k8s.io/v1beta2\n    kind: JoinConfiguration\n    nodeRegistration:\n      kubeletExtraArgs:\n        node-labels: \"aqua=gateway\"\n        authorization-mode: \"AlwaysAllow\"\n- role: worker\n  kubeadmConfigPatchesJSON6902:\n  - group: kubelet.config.k8s.io\n    version: v1beta1\n    kind: KubeletConfiguration\n    patch: |\n      - op: add\n        path: /authentication\n        value: {\"anonymous\": {\"enabled\": true}}\n      - op: add\n        path: /tlsCipherSuites\n        value: [\"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\",\"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\"]\n- role: worker\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-many-fields.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nname: not-default\nfeatureGates:\n  AllBeta: false\nnetworking:\n  ipFamily: ipv6\nnodes:\n- role: control-plane\n- role: worker\n  extraMounts:\n  - containerPath: /foo\n    hostPath: /bar\n    readOnly: true\n    selinuxRelabel: false\n    propagation: Bidirectional\n  extraPortMappings:\n  - containerPort: 8080\n    hostPort: 8080\n    protocol: UDP\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-minimal-two-nodes.yaml",
    "content": "# technically valid, minimal config file with two nodes\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-minimal.yaml",
    "content": "# technically valid, minimal config file\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n"
  },
  {
    "path": "pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-port-and-mount.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  # map an extra port\n  extraPortMappings:\n  - hostPort: 80\n    containerPort: 80\n  # mount an extra path from the host\n  extraMounts:\n  - hostPath: ./foo\n    containerPath: /bar"
  },
  {
    "path": "pkg/internal/apis/config/types.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\n/*\nNOTE: unlike the public types these should not have serialization tags and\nshould stay 100% internal. These are used to pass around the processed public\nconfig for internal usage.\n*/\n\n// Cluster contains kind cluster configuration\ntype Cluster struct {\n\t// The cluster name.\n\t// Optional, this will be overridden by --name / KIND_CLUSTER_NAME\n\tName string\n\n\t// Nodes contains the list of nodes defined in the `kind` Cluster\n\t// If unset this will default to a single control-plane node\n\t// Note that if more than one control plane is specified, an external\n\t// control plane load balancer will be provisioned implicitly\n\tNodes []Node\n\n\t/* Advanced fields */\n\n\t// Networking contains cluster wide network settings\n\tNetworking Networking\n\n\t// FeatureGates contains a map of Kubernetes feature gates to whether they\n\t// are enabled. The feature gates specified here are passed to all Kubernetes components as flags or in config.\n\t//\n\t// https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/\n\tFeatureGates map[string]bool\n\n\t// RuntimeConfig Keys and values are translated into --runtime-config values for kube-apiserver, separated by commas.\n\t//\n\t// Use this to enable alpha APIs.\n\tRuntimeConfig map[string]string\n\n\t// KubeadmConfigPatches are applied to the generated kubeadm config as\n\t// strategic merge patches to `kustomize build` internally\n\t// https://github.com/kubernetes/community/blob/a9cf5c8f3380bb52ebe57b1e2dbdec136d8dd484/contributors/devel/sig-api-machinery/strategic-merge-patch.md\n\t// This should be an inline yaml blob-string\n\tKubeadmConfigPatches []string\n\n\t// KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config\n\t// as patchesJson6902 to `kustomize build`\n\tKubeadmConfigPatchesJSON6902 []PatchJSON6902\n\n\t// ContainerdConfigPatches are applied to every node's containerd config\n\t// in the order listed.\n\t// These should be toml stringsto be applied as merge patches\n\tContainerdConfigPatches []string\n\n\t// ContainerdConfigPatchesJSON6902 are applied to every node's containerd config\n\t// in the order listed.\n\t// These should be YAML or JSON formatting RFC 6902 JSON patches\n\tContainerdConfigPatchesJSON6902 []string\n}\n\n// Node contains settings for a node in the `kind` Cluster.\n// A node in kind config represent a container that will be provisioned with all the components\n// required for the assigned role in the Kubernetes cluster\ntype Node struct {\n\t// Role defines the role of the node in the in the Kubernetes cluster\n\t// created by kind\n\t//\n\t// Defaults to \"control-plane\"\n\tRole NodeRole\n\n\t// Image is the node image to use when creating this node\n\t// If unset a default image will be used, see defaults.Image\n\tImage string\n\n\t// Labels are the labels with which the respective node will be labeled\n\tLabels map[string]string\n\n\t/* Advanced fields */\n\n\t// ExtraMounts describes additional mount points for the node container\n\t// These may be used to bind a hostPath\n\tExtraMounts []Mount\n\n\t// ExtraPortMappings describes additional port mappings for the node container\n\t// binded to a host Port\n\tExtraPortMappings []PortMapping\n\n\t// KubeadmConfigPatches are applied to the generated kubeadm config as\n\t// strategic merge patches to `kustomize build` internally\n\t// https://github.com/kubernetes/community/blob/a9cf5c8f3380bb52ebe57b1e2dbdec136d8dd484/contributors/devel/sig-api-machinery/strategic-merge-patch.md\n\t// This should be an inline yaml blob-string\n\tKubeadmConfigPatches []string\n\n\t// KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config\n\t// as patchesJson6902 to `kustomize build`\n\tKubeadmConfigPatchesJSON6902 []PatchJSON6902\n}\n\n// NodeRole defines possible role for nodes in a Kubernetes cluster managed by `kind`\ntype NodeRole string\n\nconst (\n\t// ControlPlaneRole identifies a node that hosts a Kubernetes control-plane.\n\t// NOTE: in single node clusters, control-plane nodes act also as a worker\n\t// nodes, in which case the taint will be removed. see:\n\t// https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#control-plane-node-isolation\n\tControlPlaneRole NodeRole = \"control-plane\"\n\t// WorkerRole identifies a node that hosts a Kubernetes worker\n\tWorkerRole NodeRole = \"worker\"\n)\n\n// Networking contains cluster wide network settings\ntype Networking struct {\n\t// IPFamily is the network cluster model, currently it can be ipv4 or ipv6\n\tIPFamily ClusterIPFamily\n\t// APIServerPort is the listen port on the host for the Kubernetes API Server\n\t// Defaults to a random port on the host obtained by kind\n\t//\n\t// NOTE: if you set the special value of `-1` then the node backend\n\t// (docker, podman...) will be left to pick the port instead.\n\t// This is potentially useful for remote hosts, BUT it means when the container\n\t// is restarted it will be randomized. Leave this unset to allow kind to pick it.\n\tAPIServerPort int32\n\t// APIServerAddress is the listen address on the host for the Kubernetes\n\t// API Server. This should be an IP address.\n\t//\n\t// Defaults to 127.0.0.1\n\tAPIServerAddress string\n\t// PodSubnet is the CIDR used for pod IPs\n\t// kind will select a default if unspecified\n\tPodSubnet string\n\t// ServiceSubnet is the CIDR used for services VIPs\n\t// kind will select a default if unspecified\n\tServiceSubnet string\n\t// If DisableDefaultCNI is true, kind will not install the default CNI setup.\n\t// Instead the user should install their own CNI after creating the cluster.\n\tDisableDefaultCNI bool\n\t// KubeProxyMode defines if kube-proxy should operate in iptables, ipvs or nftables mode\n\tKubeProxyMode ProxyMode\n\t// DNSSearch defines the DNS search domain to use for nodes. If not set, this will be inherited from the host.\n\tDNSSearch *[]string\n}\n\n// ClusterIPFamily defines cluster network IP family\ntype ClusterIPFamily string\n\nconst (\n\t// IPv4Family sets ClusterIPFamily to ipv4\n\tIPv4Family ClusterIPFamily = \"ipv4\"\n\t// IPv6Family sets ClusterIPFamily to ipv6\n\tIPv6Family ClusterIPFamily = \"ipv6\"\n\t// DualStackFamily sets ClusterIPFamily to dual\n\tDualStackFamily ClusterIPFamily = \"dual\"\n)\n\n// ProxyMode defines a proxy mode for kube-proxy\ntype ProxyMode string\n\nconst (\n\t// IPTablesProxyMode sets ProxyMode to iptables\n\tIPTablesProxyMode ProxyMode = \"iptables\"\n\t// IPVSProxyMode sets ProxyMode to ipvs\n\tIPVSProxyMode ProxyMode = \"ipvs\"\n\t// NFTablesProxyMode sets ProxyMode to nftables\n\tNFTablesProxyMode ProxyMode = \"nftables\"\n\t// NoneProxyMode disables kube-proxy\n\tNoneProxyMode ProxyMode = \"none\"\n)\n\n// PatchJSON6902 represents an inline kustomize json 6902 patch\n// https://tools.ietf.org/html/rfc6902\ntype PatchJSON6902 struct {\n\t// these fields specify the patch target resource\n\tGroup   string\n\tVersion string\n\tKind    string\n\t// Patch should contain the contents of the json patch as a string\n\tPatch string\n}\n\n// Mount specifies a host volume to mount into a container.\n// This is a close copy of the upstream cri Mount type\n// see: k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2\n// It additionally serializes the \"propagation\" field with the string enum\n// names on disk as opposed to the int32 values, and the serialized field names\n// have been made closer to core/v1 VolumeMount field names\n// In yaml this looks like:\n//\n//\tcontainerPath: /foo\n//\thostPath: /bar\n//\treadOnly: true\n//\tselinuxRelabel: false\n//\tpropagation: None\n//\n// Propagation may be one of: None, HostToContainer, Bidirectional\ntype Mount struct {\n\t// Path of the mount within the container.\n\tContainerPath string\n\t// Path of the mount on the host. If the hostPath doesn't exist, then runtimes\n\t// should report error. If the hostpath is a symbolic link, runtimes should\n\t// follow the symlink and mount the real destination to container.\n\tHostPath string\n\t// If set, the mount is read-only.\n\tReadonly bool\n\t// If set, the mount needs SELinux relabeling.\n\tSelinuxRelabel bool\n\t// Requested propagation mode.\n\tPropagation MountPropagation\n}\n\n// PortMapping specifies a host port mapped into a container port.\n// In yaml this looks like:\n//\n//\tcontainerPort: 80\n//\thostPort: 8000\n//\tlistenAddress: 127.0.0.1\n//\tprotocol: TCP\ntype PortMapping struct {\n\t// Port within the container.\n\tContainerPort int32\n\t// Port on the host.\n\t//\n\t// If unset, a random port will be selected.\n\t//\n\t// NOTE: if you set the special value of `-1` then the node backend\n\t// (docker, podman...) will be left to pick the port instead.\n\t// This is potentially useful for remote hosts, BUT it means when the container\n\t// is restarted it will be randomized. Leave this unset to allow kind to pick it.\n\tHostPort int32\n\t// TODO: add protocol (tcp/udp) and port-ranges\n\tListenAddress string\n\t// Protocol (TCP/UDP/SCTP)\n\tProtocol PortMappingProtocol\n}\n\n// MountPropagation represents an \"enum\" for mount propagation options,\n// see also Mount.\ntype MountPropagation string\n\nconst (\n\t// MountPropagationNone specifies that no mount propagation\n\t// (\"private\" in Linux terminology).\n\tMountPropagationNone MountPropagation = \"None\"\n\t// MountPropagationHostToContainer specifies that mounts get propagated\n\t// from the host to the container (\"rslave\" in Linux).\n\tMountPropagationHostToContainer MountPropagation = \"HostToContainer\"\n\t// MountPropagationBidirectional specifies that mounts get propagated from\n\t// the host to the container and from the container to the host\n\t// (\"rshared\" in Linux).\n\tMountPropagationBidirectional MountPropagation = \"Bidirectional\"\n)\n\n// PortMappingProtocol represents an \"enum\" for port mapping protocol options,\n// see also PortMapping.\ntype PortMappingProtocol string\n\nconst (\n\t// PortMappingProtocolTCP specifies TCP protocol\n\tPortMappingProtocolTCP PortMappingProtocol = \"TCP\"\n\t// PortMappingProtocolUDP specifies UDP protocol\n\tPortMappingProtocolUDP PortMappingProtocol = \"UDP\"\n\t// PortMappingProtocolSCTP specifies SCTP protocol\n\tPortMappingProtocolSCTP PortMappingProtocol = \"SCTP\"\n)\n"
  },
  {
    "path": "pkg/internal/apis/config/validate.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\t\"sigs.k8s.io/kind/pkg/internal/sets\"\n)\n\n// similar to valid docker container names, but since we will prefix\n// and suffix this name, we can relax it a little\n// see NewContext() for usage\n// https://godoc.org/github.com/docker/docker/daemon/names#pkg-constants\nvar validNameRE = regexp.MustCompile(`^[a-z0-9.-]+$`)\n\n// Validate returns a ConfigErrors with an entry for each problem\n// with the config, or nil if there are none\nfunc (c *Cluster) Validate() error {\n\terrs := []error{}\n\n\t// validate the name\n\tif !validNameRE.MatchString(c.Name) {\n\t\terrs = append(errs, errors.Errorf(\"'%s' is not a valid cluster name, cluster names must match `%s`\",\n\t\t\tc.Name, validNameRE.String()))\n\t}\n\n\t// the api server port only needs checking if we aren't picking a random one\n\t// at runtime\n\tif c.Networking.APIServerPort != 0 {\n\t\t// validate api server listen port\n\t\tif err := validatePort(c.Networking.APIServerPort); err != nil {\n\t\t\terrs = append(errs, errors.Wrapf(err, \"invalid apiServerPort\"))\n\t\t}\n\t}\n\n\t// ipFamily should be ipv4, ipv6, or dual\n\tif c.Networking.IPFamily != IPv4Family && c.Networking.IPFamily != IPv6Family && c.Networking.IPFamily != DualStackFamily {\n\t\terrs = append(errs, errors.Errorf(\"invalid ipFamily: %s\", c.Networking.IPFamily))\n\t}\n\n\t// podSubnet should be a valid CIDR\n\tif err := validateSubnets(c.Networking.PodSubnet, c.Networking.IPFamily); err != nil {\n\t\terrs = append(errs, errors.Errorf(\"invalid pod subnet %v\", err))\n\t}\n\n\t// serviceSubnet should be a valid CIDR\n\tif err := validateSubnets(c.Networking.ServiceSubnet, c.Networking.IPFamily); err != nil {\n\t\terrs = append(errs, errors.Errorf(\"invalid service subnet %v\", err))\n\t}\n\n\t// KubeProxyMode should be iptables or ipvs\n\tif c.Networking.KubeProxyMode != IPTablesProxyMode && c.Networking.KubeProxyMode != IPVSProxyMode &&\n\t\tc.Networking.KubeProxyMode != NoneProxyMode && c.Networking.KubeProxyMode != NFTablesProxyMode {\n\t\terrs = append(errs, errors.Errorf(\"invalid kubeProxyMode: %s\", c.Networking.KubeProxyMode))\n\t}\n\n\t// validate nodes\n\tnumByRole := make(map[NodeRole]int32)\n\t// All nodes in the config should be valid\n\tfor i, n := range c.Nodes {\n\t\t// validate the node\n\t\tif err := n.Validate(); err != nil {\n\t\t\terrs = append(errs, errors.Errorf(\"invalid configuration for node %d: %v\", i, err))\n\t\t}\n\t\t// update role count\n\t\tif num, ok := numByRole[n.Role]; ok {\n\t\t\tnumByRole[n.Role] = 1 + num\n\t\t} else {\n\t\t\tnumByRole[n.Role] = 1\n\t\t}\n\t}\n\n\t// there must be at least one control plane node\n\tnumControlPlane, anyControlPlane := numByRole[ControlPlaneRole]\n\tif !anyControlPlane || numControlPlane < 1 {\n\t\terrs = append(errs, errors.Errorf(\"must have at least one %s node\", string(ControlPlaneRole)))\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.NewAggregate(errs)\n\t}\n\treturn nil\n}\n\n// Validate returns a ConfigErrors with an entry for each problem\n// with the Node, or nil if there are none\nfunc (n *Node) Validate() error {\n\terrs := []error{}\n\n\t// validate node role should be one of the expected values\n\tswitch n.Role {\n\tcase ControlPlaneRole,\n\t\tWorkerRole:\n\tdefault:\n\t\terrs = append(errs, errors.Errorf(\"%q is not a valid node role\", n.Role))\n\t}\n\n\t// image should be defined\n\tif n.Image == \"\" {\n\t\terrs = append(errs, errors.New(\"image is a required field\"))\n\t}\n\n\t// validate extra port forwards\n\tfor _, mapping := range n.ExtraPortMappings {\n\t\tif err := validatePort(mapping.HostPort); err != nil {\n\t\t\terrs = append(errs, errors.Wrapf(err, \"invalid hostPort\"))\n\t\t}\n\n\t\tif err := validatePort(mapping.ContainerPort); err != nil {\n\t\t\terrs = append(errs, errors.Wrapf(err, \"invalid containerPort\"))\n\t\t}\n\t}\n\n\tif err := validatePortMappings(n.ExtraPortMappings); err != nil {\n\t\terrs = append(errs, errors.Wrapf(err, \"invalid portMapping\"))\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.NewAggregate(errs)\n\t}\n\n\treturn nil\n}\n\nfunc validatePortMappings(portMappings []PortMapping) error {\n\terrMsg := \"port mapping with same listen address, port and protocol already configured\"\n\n\twildcardAddrIPv4 := net.ParseIP(\"0.0.0.0\")\n\twildcardAddrIPv6 := net.ParseIP(\"::\")\n\n\t// bindMap has the following key-value structure\n\t// PORT/PROTOCOL: [ IP ]\n\t// { 80/TCP: [ 127.0.0.1, 192.168.2.3 ], 80/UDP: [ 0.0.0.0 ] }\n\tbindMap := make(map[string]sets.String)\n\n\tformatPortProtocol := func(port int32, protocol PortMappingProtocol) string {\n\t\treturn fmt.Sprintf(\"%d/%s\", port, protocol)\n\t}\n\n\tfor _, portMapping := range portMappings {\n\t\tif portMapping.HostPort == -1 || portMapping.HostPort == 0 {\n\t\t\t// Port -1 and 0 cause a random port to be selected, thus duplicates are allowed\n\t\t\tcontinue\n\t\t}\n\n\t\taddr := net.ParseIP(portMapping.ListenAddress)\n\t\taddrString := addr.String()\n\n\t\tportProtocol := formatPortProtocol(portMapping.HostPort, portMapping.Protocol)\n\t\tpossibleErr := fmt.Errorf(\"%s: %s:%s\", errMsg, addrString, portProtocol)\n\n\t\t// in golang 0.0.0.0 and [::] are equivalent, convert [::] -> 0.0.0.0\n\t\t// https://github.com/golang/go/issues/48723\n\t\tif addr.Equal(wildcardAddrIPv6) {\n\t\t\taddr = wildcardAddrIPv4\n\t\t\taddrString = addr.String()\n\t\t}\n\n\t\tif _, ok := bindMap[portProtocol]; ok {\n\n\t\t\t// wildcard address case:\n\t\t\t// return error if there already exists any listen address for same port and protocol\n\t\t\tif addr.Equal(wildcardAddrIPv4) {\n\t\t\t\tif bindMap[portProtocol].Len() > 0 {\n\t\t\t\t\treturn possibleErr\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// direct duplicate & wild card present check:\n\t\t\t// return error if same combination of ip, port and protocol already exists in bindMap.\n\t\t\t// return error if wildcard address is already present for same port & protocol\n\t\t\tif bindMap[portProtocol].Has(addrString) || bindMap[portProtocol].Has(wildcardAddrIPv4.String()) {\n\t\t\t\treturn possibleErr\n\t\t\t}\n\t\t} else {\n\t\t\t// initialize the set\n\t\t\tbindMap[portProtocol] = sets.NewString()\n\t\t}\n\n\t\t// add the entry to bindMap\n\t\tbindMap[portProtocol].Insert(addrString)\n\t}\n\treturn nil\n}\n\nfunc validatePort(port int32) error {\n\t// NOTE: -1 is a special value for auto-selecting the port in the container\n\t// backend where possible as opposed to in kind itself.\n\tif port < -1 || port > 65535 {\n\t\treturn errors.Errorf(\"invalid port number: %d\", port)\n\t}\n\treturn nil\n}\n\nfunc validateSubnets(subnetStr string, ipFamily ClusterIPFamily) error {\n\tallErrs := []error{}\n\n\tcidrsString := strings.Split(subnetStr, \",\")\n\tsubnets := make([]*net.IPNet, 0, len(cidrsString))\n\tfor _, cidrString := range cidrsString {\n\t\t_, cidr, err := net.ParseCIDR(cidrString)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse cidr value:%q with error: %v\", cidrString, err)\n\t\t}\n\t\tsubnets = append(subnets, cidr)\n\t}\n\n\tdualstack := ipFamily == DualStackFamily\n\tswitch {\n\t// if no subnets are defined\n\tcase len(subnets) == 0:\n\t\tallErrs = append(allErrs, errors.New(\"no subnets defined\"))\n\t// if DualStack only 2 CIDRs allowed\n\tcase dualstack && len(subnets) > 2:\n\t\tallErrs = append(allErrs, errors.New(\"expected one (IPv4 or IPv6) CIDR or two CIDRs from each family for dual-stack networking\"))\n\t// if DualStack and there are 2 CIDRs validate if there is at least one of each IP family\n\tcase dualstack && len(subnets) == 2:\n\t\tareDualStackCIDRs, err := isDualStackCIDRs(subnets)\n\t\tif err != nil {\n\t\t\tallErrs = append(allErrs, err)\n\t\t} else if !areDualStackCIDRs {\n\t\t\tallErrs = append(allErrs, errors.New(\"expected one (IPv4 or IPv6) CIDR or two CIDRs from each family for dual-stack networking\"))\n\t\t}\n\t// if not DualStack only one CIDR allowed\n\tcase !dualstack && len(subnets) > 1:\n\t\tallErrs = append(allErrs, errors.New(\"only one CIDR allowed for single-stack networking\"))\n\tcase ipFamily == IPv4Family && subnets[0].IP.To4() == nil:\n\t\tallErrs = append(allErrs, errors.New(\"expected IPv4 CIDR for IPv4 family\"))\n\tcase ipFamily == IPv6Family && subnets[0].IP.To4() != nil:\n\t\tallErrs = append(allErrs, errors.New(\"expected IPv6 CIDR for IPv6 family\"))\n\t}\n\n\tif len(allErrs) > 0 {\n\t\treturn errors.NewAggregate(allErrs)\n\t}\n\treturn nil\n}\n\n// isDualStackCIDRs returns if\n// - all are valid cidrs\n// - at least one cidr from each family (v4 or v6)\nfunc isDualStackCIDRs(cidrs []*net.IPNet) (bool, error) {\n\tv4Found := false\n\tv6Found := false\n\tfor _, cidr := range cidrs {\n\t\tif cidr == nil {\n\t\t\treturn false, fmt.Errorf(\"cidr %v is invalid\", cidr)\n\t\t}\n\n\t\tif v4Found && v6Found {\n\t\t\tcontinue\n\t\t}\n\n\t\tif cidr.IP != nil && cidr.IP.To4() == nil {\n\t\t\tv6Found = true\n\t\t\tcontinue\n\t\t}\n\t\tv4Found = true\n\t}\n\n\treturn v4Found && v6Found, nil\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/validate_test.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\nfunc TestClusterValidate(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tName         string\n\t\tCluster      Cluster\n\t\tExpectErrors int\n\t}{\n\t\t{\n\t\t\tName: \"Defaulted\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tName: \"multiple valid nodes\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Nodes = append(c.Nodes, newDefaultedNode(WorkerRole), newDefaultedNode(WorkerRole))\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tName: \"default IPv6\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tc.Networking.IPFamily = IPv6Family\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tName: \"bogus podSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"aa\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"bogus serviceSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.ServiceSubnet = \"aa\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"bogus apiServerPort\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.APIServerPort = 9999999\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"bogus kubeProxyMode\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.KubeProxyMode = \"notiptables\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"bogus ipFamily\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.IPFamily = \"ds\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"bogus serviceSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.ServiceSubnet = \"aa\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid number of podSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24,2.2.2.0/24\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"valid dual stack podSubnet and serviceSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24,fd00:1::/25\"\n\t\t\t\tc.Networking.ServiceSubnet = \"192.168.0.2/24,fd00:1::/25\"\n\t\t\t\tc.Networking.IPFamily = DualStackFamily\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 0,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid dual stack podSubnet and multiple serviceSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24,fd00:1::/25\"\n\t\t\t\tc.Networking.ServiceSubnet = \"192.168.0.2/24,fd00:1::/25,10.0.0.0/16\"\n\t\t\t\tc.Networking.IPFamily = DualStackFamily\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"valid dual stack podSubnet and single stack serviceSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24,fd00:1::/25\"\n\t\t\t\tc.Networking.ServiceSubnet = \"192.168.0.2/24\"\n\t\t\t\tc.Networking.IPFamily = DualStackFamily\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 0,\n\t\t},\n\t\t{\n\t\t\tName: \"valid dual stack serviceSubnet and single stack podSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24\"\n\t\t\t\tc.Networking.ServiceSubnet = \"192.168.0.2/24,fd00:1::/25\"\n\t\t\t\tc.Networking.IPFamily = DualStackFamily\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 0,\n\t\t},\n\n\t\t{\n\t\t\tName: \"bad dual stack podSubnet and serviceSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24,2.2.2.0/25\"\n\t\t\t\tc.Networking.ServiceSubnet = \"192.168.0.2/24,2.2.2.0/25\"\n\t\t\t\tc.Networking.IPFamily = DualStackFamily\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 2,\n\t\t},\n\t\t{\n\t\t\tName: \"ipv6 family and ipv4 podSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"192.168.0.2/24\"\n\t\t\t\tc.Networking.ServiceSubnet = \"192.168.0.2/24\"\n\t\t\t\tc.Networking.IPFamily = IPv6Family\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 2,\n\t\t},\n\t\t{\n\t\t\tName: \"ipv4 family and ipv6 podSubnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"fd00:1::/25\"\n\t\t\t\tc.Networking.ServiceSubnet = \"fd00:1::/25\"\n\t\t\t\tc.Networking.IPFamily = IPv4Family\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 2,\n\t\t},\n\t\t{\n\t\t\t// This test validates the empty podsubnet check. It should never happen\n\t\t\t// in real world since defaulting is happening before the validation step.\n\t\t\tName: \"no pod subnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.PodSubnet = \"\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\t// This test validates the empty servicesubnet check. It should never happen\n\t\t\t// in real world since defaulting is happening before the validation step.\n\t\t\tName: \"no service subnet\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Networking.ServiceSubnet = \"\"\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"missing control-plane\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\tc.Nodes = []Node{}\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tName: \"bogus node\",\n\t\t\tCluster: func() Cluster {\n\t\t\t\tc := Cluster{}\n\t\t\t\tn, n2 := Node{}, Node{}\n\t\t\t\tn.Role = \"bogus\"\n\t\t\t\tc.Nodes = []Node{n, n2}\n\t\t\t\tSetDefaultsCluster(&c)\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc //capture loop variable\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\terr := tc.Cluster.Validate()\n\t\t\t// the error can be:\n\t\t\t// - nil, in which case we should expect no errors or fail\n\t\t\tif err == nil {\n\t\t\t\tif tc.ExpectErrors != 0 {\n\t\t\t\t\tt.Error(\"received no errors but expected errors for case\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// get the list of errors\n\t\t\terrs := errors.Errors(err)\n\t\t\tif errs == nil {\n\t\t\t\terrs = []error{err}\n\t\t\t}\n\t\t\t// we expect a certain number of errors\n\t\t\tif len(errs) != tc.ExpectErrors {\n\t\t\t\tt.Errorf(\"expected %d errors but got len(%v) = %d\", tc.ExpectErrors, errs, len(errs))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc newDefaultedNode(role NodeRole) Node {\n\tn := Node{\n\t\tRole:  role,\n\t\tImage: \"myImage:latest\",\n\t}\n\tSetDefaultsNode(&n)\n\treturn n\n}\n\nfunc TestNodeValidate(t *testing.T) {\n\tt.Parallel()\n\tcases := []struct {\n\t\tTestName     string\n\t\tNode         Node\n\t\tExpectErrors int\n\t}{\n\t\t{\n\t\t\tTestName:     \"Canonical node\",\n\t\t\tNode:         newDefaultedNode(ControlPlaneRole),\n\t\t\tExpectErrors: 0,\n\t\t},\n\t\t{\n\t\t\tTestName:     \"Canonical node 2\",\n\t\t\tNode:         newDefaultedNode(WorkerRole),\n\t\t\tExpectErrors: 0,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Empty image field\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.Image = \"\"\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Empty role field\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.Role = \"\"\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Unknown role field\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.Role = \"ssss\"\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Invalid ContainerPort\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.ExtraPortMappings = []PortMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tContainerPort: 999999999,\n\t\t\t\t\t\tHostPort:      8080,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Invalid HostPort\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.ExtraPortMappings = []PortMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tContainerPort: 8080,\n\t\t\t\t\t\tHostPort:      999999999,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 1,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Multiple random HostPort\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.ExtraPortMappings = []PortMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tContainerPort: 80,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tContainerPort: 443,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 0,\n\t\t},\n\t\t{\n\t\t\tTestName: \"Multiple random -1 HostPort\",\n\t\t\tNode: func() Node {\n\t\t\t\tcfg := newDefaultedNode(ControlPlaneRole)\n\t\t\t\tcfg.ExtraPortMappings = []PortMapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tContainerPort: 80,\n\t\t\t\t\t\tHostPort:      -1,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tContainerPort: 443,\n\t\t\t\t\t\tHostPort:      -1,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn cfg\n\t\t\t}(),\n\t\t\tExpectErrors: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc //capture loop variable\n\t\tt.Run(tc.TestName, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\terr := tc.Node.Validate()\n\t\t\t// the error can be:\n\t\t\t// - nil, in which case we should expect no errors or fail\n\t\t\tif err == nil {\n\t\t\t\tif tc.ExpectErrors != 0 {\n\t\t\t\t\tt.Error(\"received no errors but expected errors for case\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// get the list of errors\n\t\t\terrs := errors.Errors(err)\n\t\t\tif errs == nil {\n\t\t\t\terrs = []error{err}\n\t\t\t}\n\t\t\t// we expect a certain number of errors\n\t\t\tif len(errs) != tc.ExpectErrors {\n\t\t\t\tt.Errorf(\"expected %d errors but got len(%v) = %d\", tc.ExpectErrors, errs, len(errs))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPortValidate(t *testing.T) {\n\tcases := []struct {\n\t\tTestName    string\n\t\tPort        int32\n\t\tExpectError string\n\t}{\n\t\t{\n\t\t\tTestName:    \"-1 port\",\n\t\t\tPort:        -1,\n\t\t\tExpectError: \"\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"valid port\",\n\t\t\tPort:        10,\n\t\t\tExpectError: \"\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"negative port\",\n\t\t\tPort:        -2,\n\t\t\tExpectError: \"invalid port number: -2\",\n\t\t},\n\t\t{\n\t\t\tTestName:    \"extra port\",\n\t\t\tPort:        65536,\n\t\t\tExpectError: \"invalid port number: 65536\",\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc //capture loop variable\n\t\tt.Run(tc.TestName, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\terr := validatePort(tc.Port)\n\t\t\t// the error can be:\n\t\t\t// - nil, in which case we should expect no errors or fail\n\t\t\tif err == nil && len(tc.ExpectError) > 0 {\n\t\t\t\tt.Errorf(\"Test failed, unexpected error: %s\", tc.ExpectError)\n\t\t\t}\n\n\t\t\tif err != nil && err.Error() != tc.ExpectError {\n\t\t\t\tt.Errorf(\"Test failed, error: %s expected error: %s\", err, tc.ExpectError)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestValidatePortMappings(t *testing.T) {\n\tnewPortMapping := func(addr string, port int, protocol string) PortMapping {\n\t\treturn PortMapping{\n\t\t\tHostPort:      int32(port),\n\t\t\tListenAddress: addr,\n\t\t\tProtocol:      PortMappingProtocol(protocol),\n\t\t}\n\t}\n\terrMsg := \"port mapping with same listen address, port and protocol already configured\"\n\tcases := []struct {\n\t\ttestName     string\n\t\tportMappings []PortMapping\n\t\texpectErr    string\n\t}{\n\t\t{\n\t\t\ttestName: \"unique port mappings ipv4\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"UDP\"),\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"0.0.0.0\", 3000, \"UDP\"),\n\t\t\t\tnewPortMapping(\"0.0.0.0\", 5000, \"TCP\"),\n\t\t\t},\n\t\t\texpectErr: \"\",\n\t\t},\n\t\t{\n\t\t\ttestName: \"unique port mappings ipv6\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"::1\", 80, \"UDP\"),\n\t\t\t\tnewPortMapping(\"::1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"1e3d:6e85:424d:a011:a72e:9780:5f6f:a6fc\", 3000, \"UDP\"),\n\t\t\t\tnewPortMapping(\"6516:944d:e070:a1d1:2e91:8437:a6b3:edf9\", 5000, \"TCP\"),\n\t\t\t},\n\t\t\texpectErr: \"\",\n\t\t},\n\t\t{\n\t\t\ttestName: \"exact duplicate port mappings ipv4\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"UDP\"),\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"TCP\"),\n\t\t\t},\n\t\t\t// error expected: exact duplicate\n\t\t\texpectErr: fmt.Sprintf(\"%s: 127.0.0.1:80/TCP\", errMsg),\n\t\t},\n\n\t\t{\n\t\t\ttestName: \"exact duplicate port mappings ipv6\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"::1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"::1\", 80, \"UDP\"),\n\t\t\t\tnewPortMapping(\"::1\", 80, \"TCP\"),\n\t\t\t},\n\t\t\t// error expected: exact duplicate\n\t\t\texpectErr: fmt.Sprintf(\"%s: [::1]:80/TCP\", errMsg),\n\t\t},\n\t\t{\n\t\t\ttestName: \"wildcard ipv4 & ipv6\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"0.0.0.0\", 80, \"UDP\"),\n\t\t\t\tnewPortMapping(\"::1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"::\", 80, \"UDP\"),\n\t\t\t},\n\t\t\t// error expected: 0.0.0.0 & [::] are same in golang\n\t\t\t// https://github.com/golang/go/issues/48723\n\t\t\texpectErr: fmt.Sprintf(\"%s: [::]:80/UDP\", errMsg),\n\t\t},\n\t\t{\n\t\t\ttestName: \"subset already configured ipv4\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"0.0.0.0\", 80, \"TCP\"),\n\t\t\t},\n\t\t\t// error expected: subset of 0.0.0.0 -> 127.0.0.1 is already defined for same port and protocol\n\t\t\texpectErr: fmt.Sprintf(\"%s: 0.0.0.0:80/TCP\", errMsg),\n\t\t},\n\t\t{\n\t\t\ttestName: \"subset already configured ipv6\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"::1\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"::\", 80, \"TCP\"),\n\t\t\t},\n\t\t\t// error expected: subset of :: -> ::1 is already defined for same port and protocol\n\t\t\texpectErr: fmt.Sprintf(\"%s: [::]:80/TCP\", errMsg),\n\t\t},\n\t\t{\n\t\t\ttestName: \"port mapping already configured via wildcard ipv4\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"0.0.0.0\", 80, \"TCP\"),\n\t\t\t\tnewPortMapping(\"127.0.0.1\", 80, \"TCP\"),\n\t\t\t},\n\t\t\t// error expected: port mapping is already defined for wildcard interface - 0.0.0.0\n\t\t\texpectErr: fmt.Sprintf(\"%s: 127.0.0.1:80/TCP\", errMsg),\n\t\t},\n\t\t{\n\t\t\ttestName: \"port mapping already configured via wildcard ipv6\",\n\t\t\tportMappings: []PortMapping{\n\t\t\t\tnewPortMapping(\"::\", 80, \"SCTP\"),\n\t\t\t\tnewPortMapping(\"::1\", 80, \"SCTP\"),\n\t\t\t},\n\t\t\t// error expected: port mapping is already defined for wildcard interface - ::\n\t\t\texpectErr: fmt.Sprintf(\"%s: [::1]:80/SCTP\", errMsg),\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\ttc := tc //capture loop variable\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\terr := validatePortMappings(tc.portMappings)\n\t\t\tassert.ExpectError(t, len(tc.expectErr) > 0, err)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/apis/config/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage config\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Cluster) DeepCopyInto(out *Cluster) {\n\t*out = *in\n\tif in.Nodes != nil {\n\t\tin, out := &in.Nodes, &out.Nodes\n\t\t*out = make([]Node, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tin.Networking.DeepCopyInto(&out.Networking)\n\tif in.FeatureGates != nil {\n\t\tin, out := &in.FeatureGates, &out.FeatureGates\n\t\t*out = make(map[string]bool, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.RuntimeConfig != nil {\n\t\tin, out := &in.RuntimeConfig, &out.RuntimeConfig\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.KubeadmConfigPatches != nil {\n\t\tin, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.KubeadmConfigPatchesJSON6902 != nil {\n\t\tin, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902\n\t\t*out = make([]PatchJSON6902, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ContainerdConfigPatches != nil {\n\t\tin, out := &in.ContainerdConfigPatches, &out.ContainerdConfigPatches\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ContainerdConfigPatchesJSON6902 != nil {\n\t\tin, out := &in.ContainerdConfigPatchesJSON6902, &out.ContainerdConfigPatchesJSON6902\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster.\nfunc (in *Cluster) DeepCopy() *Cluster {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Cluster)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Mount) DeepCopyInto(out *Mount) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mount.\nfunc (in *Mount) DeepCopy() *Mount {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Mount)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Networking) DeepCopyInto(out *Networking) {\n\t*out = *in\n\tif in.DNSSearch != nil {\n\t\tin, out := &in.DNSSearch, &out.DNSSearch\n\t\t*out = new([]string)\n\t\tif **in != nil {\n\t\t\tin, out := *in, *out\n\t\t\t*out = make([]string, len(*in))\n\t\t\tcopy(*out, *in)\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Networking.\nfunc (in *Networking) DeepCopy() *Networking {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Networking)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Node) DeepCopyInto(out *Node) {\n\t*out = *in\n\tif in.Labels != nil {\n\t\tin, out := &in.Labels, &out.Labels\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.ExtraMounts != nil {\n\t\tin, out := &in.ExtraMounts, &out.ExtraMounts\n\t\t*out = make([]Mount, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ExtraPortMappings != nil {\n\t\tin, out := &in.ExtraPortMappings, &out.ExtraPortMappings\n\t\t*out = make([]PortMapping, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.KubeadmConfigPatches != nil {\n\t\tin, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.KubeadmConfigPatchesJSON6902 != nil {\n\t\tin, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902\n\t\t*out = make([]PatchJSON6902, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Node.\nfunc (in *Node) DeepCopy() *Node {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Node)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *PatchJSON6902) DeepCopyInto(out *PatchJSON6902) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchJSON6902.\nfunc (in *PatchJSON6902) DeepCopy() *PatchJSON6902 {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(PatchJSON6902)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *PortMapping) DeepCopyInto(out *PortMapping) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortMapping.\nfunc (in *PortMapping) DeepCopy() *PortMapping {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(PortMapping)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/internal/assert/assert.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package assert implements helper functions for test assertions.\npackage assert\n\nimport \"reflect\"\n\n// *testing.T methods used by assert\ntype testingDotT interface {\n\tErrorf(format string, args ...interface{})\n}\n\n// ExpectError will call t.Errorf if expectError != (err == nil)\n// t should be a *testing.T normally\nfunc ExpectError(t testingDotT, expectError bool, err error) {\n\tif err != nil && !expectError {\n\t\tt.Errorf(\"Did not expect error: %v\", err)\n\t}\n\tif err == nil && expectError {\n\t\tt.Errorf(\"Expected error but got none\")\n\t}\n}\n\n// BoolEqual will call t.Errorf if expected != result\n// t should be a *testing.T normally\nfunc BoolEqual(t testingDotT, expected, result bool) {\n\tif expected != result {\n\t\tt.Errorf(\"Result did not match!\")\n\t\tt.Errorf(\"Expected: %v\", expected)\n\t\tt.Errorf(\"But got: %v\", result)\n\t}\n}\n\n// StringEqual will call t.Errorf if expected != result\n// t should be a *testing.T normally\nfunc StringEqual(t testingDotT, expected, result string) {\n\tif expected != result {\n\t\tt.Errorf(\"Strings did not match!\")\n\t\tt.Errorf(\"Expected: %q\", expected)\n\t\tt.Errorf(\"But got: %q\", result)\n\t}\n}\n\n// DeepEqual will call t.Errorf if !reflect.DeepEqual(expected, result)\n// t should be a *testing.T normally\nfunc DeepEqual(t testingDotT, expected, result interface{}) {\n\tif !reflect.DeepEqual(expected, result) {\n\t\tt.Errorf(\"Result did not DeepEqual Expected!\")\n\t\tt.Errorf(\"Expected: %+v\", expected)\n\t\tt.Errorf(\"But got: %+v\", result)\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/assert/assert_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage assert\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\n// fakeT is a fake testing.T that tracks calls to Errorf\ntype fakeT int\n\nfunc (t *fakeT) Errorf(format string, args ...interface{}) {\n\t*t++\n}\n\nfunc TestExpectError(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"expect error, nil error\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tExpectError(&f, true, nil)\n\t\tif int(f) == 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf to be called but it was not\")\n\t\t}\n\t})\n\tt.Run(\"expect error, have error\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tExpectError(&f, true, fmt.Errorf(\"heh\"))\n\t\tif int(f) != 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf not to be called but it was\")\n\t\t}\n\t})\n\tt.Run(\"do not expect error, nil error\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tExpectError(&f, false, nil)\n\t\tif int(f) != 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf not to be called but it was\")\n\t\t}\n\t})\n\tt.Run(\"do not expect error, have error\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tExpectError(&f, false, fmt.Errorf(\"heh\"))\n\t\tif int(f) == 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf to be called but it was not\")\n\t\t}\n\t})\n}\n\nfunc TestBoolEqual(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"not equal\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tBoolEqual(&f, true, false)\n\t\tif int(f) == 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf to be called but it was not\")\n\t\t}\n\t})\n\tt.Run(\"equal\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tBoolEqual(&f, true, true)\n\t\tif int(f) != 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf not to be called but it was\")\n\t\t}\n\t})\n}\n\nfunc TestStringEqual(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"not equal\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tStringEqual(&f, \"a\", \"b\")\n\t\tif int(f) == 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf to be called but it was not\")\n\t\t}\n\t})\n\tt.Run(\"equal\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tStringEqual(&f, \"a\", \"a\")\n\t\tif int(f) != 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf not to be called but it was\")\n\t\t}\n\t})\n}\n\nfunc TestDeepEqual(t *testing.T) {\n\tt.Parallel()\n\tt.Run(\"not equal\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tDeepEqual(&f, \"a\", \"b\")\n\t\tif int(f) == 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf to be called but it was not\")\n\t\t}\n\t})\n\tt.Run(\"equal\", func(t *testing.T) {\n\t\tt.Parallel()\n\t\tvar f fakeT\n\t\tDeepEqual(&f, f, f)\n\t\tif int(f) != 0 {\n\t\t\tt.Fatalf(\"Expected t.Errorf not to be called but it was\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/internal/cli/logger.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cli contains various types, variables, and functions for\n// cli functionality.\npackage cli\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"sigs.k8s.io/kind/pkg/log\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/env\"\n)\n\n// Logger is the kind cli's log.Logger implementation\ntype Logger struct {\n\twriter     io.Writer\n\twriterMu   sync.Mutex\n\tverbosity  log.Level\n\tbufferPool *bufferPool\n\t// kind special additions\n\tisSmartWriter bool\n}\n\nvar _ log.Logger = &Logger{}\n\n// NewLogger returns a new Logger with the given verbosity\nfunc NewLogger(writer io.Writer, verbosity log.Level) *Logger {\n\tl := &Logger{\n\t\tverbosity:  verbosity,\n\t\tbufferPool: newBufferPool(),\n\t}\n\tl.SetWriter(writer)\n\treturn l\n}\n\n// SetWriter sets the output writer\nfunc (l *Logger) SetWriter(w io.Writer) {\n\tl.writerMu.Lock()\n\tdefer l.writerMu.Unlock()\n\tl.writer = w\n\t_, isSpinner := w.(*Spinner)\n\tl.isSmartWriter = isSpinner || env.IsSmartTerminal(w)\n}\n\n// ColorEnabled returns true if the caller is OK to write colored output\nfunc (l *Logger) ColorEnabled() bool {\n\tl.writerMu.Lock()\n\tdefer l.writerMu.Unlock()\n\treturn l.isSmartWriter\n}\n\nfunc (l *Logger) getVerbosity() log.Level {\n\treturn log.Level(atomic.LoadInt32((*int32)(&l.verbosity)))\n}\n\n// SetVerbosity sets the loggers verbosity\nfunc (l *Logger) SetVerbosity(verbosity log.Level) {\n\tatomic.StoreInt32((*int32)(&l.verbosity), int32(verbosity))\n}\n\n// synchronized write to the inner writer\nfunc (l *Logger) write(p []byte) (n int, err error) {\n\tl.writerMu.Lock()\n\tdefer l.writerMu.Unlock()\n\treturn l.writer.Write(p)\n}\n\n// writeBuffer writes buf with write, ensuring there is a trailing newline\nfunc (l *Logger) writeBuffer(buf *bytes.Buffer) {\n\t// ensure trailing newline\n\tif buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\\n' {\n\t\tbuf.WriteByte('\\n')\n\t}\n\t// TODO: should we handle this somehow??\n\t// Who logs for the logger? 🤔\n\t_, _ = l.write(buf.Bytes())\n}\n\n// print writes a simple string to the log writer\nfunc (l *Logger) print(message string) {\n\tbuf := bytes.NewBufferString(message)\n\tl.writeBuffer(buf)\n}\n\n// printf is roughly fmt.Fprintf against the log writer\nfunc (l *Logger) printf(format string, args ...interface{}) {\n\tbuf := l.bufferPool.Get()\n\tfmt.Fprintf(buf, format, args...)\n\tl.writeBuffer(buf)\n\tl.bufferPool.Put(buf)\n}\n\n// addDebugHeader inserts the debug line header to buf\nfunc addDebugHeader(buf *bytes.Buffer) {\n\t_, file, line, ok := runtime.Caller(3)\n\t// lifted from klog\n\tif !ok {\n\t\tfile = \"???\"\n\t\tline = 1\n\t} else {\n\t\tif slash := strings.LastIndex(file, \"/\"); slash >= 0 {\n\t\t\tpath := file\n\t\t\tfile = path[slash+1:]\n\t\t\tif dirsep := strings.LastIndex(path[:slash], \"/\"); dirsep >= 0 {\n\t\t\t\tfile = path[dirsep+1:]\n\t\t\t}\n\t\t}\n\t}\n\tbuf.Grow(len(file) + 11) // we know at least this many bytes are needed\n\tbuf.WriteString(\"DEBUG: \")\n\tbuf.WriteString(file)\n\tbuf.WriteByte(':')\n\tfmt.Fprintf(buf, \"%d\", line)\n\tbuf.WriteByte(']')\n\tbuf.WriteByte(' ')\n}\n\n// debug is like print but with a debug log header\nfunc (l *Logger) debug(message string) {\n\tbuf := l.bufferPool.Get()\n\taddDebugHeader(buf)\n\tbuf.WriteString(message)\n\tl.writeBuffer(buf)\n\tl.bufferPool.Put(buf)\n}\n\n// debugf is like printf but with a debug log header\nfunc (l *Logger) debugf(format string, args ...interface{}) {\n\tbuf := l.bufferPool.Get()\n\taddDebugHeader(buf)\n\tfmt.Fprintf(buf, format, args...)\n\tl.writeBuffer(buf)\n\tl.bufferPool.Put(buf)\n}\n\n// Warn is part of the log.Logger interface\nfunc (l *Logger) Warn(message string) {\n\tl.print(message)\n}\n\n// Warnf is part of the log.Logger interface\nfunc (l *Logger) Warnf(format string, args ...interface{}) {\n\tl.printf(format, args...)\n}\n\n// Error is part of the log.Logger interface\nfunc (l *Logger) Error(message string) {\n\tl.print(message)\n}\n\n// Errorf is part of the log.Logger interface\nfunc (l *Logger) Errorf(format string, args ...interface{}) {\n\tl.printf(format, args...)\n}\n\n// V is part of the log.Logger interface\nfunc (l *Logger) V(level log.Level) log.InfoLogger {\n\treturn infoLogger{\n\t\tlogger:  l,\n\t\tlevel:   level,\n\t\tenabled: level <= l.getVerbosity(),\n\t}\n}\n\n// infoLogger implements log.InfoLogger for Logger\ntype infoLogger struct {\n\tlogger  *Logger\n\tlevel   log.Level\n\tenabled bool\n}\n\n// Enabled is part of the log.InfoLogger interface\nfunc (i infoLogger) Enabled() bool {\n\treturn i.enabled\n}\n\n// Info is part of the log.InfoLogger interface\nfunc (i infoLogger) Info(message string) {\n\tif !i.enabled {\n\t\treturn\n\t}\n\t// for > 0, we are writing debug messages, include extra info\n\tif i.level > 0 {\n\t\ti.logger.debug(message)\n\t} else {\n\t\ti.logger.print(message)\n\t}\n}\n\n// Infof is part of the log.InfoLogger interface\nfunc (i infoLogger) Infof(format string, args ...interface{}) {\n\tif !i.enabled {\n\t\treturn\n\t}\n\t// for > 0, we are writing debug messages, include extra info\n\tif i.level > 0 {\n\t\ti.logger.debugf(format, args...)\n\t} else {\n\t\ti.logger.printf(format, args...)\n\t}\n}\n\n// bufferPool is a type safe sync.Pool of *byte.Buffer, guaranteed to be Reset\ntype bufferPool struct {\n\tsync.Pool\n}\n\n// newBufferPool returns a new bufferPool\nfunc newBufferPool() *bufferPool {\n\treturn &bufferPool{\n\t\tsync.Pool{\n\t\t\tNew: func() interface{} {\n\t\t\t\t// The Pool's New function should generally only return pointer\n\t\t\t\t// types, since a pointer can be put into the return interface\n\t\t\t\t// value without an allocation:\n\t\t\t\treturn new(bytes.Buffer)\n\t\t\t},\n\t\t},\n\t}\n}\n\n// Get obtains a buffer from the pool\nfunc (b *bufferPool) Get() *bytes.Buffer {\n\treturn b.Pool.Get().(*bytes.Buffer)\n}\n\n// Put returns a buffer to the pool, resetting it first\nfunc (b *bufferPool) Put(x *bytes.Buffer) {\n\t// only store small buffers to avoid pointless allocation\n\t// avoid keeping arbitrarily large buffers\n\tif x.Len() > 256 {\n\t\treturn\n\t}\n\tx.Reset()\n\tb.Pool.Put(x)\n}\n"
  },
  {
    "path": "pkg/internal/cli/override.go",
    "content": "package cli\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/pflag\"\n)\n\n// OverrideDefaultName conditionally allows overriding the default cluster name\n// by setting the KIND_CLUSTER_NAME environment variable\n// only if --name wasn't set explicitly\nfunc OverrideDefaultName(fs *pflag.FlagSet) {\n\tif !fs.Changed(\"name\") {\n\t\tif name := os.Getenv(\"KIND_CLUSTER_NAME\"); name != \"\" {\n\t\t\t_ = fs.Set(\"name\", name)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/cli/spinner.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n)\n\n// custom CLI loading spinner for kind\nvar spinnerFrames = []string{\n\t\"⠈⠁\",\n\t\"⠈⠑\",\n\t\"⠈⠱\",\n\t\"⠈⡱\",\n\t\"⢀⡱\",\n\t\"⢄⡱\",\n\t\"⢄⡱\",\n\t\"⢆⡱\",\n\t\"⢎⡱\",\n\t\"⢎⡰\",\n\t\"⢎⡠\",\n\t\"⢎⡀\",\n\t\"⢎⠁\",\n\t\"⠎⠁\",\n\t\"⠊⠁\",\n}\n\n// Spinner is a simple and efficient CLI loading spinner used by kind\n// It is simplistic and assumes that the line length will not change.\ntype Spinner struct {\n\tstop    chan struct{} // signals writer goroutine to stop from Stop()\n\tstopped chan struct{} // signals Stop() that the writer goroutine stopped\n\tmu      *sync.Mutex   // protects the mutable bits\n\t// below are protected by mu\n\trunning bool\n\twriter  io.Writer\n\tticker  *time.Ticker // signals that it is time to write a frame\n\tprefix  string\n\tsuffix  string\n\t// format string used to write a frame, depends on the host OS / terminal\n\tframeFormat string\n}\n\n// spinner implements writer\nvar _ io.Writer = &Spinner{}\n\n// NewSpinner initializes and returns a new Spinner that will write to w\n// NOTE: w should be os.Stderr or similar, and it should be a Terminal\nfunc NewSpinner(w io.Writer) *Spinner {\n\tframeFormat := \"\\x1b[?7l\\r%s%s%s\\x1b[?7h\"\n\t// toggling wrapping seems to behave poorly on windows\n\t// in general only the simplest escape codes behave well at the moment,\n\t// and only in newer shells\n\tif runtime.GOOS == \"windows\" {\n\t\tframeFormat = \"\\r%s%s%s\"\n\t}\n\treturn &Spinner{\n\t\tstop:        make(chan struct{}, 1),\n\t\tstopped:     make(chan struct{}),\n\t\tmu:          &sync.Mutex{},\n\t\twriter:      w,\n\t\tframeFormat: frameFormat,\n\t}\n}\n\n// SetPrefix sets the prefix to print before the spinner\nfunc (s *Spinner) SetPrefix(prefix string) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\ts.prefix = prefix\n}\n\n// SetSuffix sets the suffix to print after the spinner\nfunc (s *Spinner) SetSuffix(suffix string) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\ts.suffix = suffix\n}\n\n// Start starts the spinner running\nfunc (s *Spinner) Start() {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\t// don't start if we've already started\n\tif s.running {\n\t\treturn\n\t}\n\t// flag that we've started\n\ts.running = true\n\t// start / create a frame ticker\n\ts.ticker = time.NewTicker(time.Millisecond * 100)\n\t// spin in the background\n\tgo func() {\n\t\t// write frames forever (until signaled to stop)\n\t\tfor {\n\t\t\tfor _, frame := range spinnerFrames {\n\t\t\t\tselect {\n\t\t\t\t// prefer stopping, select this signal first\n\t\t\t\tcase <-s.stop:\n\t\t\t\t\tfunc() {\n\t\t\t\t\t\ts.mu.Lock()\n\t\t\t\t\t\tdefer s.mu.Unlock()\n\t\t\t\t\t\ts.ticker.Stop()         // free up the ticker\n\t\t\t\t\t\ts.running = false       // mark as stopped (it's fine to start now)\n\t\t\t\t\t\ts.stopped <- struct{}{} // tell Stop() that we're done\n\t\t\t\t\t}()\n\t\t\t\t\treturn // ... and stop\n\t\t\t\t// otherwise continue and write one frame\n\t\t\t\tcase <-s.ticker.C:\n\t\t\t\t\tfunc() {\n\t\t\t\t\t\ts.mu.Lock()\n\t\t\t\t\t\tdefer s.mu.Unlock()\n\t\t\t\t\t\tfmt.Fprintf(s.writer, s.frameFormat, s.prefix, frame, s.suffix)\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// Stop signals the spinner to stop\nfunc (s *Spinner) Stop() {\n\ts.mu.Lock()\n\tif !s.running {\n\t\ts.mu.Unlock()\n\t\treturn\n\t}\n\t// try to stop, do nothing if channel is full (IE already busy stopping)\n\ts.stop <- struct{}{}\n\ts.mu.Unlock()\n\t// wait for stop to be finished\n\t<-s.stopped\n}\n\n// Write implements io.Writer, interrupting the spinner and writing to\n// the inner writer\nfunc (s *Spinner) Write(p []byte) (n int, err error) {\n\t// lock first, so nothing else can start writing until we are done\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\t// it the spinner is not running, just write directly\n\tif !s.running {\n\t\treturn s.writer.Write(p)\n\t}\n\t// otherwise: we will rewrite the line first\n\tif _, err := s.writer.Write([]byte(\"\\r\")); err != nil {\n\t\treturn 0, err\n\t}\n\treturn s.writer.Write(p)\n}\n"
  },
  {
    "path": "pkg/internal/cli/status.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// Status is used to track ongoing status in a CLI, with a nice loading spinner\n// when attached to a terminal\ntype Status struct {\n\tspinner *Spinner\n\tstatus  string\n\tlogger  log.Logger\n\t// for controlling coloring etc\n\tsuccessFormat string\n\tfailureFormat string\n}\n\n// StatusForLogger returns a new status object for the logger l,\n// if l is the kind cli logger and the writer is a Spinner, that spinner\n// will be used for the status\nfunc StatusForLogger(l log.Logger) *Status {\n\ts := &Status{\n\t\tlogger:        l,\n\t\tsuccessFormat: \" ✓ %s\\n\",\n\t\tfailureFormat: \" ✗ %s\\n\",\n\t}\n\t// if we're using the CLI logger, check for if it has a spinner setup\n\t// and wire the status to that\n\tif v, ok := l.(*Logger); ok {\n\t\tif v2, ok := v.writer.(*Spinner); ok {\n\t\t\ts.spinner = v2\n\t\t\t// use colored success / failure messages\n\t\t\ts.successFormat = \" \\x1b[32m✓\\x1b[0m %s\\n\"\n\t\t\ts.failureFormat = \" \\x1b[31m✗\\x1b[0m %s\\n\"\n\t\t}\n\t}\n\treturn s\n}\n\n// Start starts a new phase of the status, if attached to a terminal\n// there will be a loading spinner with this status\nfunc (s *Status) Start(status string) {\n\ts.End(true)\n\t// set new status\n\ts.status = status\n\tif s.spinner != nil {\n\t\ts.spinner.SetSuffix(fmt.Sprintf(\" %s \", s.status))\n\t\ts.spinner.Start()\n\t} else {\n\t\ts.logger.V(0).Infof(\" • %s  ...\\n\", s.status)\n\t}\n}\n\n// End completes the current status, ending any previous spinning and\n// marking the status as success or failure\nfunc (s *Status) End(success bool) {\n\tif s.status == \"\" {\n\t\treturn\n\t}\n\n\tif s.spinner != nil {\n\t\ts.spinner.Stop()\n\t\tfmt.Fprint(s.spinner.writer, \"\\r\")\n\t}\n\tif success {\n\t\ts.logger.V(0).Infof(s.successFormat, s.status)\n\t} else {\n\t\ts.logger.V(0).Infof(s.failureFormat, s.status)\n\t}\n\n\ts.status = \"\"\n}\n"
  },
  {
    "path": "pkg/internal/env/term.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package env contains CLI helpers for discovering terminal environment\n// information.\npackage env\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"runtime\"\n\n\tisatty \"github.com/mattn/go-isatty\"\n)\n\n// a fake TTY type for testing that can only be implemented within this package\ntype isTestFakeTTY interface {\n\tisTestFakeTTY()\n}\n\n// IsTerminal returns true if the writer w is a terminal\nfunc IsTerminal(w io.Writer) bool {\n\t// check for internal fake type we can use for testing.\n\tif _, ok := (w).(isTestFakeTTY); ok {\n\t\treturn true\n\t}\n\t// check for real terminals\n\tif v, ok := (w).(*os.File); ok {\n\t\treturn isatty.IsTerminal(v.Fd())\n\t}\n\treturn false\n}\n\n// IsSmartTerminal returns true if the writer w is a terminal AND\n// we think that the terminal is smart enough to use VT escape codes etc.\nfunc IsSmartTerminal(w io.Writer) bool {\n\treturn isSmartTerminal(w, runtime.GOOS, os.LookupEnv)\n}\n\nfunc isSmartTerminal(w io.Writer, GOOS string, lookupEnv func(string) (string, bool)) bool {\n\t// Not smart if it's not a tty\n\tif !IsTerminal(w) {\n\t\treturn false\n\t}\n\n\t// getenv helper for when we only care about the value\n\tgetenv := func(e string) string {\n\t\tv, _ := lookupEnv(e)\n\t\treturn v\n\t}\n\n\t// Explicit request for no ANSI escape codes\n\t// https://no-color.org/\n\tif _, set := lookupEnv(\"NO_COLOR\"); set {\n\t\treturn false\n\t}\n\n\t// Explicitly dumb terminals are not smart\n\t// https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals\n\tterm := getenv(\"TERM\")\n\tif term == \"dumb\" {\n\t\treturn false\n\t}\n\t// st has some bug 🤷‍♂️\n\t// https://github.com/kubernetes-sigs/kind/issues/1892\n\tif term == \"st-256color\" {\n\t\treturn false\n\t}\n\n\t// On Windows WT_SESSION is set by the modern terminal component.\n\t// Older terminals have poor support for UTF-8, VT escape codes, etc.\n\tif GOOS == \"windows\" && getenv(\"WT_SESSION\") == \"\" {\n\t\treturn false\n\t}\n\n\t/* CI Systems with bad Fake TTYs */\n\t// Travis CI\n\t// https://github.com/kubernetes-sigs/kind/issues/1478\n\t// We can detect it with documented magical environment variables\n\t// https://docs.travis-ci.com/user/environment-variables/#default-environment-variables\n\tif getenv(\"HAS_JOSH_K_SEAL_OF_APPROVAL\") == \"true\" && getenv(\"TRAVIS\") == \"true\" {\n\t\treturn false\n\t}\n\n\t// OK, we'll assume it's smart now, given no evidence otherwise.\n\treturn true\n}\n\n// trivial fake TTY writer for testing\ntype testFakeTTY struct{}\n\nfunc (t *testFakeTTY) Write(p []byte) (int, error) {\n\treturn len(p), nil\n}\n\nfunc (t *testFakeTTY) isTestFakeTTY() {}\n"
  },
  {
    "path": "pkg/internal/env/term_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage env\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestIsTerminal(t *testing.T) {\n\t// test trivial nil case\n\tif IsTerminal(nil) {\n\t\tt.Fatalf(\"IsTerminal should be false for nil Writer\")\n\t}\n\t// test something that isn't even a file\n\tvar buff bytes.Buffer\n\tif IsTerminal(&buff) {\n\t\tt.Fatalf(\"IsTerminal should be false for bytes.Buffer\")\n\t}\n\t// test a file\n\tf, err := os.CreateTemp(\"\", \"kind-isterminal\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempfile %v\", err)\n\t}\n\tif IsTerminal(f) {\n\t\tt.Fatalf(\"IsTerminal should be false for nil Writer\")\n\t}\n\t// TODO: testing an actual PTY would be somewhat tricky to do cleanly\n\t// but we should maybe do this in the future.\n\t// At least we know this doesn't trigger on things that are obviously not\n\t// terminals\n\tif !IsTerminal(&testFakeTTY{}) {\n\t\tt.Fatalf(\"IsTerminal should be true for testFakeTTY\")\n\t}\n}\n\nfunc TestIsSmartTerminal(t *testing.T) {\n\tcases := []struct {\n\t\tName    string\n\t\tFakeEnv map[string]string\n\t\tGOOS    string\n\t\tWriter  io.Writer\n\t\tIsSmart bool\n\t}{\n\t\t{\n\t\t\tName:    \"tty, no env\",\n\t\t\tFakeEnv: map[string]string{},\n\t\t\tGOOS:    \"linux\",\n\t\t\tIsSmart: true,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t\t{\n\t\t\tName:    \"nil writer, no env\",\n\t\t\tFakeEnv: map[string]string{},\n\t\t\tGOOS:    \"linux\",\n\t\t\tIsSmart: false,\n\t\t},\n\t\t{\n\t\t\tName:    \"tty, windows, no env\",\n\t\t\tFakeEnv: map[string]string{},\n\t\t\tGOOS:    \"windows\",\n\t\t\tIsSmart: false,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t\t{\n\t\t\tName: \"tty, windows, modern terminal env\",\n\t\t\tFakeEnv: map[string]string{\n\t\t\t\t\"WT_SESSION\": \"baz\",\n\t\t\t},\n\t\t\tGOOS:    \"windows\",\n\t\t\tIsSmart: true,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t\t{\n\t\t\tName: \"tty, TERM=dumb\",\n\t\t\tFakeEnv: map[string]string{\n\t\t\t\t\"TERM\": \"dumb\",\n\t\t\t},\n\t\t\tGOOS:    \"linux\",\n\t\t\tIsSmart: false,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t\t{\n\t\t\tName: \"tty, NO_COLOR=\",\n\t\t\tFakeEnv: map[string]string{\n\t\t\t\t\"NO_COLOR\": \"\",\n\t\t\t},\n\t\t\tGOOS:    \"linux\",\n\t\t\tIsSmart: false,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t\t{\n\t\t\tName: \"tty, Travis CI\",\n\t\t\tFakeEnv: map[string]string{\n\t\t\t\t\"TRAVIS\":                      \"true\",\n\t\t\t\t\"HAS_JOSH_K_SEAL_OF_APPROVAL\": \"true\",\n\t\t\t},\n\t\t\tGOOS:    \"linux\",\n\t\t\tIsSmart: false,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t\t{\n\t\t\tName: \"tty, TERM=st-256color\",\n\t\t\tFakeEnv: map[string]string{\n\t\t\t\t\"TERM\": \"st-256color\",\n\t\t\t},\n\t\t\tGOOS:    \"linux\",\n\t\t\tIsSmart: false,\n\t\t\tWriter:  &testFakeTTY{},\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture tc\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tres := isSmartTerminal(tc.Writer, tc.GOOS, func(s string) (string, bool) {\n\t\t\t\tk, set := tc.FakeEnv[s]\n\t\t\t\treturn k, set\n\t\t\t})\n\t\t\tassert.BoolEqual(t, tc.IsSmart, res)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/integration/integration.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package integration implements integration testing helpers.\npackage integration\n\nimport \"testing\"\n\n// *testing.T methods used by assert\ntype testingDotT interface {\n\tSkip(args ...interface{})\n}\n\n// MaybeSkip skips if integration tests should be skipped\n// currently this is when testing.Short() is true\n// This should be called at the beginning of an integration test\nfunc MaybeSkip(t testingDotT) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping integration test due to -short\")\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/patch/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package patch contains helpers for applying patches\npackage patch\n"
  },
  {
    "path": "pkg/internal/patch/json6902patch.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\tjsonpatch \"github.com/evanphx/json-patch/v5\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\ntype json6902Patch struct {\n\traw       string          // raw original contents\n\tpatch     jsonpatch.Patch // processed JSON 6902 patch\n\tmatchInfo matchInfo       // used to match resources\n}\n\nfunc convertJSON6902Patches(patchesJSON6902 []config.PatchJSON6902) ([]json6902Patch, error) {\n\tpatches := []json6902Patch{}\n\tfor _, configPatch := range patchesJSON6902 {\n\t\tpatchJSON, err := yaml.YAMLToJSON([]byte(configPatch.Patch))\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tpatch, err := jsonpatch.DecodePatch(patchJSON)\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tpatches = append(patches, json6902Patch{\n\t\t\traw:       configPatch.Patch,\n\t\t\tpatch:     patch,\n\t\t\tmatchInfo: matchInfoForConfigJSON6902Patch(configPatch),\n\t\t})\n\t}\n\treturn patches, nil\n}\n"
  },
  {
    "path": "pkg/internal/patch/kubeyaml.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// KubeYAML takes a Kubernetes object YAML document stream to patch,\n// merge patches, and JSON 6902 patches.\n//\n// It returns a patched a YAML document stream.\n//\n// Matching is performed on Kubernetes style v1 TypeMeta fields\n// (kind and apiVersion), between the YAML documents and the patches.\n//\n// Patches match if their kind and apiVersion match a document, with the exception\n// that if the patch does not set apiVersion it will be ignored.\nfunc KubeYAML(toPatch string, patches []string, patches6902 []config.PatchJSON6902) (string, error) {\n\t// pre-process, including splitting up documents etc.\n\tresources, err := parseResources(toPatch)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse yaml to patch\")\n\t}\n\tmergePatches, err := parseMergePatches(patches)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse patches\")\n\t}\n\tjson6902patches, err := convertJSON6902Patches(patches6902)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse JSON 6902 patches\")\n\t}\n\t// apply patches and build result\n\tbuilder := &strings.Builder{}\n\tfor i, r := range resources {\n\t\t// apply merge patches\n\t\tfor _, p := range mergePatches {\n\t\t\tif _, err := r.applyMergePatch(p); err != nil {\n\t\t\t\treturn \"\", errors.Wrap(err, \"failed to apply patch\")\n\t\t\t}\n\t\t}\n\t\t// apply RFC 6902 JSON patches\n\t\tfor _, p := range json6902patches {\n\t\t\tif _, err := r.apply6902Patch(p); err != nil {\n\t\t\t\treturn \"\", errors.Wrap(err, \"failed to apply JSON 6902 patch\")\n\t\t\t}\n\t\t}\n\t\t// write out result\n\t\tif err := r.encodeTo(builder); err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"failed to write patched resource\")\n\t\t}\n\t\t// write document separator\n\t\tif i+1 < len(resources) {\n\t\t\tif _, err := builder.WriteString(\"---\\n\"); err != nil {\n\t\t\t\treturn \"\", errors.Wrap(err, \"failed to write document separator\")\n\t\t\t}\n\t\t}\n\t}\n\t// verify that all patches were used\n\treturn builder.String(), nil\n}\n"
  },
  {
    "path": "pkg/internal/patch/kubeyaml_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestKubeYAML(t *testing.T) {\n\tt.Parallel()\n\ttype testCase struct {\n\t\tName            string\n\t\tToPatch         string\n\t\tPatches         []string\n\t\tPatchesJSON6902 []config.PatchJSON6902\n\t\tExpectError     bool\n\t\tExpectOutput    string\n\t}\n\tcases := []testCase{\n\t\t{\n\t\t\tName:         \"kubeadm config no patches\",\n\t\t\tToPatch:      normalKubeadmConfig,\n\t\t\tExpectError:  false,\n\t\t\tExpectOutput: normalKubeadmConfigKustomized,\n\t\t},\n\t\t{\n\t\t\tName:        \"kubeadm config bogus patches\",\n\t\t\tToPatch:     normalKubeadmConfig,\n\t\t\tPatches:     []string{\"b o g u s\"},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName:         \"kubeadm config one merge-patch\",\n\t\t\tToPatch:      normalKubeadmConfig,\n\t\t\tPatches:      []string{trivialPatch},\n\t\t\tExpectError:  false,\n\t\t\tExpectOutput: normalKubeadmConfigTrivialPatched,\n\t\t},\n\t\t{\n\t\t\tName:            \"kubeadm config one merge-patch, one 6902 patch\",\n\t\t\tToPatch:         normalKubeadmConfig,\n\t\t\tPatches:         []string{trivialPatch},\n\t\t\tPatchesJSON6902: []config.PatchJSON6902{trivialPatch6902},\n\t\t\tExpectError:     false,\n\t\t\tExpectOutput:    normalKubeadmConfigTrivialPatchedAnd6902Patched,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture test case\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tout, err := KubeYAML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902)\n\t\t\tassert.ExpectError(t, tc.ExpectError, err)\n\t\t\tif err == nil {\n\t\t\t\tassert.StringEqual(t, tc.ExpectOutput, out)\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst normalKubeadmConfig = `# config generated by kind\napiVersion: kubeadm.k8s.io/v1beta2\nkind: ClusterConfiguration\nmetadata:\n  name: config\nkubernetesVersion: v1.15.3\nclusterName: \"kind\"\ncontrolPlaneEndpoint: \"192.168.9.3:6443\"\n# on docker for mac we have to expose the api server via port forward,\n# so we need to ensure the cert is valid for localhost so we can talk\n# to the cluster after rewriting the kubeconfig to point to localhost\napiServer:\n  certSANs: [localhost, \"127.0.0.1\"]\ncontrollerManager:\n  extraArgs:\n    enable-hostpath-provisioner: \"true\"\n    # configure ipv6 default addresses for IPv6 clusters\n    \nscheduler:\n  extraArgs:\n    # configure ipv6 default addresses for IPv6 clusters\n    \nnetworking:\n  podSubnet: \"10.244.0.0/16\"\n  serviceSubnet: \"10.96.0.0/12\"\n---\napiVersion: kubeadm.k8s.io/v1beta2\nkind: InitConfiguration\nmetadata:\n  name: config\n# we use a well know token for TLS bootstrap\nbootstrapTokens:\n- token: \"abcdef.0123456789abcdef\"\n# we use a well know port for making the API server discoverable inside docker network. \n# from the host machine such port will be accessible via a random local port instead.\nlocalAPIEndpoint:\n  advertiseAddress: \"192.168.9.6\"\n  bindPort: 6443\nnodeRegistration:\n  criSocket: \"/run/containerd/containerd.sock\"\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    node-ip: \"192.168.9.6\"\n---\n# no-op entry that exists solely so it can be patched\napiVersion: kubeadm.k8s.io/v1beta2\nkind: JoinConfiguration\nmetadata:\n  name: config\ncontrolPlane:\n  localAPIEndpoint:\n    advertiseAddress: \"192.168.9.6\"\n    bindPort: 6443\nnodeRegistration:\n  criSocket: \"/run/containerd/containerd.sock\"\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    node-ip: \"192.168.9.6\"\ndiscovery:\n  bootstrapToken:\n    apiServerEndpoint: \"192.168.9.3:6443\"\n    token: \"abcdef.0123456789abcdef\"\n    unsafeSkipCAVerification: true\n---\napiVersion: kubelet.config.k8s.io/v1beta1\nkind: KubeletConfiguration\nmetadata:\n  name: config\n# configure ipv6 addresses in IPv6 mode\n \n# disable disk resource management by default\n# kubelet will see the host disk that the inner container runtime\n# is ultimately backed by and attempt to recover disk space. we don't want that.\nimageGCHighThresholdPercent: 100\nevictionHard:\n  nodefs.available: \"0%\"\n  nodefs.inodesFree: \"0%\"\n  imagefs.available: \"0%\"\n---\n# no-op entry that exists solely so it can be patched\napiVersion: kubeproxy.config.k8s.io/v1alpha1\nkind: KubeProxyConfiguration\nmetadata:\n  name: config\n---`\n\nconst normalKubeadmConfigKustomized = `apiServer:\n  certSANs:\n  - localhost\n  - 127.0.0.1\napiVersion: kubeadm.k8s.io/v1beta2\nclusterName: kind\ncontrolPlaneEndpoint: 192.168.9.3:6443\ncontrollerManager:\n  extraArgs:\n    enable-hostpath-provisioner: \"true\"\nkind: ClusterConfiguration\nkubernetesVersion: v1.15.3\nmetadata:\n  name: config\nnetworking:\n  podSubnet: 10.244.0.0/16\n  serviceSubnet: 10.96.0.0/12\nscheduler:\n  extraArgs: null\n---\napiVersion: kubeadm.k8s.io/v1beta2\nbootstrapTokens:\n- token: abcdef.0123456789abcdef\nkind: InitConfiguration\nlocalAPIEndpoint:\n  advertiseAddress: 192.168.9.6\n  bindPort: 6443\nmetadata:\n  name: config\nnodeRegistration:\n  criSocket: /run/containerd/containerd.sock\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    node-ip: 192.168.9.6\n---\napiVersion: kubeadm.k8s.io/v1beta2\ncontrolPlane:\n  localAPIEndpoint:\n    advertiseAddress: 192.168.9.6\n    bindPort: 6443\ndiscovery:\n  bootstrapToken:\n    apiServerEndpoint: 192.168.9.3:6443\n    token: abcdef.0123456789abcdef\n    unsafeSkipCAVerification: true\nkind: JoinConfiguration\nmetadata:\n  name: config\nnodeRegistration:\n  criSocket: /run/containerd/containerd.sock\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    node-ip: 192.168.9.6\n---\napiVersion: kubelet.config.k8s.io/v1beta1\nevictionHard:\n  imagefs.available: 0%\n  nodefs.available: 0%\n  nodefs.inodesFree: 0%\nimageGCHighThresholdPercent: 100\nkind: KubeletConfiguration\nmetadata:\n  name: config\n---\napiVersion: kubeproxy.config.k8s.io/v1alpha1\nkind: KubeProxyConfiguration\nmetadata:\n  name: config\n`\n\nconst trivialPatch = `\nkind: ClusterConfiguration\napiVersion: kubeadm.k8s.io/v1beta2\n\nscheduler:\n  extraArgs:\n   some-extra-arg: the-arg\n----\nkind: InitConfiguration\nnodeRegistration:\n  kubeletExtraArgs:\n    \"v\": \"4\"\n    \"logging-format\": \"json\"\n`\n\nconst normalKubeadmConfigTrivialPatched = `apiServer:\n  certSANs:\n  - localhost\n  - 127.0.0.1\napiVersion: kubeadm.k8s.io/v1beta2\nclusterName: kind\ncontrolPlaneEndpoint: 192.168.9.3:6443\ncontrollerManager:\n  extraArgs:\n    enable-hostpath-provisioner: \"true\"\nkind: ClusterConfiguration\nkubernetesVersion: v1.15.3\nmetadata:\n  name: config\nnetworking:\n  podSubnet: 10.244.0.0/16\n  serviceSubnet: 10.96.0.0/12\nscheduler:\n  extraArgs:\n    some-extra-arg: the-arg\n---\napiVersion: kubeadm.k8s.io/v1beta2\nbootstrapTokens:\n- token: abcdef.0123456789abcdef\nkind: InitConfiguration\nlocalAPIEndpoint:\n  advertiseAddress: 192.168.9.6\n  bindPort: 6443\nmetadata:\n  name: config\nnodeRegistration:\n  criSocket: /run/containerd/containerd.sock\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    logging-format: json\n    node-ip: 192.168.9.6\n    v: \"4\"\n---\napiVersion: kubeadm.k8s.io/v1beta2\ncontrolPlane:\n  localAPIEndpoint:\n    advertiseAddress: 192.168.9.6\n    bindPort: 6443\ndiscovery:\n  bootstrapToken:\n    apiServerEndpoint: 192.168.9.3:6443\n    token: abcdef.0123456789abcdef\n    unsafeSkipCAVerification: true\nkind: JoinConfiguration\nmetadata:\n  name: config\nnodeRegistration:\n  criSocket: /run/containerd/containerd.sock\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    node-ip: 192.168.9.6\n---\napiVersion: kubelet.config.k8s.io/v1beta1\nevictionHard:\n  imagefs.available: 0%\n  nodefs.available: 0%\n  nodefs.inodesFree: 0%\nimageGCHighThresholdPercent: 100\nkind: KubeletConfiguration\nmetadata:\n  name: config\n---\napiVersion: kubeproxy.config.k8s.io/v1alpha1\nkind: KubeProxyConfiguration\nmetadata:\n  name: config\n`\n\nvar trivialPatch6902 = config.PatchJSON6902{\n\tGroup:   \"kubeadm.k8s.io\",\n\tVersion: \"v1beta2\",\n\tKind:    \"ClusterConfiguration\",\n\tPatch: `\n- op: add\n  path: /apiServer/certSANs/-\n  value: my-hostname`,\n}\n\nconst normalKubeadmConfigTrivialPatchedAnd6902Patched = `apiServer:\n  certSANs:\n  - localhost\n  - 127.0.0.1\n  - my-hostname\napiVersion: kubeadm.k8s.io/v1beta2\nclusterName: kind\ncontrolPlaneEndpoint: 192.168.9.3:6443\ncontrollerManager:\n  extraArgs:\n    enable-hostpath-provisioner: \"true\"\nkind: ClusterConfiguration\nkubernetesVersion: v1.15.3\nmetadata:\n  name: config\nnetworking:\n  podSubnet: 10.244.0.0/16\n  serviceSubnet: 10.96.0.0/12\nscheduler:\n  extraArgs:\n    some-extra-arg: the-arg\n---\napiVersion: kubeadm.k8s.io/v1beta2\nbootstrapTokens:\n- token: abcdef.0123456789abcdef\nkind: InitConfiguration\nlocalAPIEndpoint:\n  advertiseAddress: 192.168.9.6\n  bindPort: 6443\nmetadata:\n  name: config\nnodeRegistration:\n  criSocket: /run/containerd/containerd.sock\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    logging-format: json\n    node-ip: 192.168.9.6\n    v: \"4\"\n---\napiVersion: kubeadm.k8s.io/v1beta2\ncontrolPlane:\n  localAPIEndpoint:\n    advertiseAddress: 192.168.9.6\n    bindPort: 6443\ndiscovery:\n  bootstrapToken:\n    apiServerEndpoint: 192.168.9.3:6443\n    token: abcdef.0123456789abcdef\n    unsafeSkipCAVerification: true\nkind: JoinConfiguration\nmetadata:\n  name: config\nnodeRegistration:\n  criSocket: /run/containerd/containerd.sock\n  kubeletExtraArgs:\n    fail-swap-on: \"false\"\n    node-ip: 192.168.9.6\n---\napiVersion: kubelet.config.k8s.io/v1beta1\nevictionHard:\n  imagefs.available: 0%\n  nodefs.available: 0%\n  nodefs.inodesFree: 0%\nimageGCHighThresholdPercent: 100\nkind: KubeletConfiguration\nmetadata:\n  name: config\n---\napiVersion: kubeproxy.config.k8s.io/v1alpha1\nkind: KubeProxyConfiguration\nmetadata:\n  name: config\n`\n"
  },
  {
    "path": "pkg/internal/patch/matchinfo.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/apis/config\"\n)\n\n// we match resources and patches on their v1 TypeMeta\ntype matchInfo struct {\n\tKind       string `json:\"kind,omitempty\"`\n\tAPIVersion string `json:\"apiVersion,omitempty\"`\n}\n\nfunc parseYAMLMatchInfo(raw string) (matchInfo, error) {\n\tm := matchInfo{}\n\tif err := yaml.Unmarshal([]byte(raw), &m); err != nil {\n\t\treturn matchInfo{}, errors.Wrapf(err, \"failed to parse type meta for %q\", raw)\n\t}\n\treturn m, nil\n}\n\nfunc matchInfoForConfigJSON6902Patch(patch config.PatchJSON6902) matchInfo {\n\treturn matchInfo{\n\t\tKind:       patch.Kind,\n\t\tAPIVersion: groupVersionToAPIVersion(patch.Group, patch.Version),\n\t}\n}\n\nfunc groupVersionToAPIVersion(group, version string) string {\n\tif group == \"\" {\n\t\treturn version\n\t}\n\treturn group + \"/\" + version\n}\n"
  },
  {
    "path": "pkg/internal/patch/mergepatch.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\ntype mergePatch struct {\n\traw       string    // the original raw data\n\tjson      []byte    // the processed data (in JSON form)\n\tmatchInfo matchInfo // for matching resources\n}\n\nfunc parseMergePatches(rawPatches []string) ([]mergePatch, error) {\n\tpatches := []mergePatch{}\n\t// split document streams before trying to parse them\n\tsplitRawPatches := make([]string, 0, len(rawPatches))\n\tfor _, raw := range rawPatches {\n\t\tsplitRaw, err := splitYAMLDocuments(raw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsplitRawPatches = append(splitRawPatches, splitRaw...)\n\t}\n\tfor _, raw := range splitRawPatches {\n\t\tmatchInfo, err := parseYAMLMatchInfo(raw)\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tjson, err := yaml.YAMLToJSON([]byte(raw))\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tpatches = append(patches, mergePatch{\n\t\t\traw:       raw,\n\t\t\tjson:      json,\n\t\t\tmatchInfo: matchInfo,\n\t\t})\n\t}\n\treturn patches, nil\n}\n"
  },
  {
    "path": "pkg/internal/patch/resource.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\n\tjsonpatch \"github.com/evanphx/json-patch/v5\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\ntype resource struct {\n\traw       string    // the original raw data\n\tjson      []byte    // the processed data (in JSON form), may be mutated\n\tmatchInfo matchInfo // for matching patches\n}\n\nfunc (r *resource) apply6902Patch(patch json6902Patch) (matches bool, err error) {\n\tif !r.matches(patch.matchInfo) {\n\t\treturn false, nil\n\t}\n\tpatched, err := patch.patch.Apply(r.json)\n\tif err != nil {\n\t\treturn true, errors.WithStack(err)\n\t}\n\tr.json = patched\n\treturn true, nil\n}\n\nfunc (r *resource) applyMergePatch(patch mergePatch) (matches bool, err error) {\n\tif !r.matches(patch.matchInfo) {\n\t\treturn false, nil\n\t}\n\tpatched, err := jsonpatch.MergePatch(r.json, patch.json)\n\tif err != nil {\n\t\treturn true, errors.WithStack(err)\n\t}\n\tr.json = patched\n\treturn true, nil\n}\n\nfunc (r resource) matches(o matchInfo) bool {\n\tm := &r.matchInfo\n\t// we require kind to match, but if the patch does not specify\n\t// APIVersion we ignore it (eg to allow trivial patches across kubeadm versions)\n\treturn m.Kind == o.Kind && (o.APIVersion == \"\" || m.APIVersion == o.APIVersion)\n}\n\nfunc (r *resource) encodeTo(w io.Writer) error {\n\tencoded, err := yaml.JSONToYAML(r.json)\n\tif err != nil {\n\t\treturn errors.WithStack(err)\n\t}\n\tif _, err := w.Write(encoded); err != nil {\n\t\treturn errors.WithStack(err)\n\t}\n\treturn nil\n}\n\nfunc parseResources(yamlDocumentStream string) ([]resource, error) {\n\tresources := []resource{}\n\tdocuments, err := splitYAMLDocuments(yamlDocumentStream)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, raw := range documents {\n\t\tmatchInfo, err := parseYAMLMatchInfo(raw)\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tjson, err := yaml.YAMLToJSON([]byte(raw))\n\t\tif err != nil {\n\t\t\treturn nil, errors.WithStack(err)\n\t\t}\n\t\tresources = append(resources, resource{\n\t\t\traw:       raw,\n\t\t\tjson:      json,\n\t\t\tmatchInfo: matchInfo,\n\t\t})\n\t}\n\treturn resources, nil\n}\n\nfunc splitYAMLDocuments(yamlDocumentStream string) ([]string, error) {\n\tdocuments := []string{}\n\tscanner := bufio.NewScanner(strings.NewReader(yamlDocumentStream))\n\tscanner.Split(splitYAMLDocument)\n\tfor scanner.Scan() {\n\t\tdocuments = append(documents, scanner.Text())\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"error splitting documents\")\n\t}\n\treturn documents, nil\n}\n\nconst yamlSeparator = \"\\n---\"\n\n// splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents.\n// this is borrowed from k8s.io/apimachinery/pkg/util/yaml/decoder.go\nfunc splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) {\n\tif atEOF && len(data) == 0 {\n\t\treturn 0, nil, nil\n\t}\n\tsep := len([]byte(yamlSeparator))\n\tif i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 {\n\t\t// We have a potential document terminator\n\t\ti += sep\n\t\tafter := data[i:]\n\t\tif len(after) == 0 {\n\t\t\t// we can't read any more characters\n\t\t\tif atEOF {\n\t\t\t\treturn len(data), data[:len(data)-sep], nil\n\t\t\t}\n\t\t\treturn 0, nil, nil\n\t\t}\n\t\tif j := bytes.IndexByte(after, '\\n'); j >= 0 {\n\t\t\treturn i + j + 1, data[0 : i-sep], nil\n\t\t}\n\t\treturn 0, nil, nil\n\t}\n\t// If we're at EOF, we have a final, non-terminated line. Return it.\n\tif atEOF {\n\t\treturn len(data), data, nil\n\t}\n\t// Request more data.\n\treturn 0, nil, nil\n}\n"
  },
  {
    "path": "pkg/internal/patch/toml.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\n\tburntoml \"github.com/BurntSushi/toml\"\n\tjsonpatch \"github.com/evanphx/json-patch/v5\"\n\ttoml \"github.com/pelletier/go-toml\"\n\n\tyaml \"go.yaml.in/yaml/v3\"\n\n\t\"sigs.k8s.io/kind/pkg/errors\"\n)\n\n// ContainerdTOML patches toPatch with the patches (should be TOML merge patches) and patches6902 (should be JSON 6902 patches)\nfunc ContainerdTOML(toPatch string, patches []string, patches6902 []string) (string, error) {\n\t// convert to JSON for patching\n\tj, err := tomlToJSON([]byte(toPatch))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tversion, err := containerdConfigVersion(toPatch)\n\tif err != nil {\n\t\treturn \"\", errors.WithStack(err)\n\t}\n\tif version == 0 {\n\t\treturn \"\", errors.New(\"failed to detect containerd config version\")\n\t}\n\t// apply merge patches\n\tfor _, patch := range patches {\n\t\tpj, err := tomlToJSON([]byte(patch))\n\t\tif err != nil {\n\t\t\treturn \"\", errors.WithStack(err)\n\t\t}\n\t\tpatchVersion, err := containerdConfigVersion(patch)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.WithStack(err)\n\t\t}\n\t\t// skip if patch sets version and version does not match\n\t\tif patchVersion != 0 && patchVersion != version {\n\t\t\tcontinue\n\t\t}\n\t\tpatched, err := jsonpatch.MergePatch(j, pj)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.WithStack(err)\n\t\t}\n\t\tj = patched\n\t}\n\t// apply JSON 6902 patches\n\tfor _, patch6902 := range patches6902 {\n\t\tpatch, err := jsonpatch.DecodePatch([]byte(patch6902))\n\t\tif err != nil {\n\t\t\treturn \"\", errors.WithStack(err)\n\t\t}\n\t\tpatched, err := patch.Apply(j)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.WithStack(err)\n\t\t}\n\t\tj = patched\n\t}\n\t// convert result back to TOML\n\treturn jsonToTOMLString(j)\n}\n\nfunc containerdConfigVersion(configTOML string) (int, error) {\n\ttype version struct {\n\t\tVersion int `toml:\"version,omitempty\"`\n\t}\n\tv := version{}\n\tif err := toml.Unmarshal([]byte(configTOML), &v); err != nil {\n\t\treturn 0, errors.WithStack(err)\n\t}\n\treturn v.Version, nil\n}\n\n// tomlToJSON converts arbitrary TOML to JSON\nfunc tomlToJSON(t []byte) ([]byte, error) {\n\t// we use github.com.pelletier/go-toml here to unmarshal arbitrary TOML to JSON\n\ttree, err := toml.LoadBytes(t)\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\tb, err := json.Marshal(tree.ToMap())\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\treturn b, nil\n}\n\n// jsonToTOMLString converts arbitrary JSON to TOML\nfunc jsonToTOMLString(j []byte) (string, error) {\n\tvar unstruct interface{}\n\t// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the\n\t// Go JSON library doesn't try to pick the right number type (int, float,\n\t// etc.) when unmarshalling to interface{}, it just picks float64\n\t// universally. go-yaml does go through the effort of picking the right\n\t// number type, so we can preserve number type throughout this process.\n\tif err := yaml.Unmarshal(j, &unstruct); err != nil {\n\t\treturn \"\", errors.WithStack(err)\n\t}\n\t// we use github.com/BurntSushi/toml here because github.com.pelletier/go-toml\n\t// can only marshal structs AND BurntSushi/toml is what contained uses\n\t// and has more canonically formatted output (we initially plan to use\n\t// this package for patching containerd config)\n\tvar buff bytes.Buffer\n\tif err := burntoml.NewEncoder(&buff).Encode(unstruct); err != nil {\n\t\treturn \"\", errors.WithStack(err)\n\t}\n\treturn buff.String(), nil\n}\n"
  },
  {
    "path": "pkg/internal/patch/toml_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patch\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kind/pkg/internal/assert\"\n)\n\nfunc TestContainerdTOML(t *testing.T) {\n\tt.Parallel()\n\ttype testCase struct {\n\t\tName            string\n\t\tToPatch         string\n\t\tPatches         []string\n\t\tPatchesJSON6902 []string\n\t\tExpectError     bool\n\t\tExpectOutput    string\n\t}\n\tcases := []testCase{\n\t\t{\n\t\t\tName:         \"invalid TOML\",\n\t\t\tToPatch:      `🗿`,\n\t\t\tExpectError:  true,\n\t\t\tExpectOutput: \"\",\n\t\t},\n\t\t{\n\t\t\tName:         \"invalid containerd versioning\",\n\t\t\tToPatch:      `version = \"five\"`,\n\t\t\tExpectError:  true,\n\t\t\tExpectOutput: \"\",\n\t\t},\n\t\t{\n\t\t\tName: \"no patches\",\n\t\t\tToPatch: `version = 2\ndisabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tExpectError: false,\n\t\t\tExpectOutput: `disabled_plugins = [\"restart\"]\nversion = 2\n\n[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"Only matching patches\",\n\t\t\tToPatch: `version = 2\n\ndisabled_plugins = [\"restart\"]\n\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatches:     []string{\"version = 3\\ndisabled_plugins=[\\\"bar\\\"]\", \"version = 2\\n disabled_plugins=[\\\"baz\\\"]\"},\n\t\t\tExpectError: false,\n\t\t\tExpectOutput: `disabled_plugins = [\"baz\"]\nversion = 2\n\n[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid patch TOML\",\n\t\t\tToPatch: `version = 2\ndisabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatches:     []string{\"🏰\"},\n\t\t\tExpectError: true,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid 6902 patch JSON\",\n\t\t\tToPatch: `disabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatchesJSON6902: []string{\"🏰\"},\n\t\t\tExpectError:     true,\n\t\t},\n\t\t{\n\t\t\tName: \"trivial patch\",\n\t\t\tToPatch: `version = 2\ndisabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatches:     []string{`disabled_plugins=[]`},\n\t\t\tExpectError: false,\n\t\t\tExpectOutput: `disabled_plugins = []\nversion = 2\n\n[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"trivial 6902 patch\",\n\t\t\tToPatch: `version = 2\ndisabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatchesJSON6902: []string{`[{\"op\": \"remove\", \"path\": \"/disabled_plugins\"}]`},\n\t\t\tExpectError:     false,\n\t\t\tExpectOutput: `version = 2\n\n[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"trivial patch and trivial 6902 patch\",\n\t\t\tToPatch: `version = 2\ndisabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatches:         []string{`disabled_plugins=[\"foo\"]`},\n\t\t\tPatchesJSON6902: []string{`[{\"op\": \"remove\", \"path\": \"/disabled_plugins\"}]`},\n\t\t\tExpectError:     false,\n\t\t\tExpectOutput: `version = 2\n\n[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid path 6902 patch\",\n\t\t\tToPatch: `disabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatchesJSON6902: []string{`[{\"op\": \"remove\", \"path\": \"/fooooooo\"}]`},\n\t\t\tExpectError:     true,\n\t\t\tExpectOutput: `[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"patch registry\",\n\t\t\tToPatch: `version = 2\ndisabled_plugins = [\"restart\"]\n[plugins.linux]\n  shim_debug = true\n[plugins.cri.containerd.runtimes.runsc]\n  runtime_type = \"io.containerd.runsc.v1\"`,\n\t\t\tPatches: []string{`[plugins.cri.registry.mirrors]\n  [plugins.cri.registry.mirrors.\"registry:5000\"]\n    endpoint = [\"http://registry:5000\"]`},\n\t\t\tExpectError: false,\n\t\t\tExpectOutput: `disabled_plugins = [\"restart\"]\nversion = 2\n\n[plugins]\n  [plugins.cri]\n    [plugins.cri.containerd]\n      [plugins.cri.containerd.runtimes]\n        [plugins.cri.containerd.runtimes.runsc]\n          runtime_type = \"io.containerd.runsc.v1\"\n    [plugins.cri.registry]\n      [plugins.cri.registry.mirrors]\n        [plugins.cri.registry.mirrors.\"registry:5000\"]\n          endpoint = [\"http://registry:5000\"]\n  [plugins.linux]\n    shim_debug = true\n`,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\ttc := tc // capture test case\n\t\tt.Run(tc.Name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tout, err := ContainerdTOML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902)\n\t\t\tassert.ExpectError(t, tc.ExpectError, err)\n\t\t\tif err == nil {\n\t\t\t\tassert.StringEqual(t, tc.ExpectOutput, out)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/runtime/runtime.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package runtime contains functions for getting runtime information.\npackage runtime\n\nimport (\n\t\"os\"\n\n\t\"sigs.k8s.io/kind/pkg/cluster\"\n\t\"sigs.k8s.io/kind/pkg/log\"\n)\n\n// GetDefault selected the default runtime from the environment override\nfunc GetDefault(logger log.Logger) cluster.ProviderOption {\n\tswitch p := os.Getenv(\"KIND_EXPERIMENTAL_PROVIDER\"); p {\n\tcase \"\":\n\t\treturn nil\n\tcase \"podman\":\n\t\tlogger.Warn(\"using podman due to KIND_EXPERIMENTAL_PROVIDER\")\n\t\treturn cluster.ProviderWithPodman()\n\tcase \"docker\":\n\t\tlogger.Warn(\"using docker due to KIND_EXPERIMENTAL_PROVIDER\")\n\t\treturn cluster.ProviderWithDocker()\n\tcase \"nerdctl\", \"finch\", \"nerdctl.lima\":\n\t\tlogger.Warnf(\"using %s due to KIND_EXPERIMENTAL_PROVIDER\", p)\n\t\treturn cluster.ProviderWithNerdctl(p)\n\tdefault:\n\t\tlogger.Warnf(\"ignoring unknown value %q for KIND_EXPERIMENTAL_PROVIDER\", p)\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/sets/doc.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package sets implements set types.\n//\n// This is forked from k8s.io/apimachinery/pkg/util/sets (under the same project\n// and license), because k8s.io/apimachinery is a relatively heavy dependency\n// and we only need some trivial utilities. Avoiding importing k8s.io/apimachinery\n// makes kind easier to embed in other projects for testing etc.\n//\n// The set implementation is relatively small and very stable.\npackage sets\n"
  },
  {
    "path": "pkg/internal/sets/empty.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by set-gen. DO NOT EDIT.\n\npackage sets\n\n// Empty is public since it is used by some internal API objects for conversions between external\n// string arrays and internal sets, and conversion logic requires public types today.\ntype Empty struct{}\n"
  },
  {
    "path": "pkg/internal/sets/string.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by set-gen. DO NOT EDIT.\n\npackage sets\n\nimport (\n\t\"reflect\"\n\t\"sort\"\n)\n\n// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.\ntype String map[string]Empty\n\n// NewString creates a String from a list of values.\nfunc NewString(items ...string) String {\n\tss := String{}\n\tss.Insert(items...)\n\treturn ss\n}\n\n// StringKeySet creates a String from a keys of a map[string](? extends interface{}).\n// If the value passed in is not actually a map, this will panic.\nfunc StringKeySet(theMap interface{}) String {\n\tv := reflect.ValueOf(theMap)\n\tret := String{}\n\n\tfor _, keyValue := range v.MapKeys() {\n\t\tret.Insert(keyValue.Interface().(string))\n\t}\n\treturn ret\n}\n\n// Insert adds items to the set.\nfunc (s String) Insert(items ...string) String {\n\tfor _, item := range items {\n\t\ts[item] = Empty{}\n\t}\n\treturn s\n}\n\n// Delete removes all items from the set.\nfunc (s String) Delete(items ...string) String {\n\tfor _, item := range items {\n\t\tdelete(s, item)\n\t}\n\treturn s\n}\n\n// Has returns true if and only if item is contained in the set.\nfunc (s String) Has(item string) bool {\n\t_, contained := s[item]\n\treturn contained\n}\n\n// HasAll returns true if and only if all items are contained in the set.\nfunc (s String) HasAll(items ...string) bool {\n\tfor _, item := range items {\n\t\tif !s.Has(item) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// HasAny returns true if any items are contained in the set.\nfunc (s String) HasAny(items ...string) bool {\n\tfor _, item := range items {\n\t\tif s.Has(item) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Difference returns a set of objects that are not in s2\n// For example:\n// s1 = {a1, a2, a3}\n// s2 = {a1, a2, a4, a5}\n// s1.Difference(s2) = {a3}\n// s2.Difference(s1) = {a4, a5}\nfunc (s String) Difference(s2 String) String {\n\tresult := NewString()\n\tfor key := range s {\n\t\tif !s2.Has(key) {\n\t\t\tresult.Insert(key)\n\t\t}\n\t}\n\treturn result\n}\n\n// Union returns a new set which includes items in either s1 or s2.\n// For example:\n// s1 = {a1, a2}\n// s2 = {a3, a4}\n// s1.Union(s2) = {a1, a2, a3, a4}\n// s2.Union(s1) = {a1, a2, a3, a4}\nfunc (s1 String) Union(s2 String) String {\n\tresult := NewString()\n\tfor key := range s1 {\n\t\tresult.Insert(key)\n\t}\n\tfor key := range s2 {\n\t\tresult.Insert(key)\n\t}\n\treturn result\n}\n\n// Intersection returns a new set which includes the item in BOTH s1 and s2\n// For example:\n// s1 = {a1, a2}\n// s2 = {a2, a3}\n// s1.Intersection(s2) = {a2}\nfunc (s1 String) Intersection(s2 String) String {\n\tvar walk, other String\n\tresult := NewString()\n\tif s1.Len() < s2.Len() {\n\t\twalk = s1\n\t\tother = s2\n\t} else {\n\t\twalk = s2\n\t\tother = s1\n\t}\n\tfor key := range walk {\n\t\tif other.Has(key) {\n\t\t\tresult.Insert(key)\n\t\t}\n\t}\n\treturn result\n}\n\n// IsSuperset returns true if and only if s1 is a superset of s2.\nfunc (s1 String) IsSuperset(s2 String) bool {\n\tfor item := range s2 {\n\t\tif !s1.Has(item) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Equal returns true if and only if s1 is equal (as a set) to s2.\n// Two sets are equal if their membership is identical.\n// (In practice, this means same elements, order doesn't matter)\nfunc (s1 String) Equal(s2 String) bool {\n\treturn len(s1) == len(s2) && s1.IsSuperset(s2)\n}\n\ntype sortableSliceOfString []string\n\nfunc (s sortableSliceOfString) Len() int           { return len(s) }\nfunc (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) }\nfunc (s sortableSliceOfString) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }\n\n// List returns the contents as a sorted string slice.\nfunc (s String) List() []string {\n\tres := make(sortableSliceOfString, 0, len(s))\n\tfor key := range s {\n\t\tres = append(res, key)\n\t}\n\tsort.Sort(res)\n\treturn []string(res)\n}\n\n// UnsortedList returns the slice with contents in random order.\nfunc (s String) UnsortedList() []string {\n\tres := make([]string, 0, len(s))\n\tfor key := range s {\n\t\tres = append(res, key)\n\t}\n\treturn res\n}\n\n// Returns a single element from the set.\nfunc (s String) PopAny() (string, bool) {\n\tfor key := range s {\n\t\ts.Delete(key)\n\t\treturn key, true\n\t}\n\tvar zeroValue string\n\treturn zeroValue, false\n}\n\n// Len returns the size of the set.\nfunc (s String) Len() int {\n\treturn len(s)\n}\n\nfunc lessString(lhs, rhs string) bool {\n\treturn lhs < rhs\n}\n"
  },
  {
    "path": "pkg/internal/version/doc.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package version provides utilities for version number comparisons\n//\n// This is forked from k8s.io/apimachinery/pkg/util/version to make\n// kind easier to import (k8s.io/apimachinery/pkg/util/version is a stable,\n// mature package with no externaldependencies within a large, heavy module)\npackage version\n"
  },
  {
    "path": "pkg/internal/version/version.go",
    "content": "/*\nCopyright 2016 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Version is an opaque representation of a version number\ntype Version struct {\n\tcomponents    []uint\n\tsemver        bool\n\tpreRelease    string\n\tbuildMetadata string\n}\n\nvar (\n\t// versionMatchRE splits a version string into numeric and \"extra\" parts\n\tversionMatchRE = regexp.MustCompile(`^\\s*v?([0-9]+(?:\\.[0-9]+)*)(.*)*$`)\n\t// extraMatchRE splits the \"extra\" part of versionMatchRE into semver pre-release and build metadata; it does not validate the \"no leading zeroes\" constraint for pre-release\n\textraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?\\s*$`)\n)\n\nfunc parse(str string, semver bool) (*Version, error) {\n\tparts := versionMatchRE.FindStringSubmatch(str)\n\tif parts == nil {\n\t\treturn nil, fmt.Errorf(\"could not parse %q as version\", str)\n\t}\n\tnumbers, extra := parts[1], parts[2]\n\n\tcomponents := strings.Split(numbers, \".\")\n\tif (semver && len(components) != 3) || (!semver && len(components) < 2) {\n\t\treturn nil, fmt.Errorf(\"illegal version string %q\", str)\n\t}\n\n\tv := &Version{\n\t\tcomponents: make([]uint, len(components)),\n\t\tsemver:     semver,\n\t}\n\tfor i, comp := range components {\n\t\tif (i == 0 || semver) && strings.HasPrefix(comp, \"0\") && comp != \"0\" {\n\t\t\treturn nil, fmt.Errorf(\"illegal zero-prefixed version component %q in %q\", comp, str)\n\t\t}\n\t\tnum, err := strconv.ParseUint(comp, 10, 0)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"illegal non-numeric version component %q in %q: %v\", comp, str, err)\n\t\t}\n\t\tv.components[i] = uint(num)\n\t}\n\n\tif semver && extra != \"\" {\n\t\textraParts := extraMatchRE.FindStringSubmatch(extra)\n\t\tif extraParts == nil {\n\t\t\treturn nil, fmt.Errorf(\"could not parse pre-release/metadata (%s) in version %q\", extra, str)\n\t\t}\n\t\tv.preRelease, v.buildMetadata = extraParts[1], extraParts[2]\n\n\t\tfor _, comp := range strings.Split(v.preRelease, \".\") {\n\t\t\tif _, err := strconv.ParseUint(comp, 10, 0); err == nil {\n\t\t\t\tif strings.HasPrefix(comp, \"0\") && comp != \"0\" {\n\t\t\t\t\treturn nil, fmt.Errorf(\"illegal zero-prefixed version component %q in %q\", comp, str)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn v, nil\n}\n\n// ParseGeneric parses a \"generic\" version string. The version string must consist of two\n// or more dot-separated numeric fields (the first of which can't have leading zeroes),\n// followed by arbitrary uninterpreted data (which need not be separated from the final\n// numeric field by punctuation). For convenience, leading and trailing whitespace is\n// ignored, and the version can be preceded by the letter \"v\". See also ParseSemantic.\nfunc ParseGeneric(str string) (*Version, error) {\n\treturn parse(str, false)\n}\n\n// MustParseGeneric is like ParseGeneric except that it panics on error\nfunc MustParseGeneric(str string) *Version {\n\tv, err := ParseGeneric(str)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// ParseSemantic parses a version string that exactly obeys the syntax and semantics of\n// the \"Semantic Versioning\" specification (http://semver.org/) (although it ignores\n// leading and trailing whitespace, and allows the version to be preceded by \"v\"). For\n// version strings that are not guaranteed to obey the Semantic Versioning syntax, use\n// ParseGeneric.\nfunc ParseSemantic(str string) (*Version, error) {\n\treturn parse(str, true)\n}\n\n// MustParseSemantic is like ParseSemantic except that it panics on error\nfunc MustParseSemantic(str string) *Version {\n\tv, err := ParseSemantic(str)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Major returns the major release number\nfunc (v *Version) Major() uint {\n\treturn v.components[0]\n}\n\n// Minor returns the minor release number\nfunc (v *Version) Minor() uint {\n\treturn v.components[1]\n}\n\n// Patch returns the patch release number if v is a Semantic Version, or 0\nfunc (v *Version) Patch() uint {\n\tif len(v.components) < 3 {\n\t\treturn 0\n\t}\n\treturn v.components[2]\n}\n\n// BuildMetadata returns the build metadata, if v is a Semantic Version, or \"\"\nfunc (v *Version) BuildMetadata() string {\n\treturn v.buildMetadata\n}\n\n// PreRelease returns the prerelease metadata, if v is a Semantic Version, or \"\"\nfunc (v *Version) PreRelease() string {\n\treturn v.preRelease\n}\n\n// Components returns the version number components\nfunc (v *Version) Components() []uint {\n\treturn v.components\n}\n\n// WithMajor returns copy of the version object with requested major number\nfunc (v *Version) WithMajor(major uint) *Version {\n\tresult := *v\n\tresult.components = []uint{major, v.Minor(), v.Patch()}\n\treturn &result\n}\n\n// WithMinor returns copy of the version object with requested minor number\nfunc (v *Version) WithMinor(minor uint) *Version {\n\tresult := *v\n\tresult.components = []uint{v.Major(), minor, v.Patch()}\n\treturn &result\n}\n\n// WithPatch returns copy of the version object with requested patch number\nfunc (v *Version) WithPatch(patch uint) *Version {\n\tresult := *v\n\tresult.components = []uint{v.Major(), v.Minor(), patch}\n\treturn &result\n}\n\n// WithPreRelease returns copy of the version object with requested prerelease\nfunc (v *Version) WithPreRelease(preRelease string) *Version {\n\tresult := *v\n\tresult.components = []uint{v.Major(), v.Minor(), v.Patch()}\n\tresult.preRelease = preRelease\n\treturn &result\n}\n\n// WithBuildMetadata returns copy of the version object with requested buildMetadata\nfunc (v *Version) WithBuildMetadata(buildMetadata string) *Version {\n\tresult := *v\n\tresult.components = []uint{v.Major(), v.Minor(), v.Patch()}\n\tresult.buildMetadata = buildMetadata\n\treturn &result\n}\n\n// String converts a Version back to a string; note that for versions parsed with\n// ParseGeneric, this will not include the trailing uninterpreted portion of the version\n// number.\nfunc (v *Version) String() string {\n\tif v == nil {\n\t\treturn \"<nil>\"\n\t}\n\tvar buffer bytes.Buffer\n\n\tfor i, comp := range v.components {\n\t\tif i > 0 {\n\t\t\tbuffer.WriteString(\".\")\n\t\t}\n\t\tfmt.Fprintf(&buffer, \"%d\", comp)\n\t}\n\tif v.preRelease != \"\" {\n\t\tbuffer.WriteString(\"-\")\n\t\tbuffer.WriteString(v.preRelease)\n\t}\n\tif v.buildMetadata != \"\" {\n\t\tbuffer.WriteString(\"+\")\n\t\tbuffer.WriteString(v.buildMetadata)\n\t}\n\n\treturn buffer.String()\n}\n\n// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0\n// if they are equal\nfunc (v *Version) compareInternal(other *Version) int {\n\n\tvLen := len(v.components)\n\toLen := len(other.components)\n\tfor i := 0; i < vLen && i < oLen; i++ {\n\t\tswitch {\n\t\tcase other.components[i] < v.components[i]:\n\t\t\treturn 1\n\t\tcase other.components[i] > v.components[i]:\n\t\t\treturn -1\n\t\t}\n\t}\n\n\t// If components are common but one has more items and they are not zeros, it is bigger\n\tswitch {\n\tcase oLen < vLen && !onlyZeros(v.components[oLen:]):\n\t\treturn 1\n\tcase oLen > vLen && !onlyZeros(other.components[vLen:]):\n\t\treturn -1\n\t}\n\n\tif !v.semver || !other.semver {\n\t\treturn 0\n\t}\n\n\tswitch {\n\tcase v.preRelease == \"\" && other.preRelease != \"\":\n\t\treturn 1\n\tcase v.preRelease != \"\" && other.preRelease == \"\":\n\t\treturn -1\n\tcase v.preRelease == other.preRelease: // includes case where both are \"\"\n\t\treturn 0\n\t}\n\n\tvPR := strings.Split(v.preRelease, \".\")\n\toPR := strings.Split(other.preRelease, \".\")\n\tfor i := 0; i < len(vPR) && i < len(oPR); i++ {\n\t\tvNum, err := strconv.ParseUint(vPR[i], 10, 0)\n\t\tif err == nil {\n\t\t\toNum, err := strconv.ParseUint(oPR[i], 10, 0)\n\t\t\tif err == nil {\n\t\t\t\tswitch {\n\t\t\t\tcase oNum < vNum:\n\t\t\t\t\treturn 1\n\t\t\t\tcase oNum > vNum:\n\t\t\t\t\treturn -1\n\t\t\t\tdefault:\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif oPR[i] < vPR[i] {\n\t\t\treturn 1\n\t\t} else if oPR[i] > vPR[i] {\n\t\t\treturn -1\n\t\t}\n\t}\n\n\tswitch {\n\tcase len(oPR) < len(vPR):\n\t\treturn 1\n\tcase len(oPR) > len(vPR):\n\t\treturn -1\n\t}\n\n\treturn 0\n}\n\n// returns false if array contain any non-zero element\nfunc onlyZeros(array []uint) bool {\n\tfor _, num := range array {\n\t\tif num != 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// AtLeast tests if a version is at least equal to a given minimum version. If both\n// Versions are Semantic Versions, this will use the Semantic Version comparison\n// algorithm. Otherwise, it will compare only the numeric components, with non-present\n// components being considered \"0\" (ie, \"1.4\" is equal to \"1.4.0\").\nfunc (v *Version) AtLeast(min *Version) bool {\n\treturn v.compareInternal(min) != -1\n}\n\n// LessThan tests if a version is less than a given version. (It is exactly the opposite\n// of AtLeast, for situations where asking \"is v too old?\" makes more sense than asking\n// \"is v new enough?\".)\nfunc (v *Version) LessThan(other *Version) bool {\n\treturn v.compareInternal(other) == -1\n}\n\n// Compare compares v against a version string (which will be parsed as either Semantic\n// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if\n// it is greater than other, or 0 if they are equal.\nfunc (v *Version) Compare(other string) (int, error) {\n\tov, err := parse(other, v.semver)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn v.compareInternal(ov), nil\n}\n"
  },
  {
    "path": "pkg/internal/version/version_test.go",
    "content": "/*\nCopyright 2016 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype testItem struct {\n\tversion    string\n\tunparsed   string\n\tequalsPrev bool\n}\n\nfunc testOne(v *Version, item, prev testItem) error {\n\tstr := v.String()\n\tif item.unparsed == \"\" {\n\t\tif str != item.version {\n\t\t\treturn fmt.Errorf(\"bad round-trip: %q -> %q\", item.version, str)\n\t\t}\n\t} else {\n\t\tif str != item.unparsed {\n\t\t\treturn fmt.Errorf(\"bad unparse: %q -> %q, expected %q\", item.version, str, item.unparsed)\n\t\t}\n\t}\n\n\tif prev.version != \"\" {\n\t\tcmp, err := v.Compare(prev.version)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unexpected parse error: %v\", err)\n\t\t}\n\t\trv, err := parse(prev.version, v.semver)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unexpected parse error: %v\", err)\n\t\t}\n\t\trcmp, err := rv.Compare(item.version)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unexpected parse error: %v\", err)\n\t\t}\n\n\t\tswitch {\n\t\tcase cmp == -1:\n\t\t\treturn fmt.Errorf(\"unexpected ordering %q < %q\", item.version, prev.version)\n\t\tcase cmp == 0 && !item.equalsPrev:\n\t\t\treturn fmt.Errorf(\"unexpected comparison %q == %q\", item.version, prev.version)\n\t\tcase cmp == 1 && item.equalsPrev:\n\t\t\treturn fmt.Errorf(\"unexpected comparison %q != %q\", item.version, prev.version)\n\t\tcase cmp != -rcmp:\n\t\t\treturn fmt.Errorf(\"unexpected reverse comparison %q <=> %q %v %v %v %v\", item.version, prev.version, cmp, rcmp, v.Components(), rv.Components())\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc TestSemanticVersions(t *testing.T) {\n\ttests := []testItem{\n\t\t// This is every version string that appears in the 2.0 semver spec,\n\t\t// sorted in strictly increasing order except as noted.\n\t\t{version: \"0.1.0\"},\n\t\t{version: \"1.0.0-0.3.7\"},\n\t\t{version: \"1.0.0-alpha\"},\n\t\t{version: \"1.0.0-alpha+001\", equalsPrev: true},\n\t\t{version: \"1.0.0-alpha.1\"},\n\t\t{version: \"1.0.0-alpha.beta\"},\n\t\t{version: \"1.0.0-beta\"},\n\t\t{version: \"1.0.0-beta+exp.sha.5114f85\", equalsPrev: true},\n\t\t{version: \"1.0.0-beta.2\"},\n\t\t{version: \"1.0.0-beta.11\"},\n\t\t{version: \"1.0.0-rc.1\"},\n\t\t{version: \"1.0.0-x.7.z.92\"},\n\t\t{version: \"1.0.0\"},\n\t\t{version: \"1.0.0+20130313144700\", equalsPrev: true},\n\t\t{version: \"1.8.0-alpha.3\"},\n\t\t{version: \"1.8.0-alpha.3.673+73326ef01d2d7c\"},\n\t\t{version: \"1.9.0\"},\n\t\t{version: \"1.10.0\"},\n\t\t{version: \"1.11.0\"},\n\t\t{version: \"2.0.0\"},\n\t\t{version: \"2.1.0\"},\n\t\t{version: \"2.1.1\"},\n\t\t{version: \"42.0.0\"},\n\n\t\t// We also allow whitespace and \"v\" prefix\n\t\t{version: \"   42.0.0\", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"\\t42.0.0  \", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"43.0.0-1\", unparsed: \"43.0.0-1\"},\n\t\t{version: \"43.0.0-1  \", unparsed: \"43.0.0-1\", equalsPrev: true},\n\t\t{version: \"v43.0.0-1\", unparsed: \"43.0.0-1\", equalsPrev: true},\n\t\t{version: \"  v43.0.0\", unparsed: \"43.0.0\"},\n\t\t{version: \" 43.0.0 \", unparsed: \"43.0.0\", equalsPrev: true},\n\t}\n\n\tvar prev testItem\n\tfor _, item := range tests {\n\t\tv, err := ParseSemantic(item.version)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected parse error: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\terr = testOne(v, item, prev)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%v\", err)\n\t\t}\n\t\tprev = item\n\t}\n}\n\nfunc TestBadSemanticVersions(t *testing.T) {\n\ttests := []string{\n\t\t// \"MUST take the form X.Y.Z\"\n\t\t\"1\",\n\t\t\"1.2\",\n\t\t\"1.2.3.4\",\n\t\t\".2.3\",\n\t\t\"1..3\",\n\t\t\"1.2.\",\n\t\t\"\",\n\t\t\"..\",\n\t\t// \"where X, Y, and Z are non-negative integers\"\n\t\t\"-1.2.3\",\n\t\t\"1.-2.3\",\n\t\t\"1.2.-3\",\n\t\t\"1a.2.3\",\n\t\t\"1.2a.3\",\n\t\t\"1.2.3a\",\n\t\t\"a1.2.3\",\n\t\t\"a.b.c\",\n\t\t\"1 .2.3\",\n\t\t\"1. 2.3\",\n\t\t// \"and MUST NOT contain leading zeroes.\"\n\t\t\"01.2.3\",\n\t\t\"1.02.3\",\n\t\t\"1.2.03\",\n\t\t// \"[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen\"\n\t\t\"1.2.3-/\",\n\t\t// \"[pre-release] identifiers MUST NOT be empty\"\n\t\t\"1.2.3-\",\n\t\t\"1.2.3-.\",\n\t\t\"1.2.3-foo.\",\n\t\t\"1.2.3-.foo\",\n\t\t// \"Numeric [pre-release] identifiers MUST NOT include leading zeroes\"\n\t\t\"1.2.3-01\",\n\t\t// \"[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen\"\n\t\t\"1.2.3+/\",\n\t\t// \"[build metadata] identifiers MUST NOT be empty\"\n\t\t\"1.2.3+\",\n\t\t\"1.2.3+.\",\n\t\t\"1.2.3+foo.\",\n\t\t\"1.2.3+.foo\",\n\n\t\t// whitespace/\"v\"-prefix checks\n\t\t\"v 1.2.3\",\n\t\t\"vv1.2.3\",\n\t}\n\n\tfor i := range tests {\n\t\t_, err := ParseSemantic(tests[i])\n\t\tif err == nil {\n\t\t\tt.Errorf(\"unexpected success parsing invalid semver %q\", tests[i])\n\t\t}\n\t}\n}\n\nfunc TestGenericVersions(t *testing.T) {\n\ttests := []testItem{\n\t\t// This is all of the strings from TestSemanticVersions, plus some strings\n\t\t// from TestBadSemanticVersions that should parse as generic versions,\n\t\t// plus some additional strings.\n\t\t{version: \"0.1.0\", unparsed: \"0.1.0\"},\n\t\t{version: \"1.0.0-0.3.7\", unparsed: \"1.0.0\"},\n\t\t{version: \"1.0.0-alpha\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0-alpha+001\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0-alpha.1\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0-alpha.beta\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0.beta\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0-beta+exp.sha.5114f85\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0.beta.2\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0.beta.11\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0.rc.1\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0-x.7.z.92\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.0.0+20130313144700\", unparsed: \"1.0.0\", equalsPrev: true},\n\t\t{version: \"1.2\", unparsed: \"1.2\"},\n\t\t{version: \"1.2a.3\", unparsed: \"1.2\", equalsPrev: true},\n\t\t{version: \"1.2.3\", unparsed: \"1.2.3\"},\n\t\t{version: \"1.2.3.0\", unparsed: \"1.2.3.0\", equalsPrev: true},\n\t\t{version: \"1.2.3a\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3-foo.\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3-.foo\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3-01\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3+\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3+foo.\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3+.foo\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.02.3\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.03\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.003\", unparsed: \"1.2.3\", equalsPrev: true},\n\t\t{version: \"1.2.3.4\", unparsed: \"1.2.3.4\"},\n\t\t{version: \"1.2.3.4b3\", unparsed: \"1.2.3.4\", equalsPrev: true},\n\t\t{version: \"1.2.3.4.5\", unparsed: \"1.2.3.4.5\"},\n\t\t{version: \"1.9.0\", unparsed: \"1.9.0\"},\n\t\t{version: \"1.9.0.0.0.0.0.0\", unparsed: \"1.9.0.0.0.0.0.0\", equalsPrev: true},\n\t\t{version: \"1.10.0\", unparsed: \"1.10.0\"},\n\t\t{version: \"1.11.0\", unparsed: \"1.11.0\"},\n\t\t{version: \"1.11.0.0.5\", unparsed: \"1.11.0.0.5\"},\n\t\t{version: \"2.0.0\", unparsed: \"2.0.0\"},\n\t\t{version: \"2.1.0\", unparsed: \"2.1.0\"},\n\t\t{version: \"2.1.1\", unparsed: \"2.1.1\"},\n\t\t{version: \"42.0.0\", unparsed: \"42.0.0\"},\n\t\t{version: \"   42.0.0\", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"\\t42.0.0  \", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"42.0.0-1\", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"42.0.0-1  \", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"v42.0.0-1\", unparsed: \"42.0.0\", equalsPrev: true},\n\t\t{version: \"  v43.0.0\", unparsed: \"43.0.0\"},\n\t\t{version: \" 43.0.0 \", unparsed: \"43.0.0\", equalsPrev: true},\n\t}\n\n\tvar prev testItem\n\tfor _, item := range tests {\n\t\tv, err := ParseGeneric(item.version)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected parse error: %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\terr = testOne(v, item, prev)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%v\", err)\n\t\t}\n\t\tprev = item\n\t}\n}\n\nfunc TestBadGenericVersions(t *testing.T) {\n\ttests := []string{\n\t\t\"1\",\n\t\t\"01.2.3\",\n\t\t\"-1.2.3\",\n\t\t\"1.-2.3\",\n\t\t\".2.3\",\n\t\t\"1..3\",\n\t\t\"1a.2.3\",\n\t\t\"a1.2.3\",\n\t\t\"1 .2.3\",\n\t\t\"1. 2.3\",\n\t\t\"1.bob\",\n\t\t\"bob\",\n\t\t\"v 1.2.3\",\n\t\t\"vv1.2.3\",\n\t\t\"\",\n\t\t\".\",\n\t}\n\n\tfor i := range tests {\n\t\t_, err := ParseGeneric(tests[i])\n\t\tif err == nil {\n\t\t\tt.Errorf(\"unexpected success parsing invalid version %q\", tests[i])\n\t\t}\n\t}\n}\n\nfunc TestComponents(t *testing.T) {\n\n\tvar tests = []struct {\n\t\tversion               string\n\t\tsemver                bool\n\t\texpectedComponents    []uint\n\t\texpectedMajor         uint\n\t\texpectedMinor         uint\n\t\texpectedPatch         uint\n\t\texpectedPreRelease    string\n\t\texpectedBuildMetadata string\n\t}{\n\t\t{\n\t\t\tversion:            \"1.0.2\",\n\t\t\tsemver:             true,\n\t\t\texpectedComponents: []uint{1, 0, 2},\n\t\t\texpectedMajor:      1,\n\t\t\texpectedMinor:      0,\n\t\t\texpectedPatch:      2,\n\t\t},\n\t\t{\n\t\t\tversion:               \"1.0.2-alpha+001\",\n\t\t\tsemver:                true,\n\t\t\texpectedComponents:    []uint{1, 0, 2},\n\t\t\texpectedMajor:         1,\n\t\t\texpectedMinor:         0,\n\t\t\texpectedPatch:         2,\n\t\t\texpectedPreRelease:    \"alpha\",\n\t\t\texpectedBuildMetadata: \"001\",\n\t\t},\n\t\t{\n\t\t\tversion:            \"1.2\",\n\t\t\tsemver:             false,\n\t\t\texpectedComponents: []uint{1, 2},\n\t\t\texpectedMajor:      1,\n\t\t\texpectedMinor:      2,\n\t\t},\n\t\t{\n\t\t\tversion:               \"1.0.2-beta+exp.sha.5114f85\",\n\t\t\tsemver:                true,\n\t\t\texpectedComponents:    []uint{1, 0, 2},\n\t\t\texpectedMajor:         1,\n\t\t\texpectedMinor:         0,\n\t\t\texpectedPatch:         2,\n\t\t\texpectedPreRelease:    \"beta\",\n\t\t\texpectedBuildMetadata: \"exp.sha.5114f85\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tversion, _ := parse(test.version, test.semver)\n\t\tif !reflect.DeepEqual(test.expectedComponents, version.Components()) {\n\t\t\tt.Error(\"parse returned un'expected components\")\n\t\t}\n\t\tif test.expectedMajor != version.Major() {\n\t\t\tt.Errorf(\"parse returned version.Major %d, expected %d\", test.expectedMajor, version.Major())\n\t\t}\n\t\tif test.expectedMinor != version.Minor() {\n\t\t\tt.Errorf(\"parse returned version.Minor %d, expected %d\", test.expectedMinor, version.Minor())\n\t\t}\n\t\tif test.expectedPatch != version.Patch() {\n\t\t\tt.Errorf(\"parse returned version.Patch %d, expected %d\", test.expectedPatch, version.Patch())\n\t\t}\n\t\tif test.expectedPreRelease != version.PreRelease() {\n\t\t\tt.Errorf(\"parse returned version.PreRelease %s, expected %s\", test.expectedPreRelease, version.PreRelease())\n\t\t}\n\t\tif test.expectedBuildMetadata != version.BuildMetadata() {\n\t\t\tt.Errorf(\"parse returned version.BuildMetadata %s, expected %s\", test.expectedBuildMetadata, version.BuildMetadata())\n\t\t}\n\t}\n}\n\nfunc TestVersion_LessThan_AtLeast(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tversion  string\n\t\tlessThan string\n\t\twant     bool\n\t}{\n\t\t{\n\t\t\tname:     \"same version\",\n\t\t\tversion:  \"1.21.1\",\n\t\t\tlessThan: \"1.21.1\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"one patch less\",\n\t\t\tversion:  \"1.21.1\",\n\t\t\tlessThan: \"1.21.2\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"one patch greater\",\n\t\t\tversion:  \"1.21.3\",\n\t\t\tlessThan: \"1.21.2\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"one patch less but one minor more\",\n\t\t\tversion:  \"1.22.1\",\n\t\t\tlessThan: \"1.21.2\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"one patch greater but one minor less\",\n\t\t\tversion:  \"1.20.3\",\n\t\t\tlessThan: \"1.21.2\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"same version against prerelease\",\n\t\t\tversion:  \"v1.24.0\",\n\t\t\tlessThan: \"v1.24.0-beta.0.115+65178fec72df62\",\n\t\t\twant:     false,\n\t\t},\n\t\t{\n\t\t\tname:     \"prerelease against same version\",\n\t\t\tversion:  \"v1.24.0-beta.0.115+65178fec72df62\",\n\t\t\tlessThan: \"v1.24.0\",\n\t\t\twant:     true,\n\t\t},\n\t\t{\n\t\t\tname:     \"prerelease against same version considering prereleases\",\n\t\t\tversion:  \"v1.24.0-beta.0.115+65178fec72df62\",\n\t\t\tlessThan: \"v1.24.0-0\",\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\tv := MustParseSemantic(tt.version)\n\t\t\tif got := v.LessThan(MustParseSemantic(tt.lessThan)); got != tt.want {\n\t\t\t\tt.Errorf(\"Version.LessThan() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t\tif got := v.AtLeast(MustParseSemantic(tt.lessThan)); got == tt.want {\n\t\t\t\tt.Errorf(\"Version.AtLeast() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/log/doc.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package log defines a logging interface that kind uses\n// This is roughly a minimal subset of klog github.com/kubernetes/klog\npackage log\n"
  },
  {
    "path": "pkg/log/noop.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage log\n\n// NoopLogger implements the Logger interface and never logs anything\ntype NoopLogger struct{}\n\n// Warn meets the Logger interface but does nothing\nfunc (n NoopLogger) Warn(message string) {}\n\n// Warnf meets the Logger interface but does nothing\nfunc (n NoopLogger) Warnf(format string, args ...interface{}) {}\n\n// Error meets the Logger interface but does nothing\nfunc (n NoopLogger) Error(message string) {}\n\n// Errorf meets the Logger interface but does nothing\nfunc (n NoopLogger) Errorf(format string, args ...interface{}) {}\n\n// V meets the Logger interface but does nothing\nfunc (n NoopLogger) V(level Level) InfoLogger { return NoopInfoLogger{} }\n\n// NoopInfoLogger implements the InfoLogger interface and never logs anything\ntype NoopInfoLogger struct{}\n\n// Enabled meets the InfoLogger interface but always returns false\nfunc (n NoopInfoLogger) Enabled() bool { return false }\n\n// Info meets the InfoLogger interface but does nothing\nfunc (n NoopInfoLogger) Info(message string) {}\n\n// Infof meets the InfoLogger interface but does nothing\nfunc (n NoopInfoLogger) Infof(format string, args ...interface{}) {}\n"
  },
  {
    "path": "pkg/log/types.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage log\n\n// Level is a verbosity logging level for Info logs\n// See also https://github.com/kubernetes/klog\ntype Level int32\n\n// Logger defines the logging interface kind uses\n// It is roughly a subset of github.com/kubernetes/klog\ntype Logger interface {\n\t// Warn should be used to write user facing warnings\n\tWarn(message string)\n\t// Warnf should be used to write Printf style user facing warnings\n\tWarnf(format string, args ...interface{})\n\t// Error may be used to write an error message when it occurs\n\t// Prefer returning an error instead in most cases\n\tError(message string)\n\t// Errorf may be used to write a Printf style error message when it occurs\n\t// Prefer returning an error instead in most cases\n\tErrorf(format string, args ...interface{})\n\t// V() returns an InfoLogger for a given verbosity Level\n\t//\n\t// Normal verbosity levels:\n\t// V(0): normal user facing messages go to V(0)\n\t// V(1): debug messages start when V(N > 0), these should be high level\n\t// V(2): more detailed log messages\n\t// V(3+): trace level logging, in increasing \"noisiness\" ... allowing\n\t// arbitrarily detailed logging at extremely low cost unless the\n\t// logger has actually been configured to display these (E.G. via the -v\n\t// command line flag)\n\t//\n\t// It is expected that the returned InfoLogger will be extremely cheap\n\t// to interact with for a Level greater than the enabled level\n\tV(Level) InfoLogger\n}\n\n// InfoLogger defines the info logging interface kind uses\n// It is roughly a subset of Verbose from github.com/kubernetes/klog\ntype InfoLogger interface {\n\t// Info is used to write a user facing status message\n\t//\n\t// See: Logger.V\n\tInfo(message string)\n\t// Infof is used to write a Printf style user facing status message\n\tInfof(format string, args ...interface{})\n\t// Enabled should return true if this verbosity level is enabled\n\t// on the Logger\n\t//\n\t// See: Logger.V\n\tEnabled() bool\n}\n"
  },
  {
    "path": "site/.gitignore",
    "content": "public/*\nresources/*\n.hugo_build.lock"
  },
  {
    "path": "site/LICENSE",
    "content": "Attribution 4.0 International\n\n=======================================================================\n\nCreative Commons Corporation (\"Creative Commons\") is not a law firm and\ndoes not provide legal services or legal advice. Distribution of\nCreative Commons public licenses does not create a lawyer-client or\nother relationship. Creative Commons makes its licenses and related\ninformation available on an \"as-is\" basis. Creative Commons gives no\nwarranties regarding its licenses, any material licensed under their\nterms and conditions, or any related information. Creative Commons\ndisclaims all liability for damages resulting from their use to the\nfullest extent possible.\n\nUsing Creative Commons Public Licenses\n\nCreative Commons public licenses provide a standard set of terms and\nconditions that creators and other rights holders may use to share\noriginal works of authorship and other material subject to copyright\nand certain other rights specified in the public license below. The\nfollowing considerations are for informational purposes only, are not\nexhaustive, and do not form part of our licenses.\n\n     Considerations for licensors: Our public licenses are\n     intended for use by those authorized to give the public\n     permission to use material in ways otherwise restricted by\n     copyright and certain other rights. Our licenses are\n     irrevocable. Licensors should read and understand the terms\n     and conditions of the license they choose before applying it.\n     Licensors should also secure all rights necessary before\n     applying our licenses so that the public can reuse the\n     material as expected. Licensors should clearly mark any\n     material not subject to the license. This includes other CC-\n     licensed material, or material used under an exception or\n     limitation to copyright. More considerations for licensors:\n\twiki.creativecommons.org/Considerations_for_licensors\n\n     Considerations for the public: By using one of our public\n     licenses, a licensor grants the public permission to use the\n     licensed material under specified terms and conditions. If\n     the licensor's permission is not necessary for any reason--for\n     example, because of any applicable exception or limitation to\n     copyright--then that use is not regulated by the license. Our\n     licenses grant only permissions under copyright and certain\n     other rights that a licensor has authority to grant. Use of\n     the licensed material may still be restricted for other\n     reasons, including because others have copyright or other\n     rights in the material. A licensor may make special requests,\n     such as asking that all changes be marked or described.\n     Although not required by our licenses, you are encouraged to\n     respect those requests where reasonable. More_considerations\n     for the public: \n\twiki.creativecommons.org/Considerations_for_licensees\n\n=======================================================================\n\nCreative Commons Attribution 4.0 International Public License\n\nBy exercising the Licensed Rights (defined below), You accept and agree\nto be bound by the terms and conditions of this Creative Commons\nAttribution 4.0 International Public License (\"Public License\"). To the\nextent this Public License may be interpreted as a contract, You are\ngranted the Licensed Rights in consideration of Your acceptance of\nthese terms and conditions, and the Licensor grants You such rights in\nconsideration of benefits the Licensor receives from making the\nLicensed Material available under these terms and conditions.\n\n\nSection 1 -- Definitions.\n\n  a. Adapted Material means material subject to Copyright and Similar\n     Rights that is derived from or based upon the Licensed Material\n     and in which the Licensed Material is translated, altered,\n     arranged, transformed, or otherwise modified in a manner requiring\n     permission under the Copyright and Similar Rights held by the\n     Licensor. For purposes of this Public License, where the Licensed\n     Material is a musical work, performance, or sound recording,\n     Adapted Material is always produced where the Licensed Material is\n     synched in timed relation with a moving image.\n\n  b. Adapter's License means the license You apply to Your Copyright\n     and Similar Rights in Your contributions to Adapted Material in\n     accordance with the terms and conditions of this Public License.\n\n  c. Copyright and Similar Rights means copyright and/or similar rights\n     closely related to copyright including, without limitation,\n     performance, broadcast, sound recording, and Sui Generis Database\n     Rights, without regard to how the rights are labeled or\n     categorized. For purposes of this Public License, the rights\n     specified in Section 2(b)(1)-(2) are not Copyright and Similar\n     Rights.\n\n  d. Effective Technological Measures means those measures that, in the\n     absence of proper authority, may not be circumvented under laws\n     fulfilling obligations under Article 11 of the WIPO Copyright\n     Treaty adopted on December 20, 1996, and/or similar international\n     agreements.\n\n  e. Exceptions and Limitations means fair use, fair dealing, and/or\n     any other exception or limitation to Copyright and Similar Rights\n     that applies to Your use of the Licensed Material.\n\n  f. Licensed Material means the artistic or literary work, database,\n     or other material to which the Licensor applied this Public\n     License.\n\n  g. Licensed Rights means the rights granted to You subject to the\n     terms and conditions of this Public License, which are limited to\n     all Copyright and Similar Rights that apply to Your use of the\n     Licensed Material and that the Licensor has authority to license.\n\n  h. Licensor means the individual(s) or entity(ies) granting rights\n     under this Public License.\n\n  i. Share means to provide material to the public by any means or\n     process that requires permission under the Licensed Rights, such\n     as reproduction, public display, public performance, distribution,\n     dissemination, communication, or importation, and to make material\n     available to the public including in ways that members of the\n     public may access the material from a place and at a time\n     individually chosen by them.\n\n  j. Sui Generis Database Rights means rights other than copyright\n     resulting from Directive 96/9/EC of the European Parliament and of\n     the Council of 11 March 1996 on the legal protection of databases,\n     as amended and/or succeeded, as well as other essentially\n     equivalent rights anywhere in the world.\n\n  k. You means the individual or entity exercising the Licensed Rights\n     under this Public License. Your has a corresponding meaning.\n\n\nSection 2 -- Scope.\n\n  a. License grant.\n\n       1. Subject to the terms and conditions of this Public License,\n          the Licensor hereby grants You a worldwide, royalty-free,\n          non-sublicensable, non-exclusive, irrevocable license to\n          exercise the Licensed Rights in the Licensed Material to:\n\n            a. reproduce and Share the Licensed Material, in whole or\n               in part; and\n\n            b. produce, reproduce, and Share Adapted Material.\n\n       2. Exceptions and Limitations. For the avoidance of doubt, where\n          Exceptions and Limitations apply to Your use, this Public\n          License does not apply, and You do not need to comply with\n          its terms and conditions.\n\n       3. Term. The term of this Public License is specified in Section\n          6(a).\n\n       4. Media and formats; technical modifications allowed. The\n          Licensor authorizes You to exercise the Licensed Rights in\n          all media and formats whether now known or hereafter created,\n          and to make technical modifications necessary to do so. The\n          Licensor waives and/or agrees not to assert any right or\n          authority to forbid You from making technical modifications\n          necessary to exercise the Licensed Rights, including\n          technical modifications necessary to circumvent Effective\n          Technological Measures. For purposes of this Public License,\n          simply making modifications authorized by this Section 2(a)\n          (4) never produces Adapted Material.\n\n       5. Downstream recipients.\n\n            a. Offer from the Licensor -- Licensed Material. Every\n               recipient of the Licensed Material automatically\n               receives an offer from the Licensor to exercise the\n               Licensed Rights under the terms and conditions of this\n               Public License.\n\n            b. No downstream restrictions. You may not offer or impose\n               any additional or different terms or conditions on, or\n               apply any Effective Technological Measures to, the\n               Licensed Material if doing so restricts exercise of the\n               Licensed Rights by any recipient of the Licensed\n               Material.\n\n       6. No endorsement. Nothing in this Public License constitutes or\n          may be construed as permission to assert or imply that You\n          are, or that Your use of the Licensed Material is, connected\n          with, or sponsored, endorsed, or granted official status by,\n          the Licensor or others designated to receive attribution as\n          provided in Section 3(a)(1)(A)(i).\n\n  b. Other rights.\n\n       1. Moral rights, such as the right of integrity, are not\n          licensed under this Public License, nor are publicity,\n          privacy, and/or other similar personality rights; however, to\n          the extent possible, the Licensor waives and/or agrees not to\n          assert any such rights held by the Licensor to the limited\n          extent necessary to allow You to exercise the Licensed\n          Rights, but not otherwise.\n\n       2. Patent and trademark rights are not licensed under this\n          Public License.\n\n       3. To the extent possible, the Licensor waives any right to\n          collect royalties from You for the exercise of the Licensed\n          Rights, whether directly or through a collecting society\n          under any voluntary or waivable statutory or compulsory\n          licensing scheme. In all other cases the Licensor expressly\n          reserves any right to collect such royalties.\n\n\nSection 3 -- License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the\nfollowing conditions.\n\n  a. Attribution.\n\n       1. If You Share the Licensed Material (including in modified\n          form), You must:\n\n            a. retain the following if it is supplied by the Licensor\n               with the Licensed Material:\n\n                 i. identification of the creator(s) of the Licensed\n                    Material and any others designated to receive\n                    attribution, in any reasonable manner requested by\n                    the Licensor (including by pseudonym if\n                    designated);\n\n                ii. a copyright notice;\n\n               iii. a notice that refers to this Public License;\n\n                iv. a notice that refers to the disclaimer of\n                    warranties;\n\n                 v. a URI or hyperlink to the Licensed Material to the\n                    extent reasonably practicable;\n\n            b. indicate if You modified the Licensed Material and\n               retain an indication of any previous modifications; and\n\n            c. indicate the Licensed Material is licensed under this\n               Public License, and include the text of, or the URI or\n               hyperlink to, this Public License.\n\n       2. You may satisfy the conditions in Section 3(a)(1) in any\n          reasonable manner based on the medium, means, and context in\n          which You Share the Licensed Material. For example, it may be\n          reasonable to satisfy the conditions by providing a URI or\n          hyperlink to a resource that includes the required\n          information.\n\n       3. If requested by the Licensor, You must remove any of the\n          information required by Section 3(a)(1)(A) to the extent\n          reasonably practicable.\n\n       4. If You Share Adapted Material You produce, the Adapter's\n          License You apply must not prevent recipients of the Adapted\n          Material from complying with this Public License.\n\n\nSection 4 -- Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that\napply to Your use of the Licensed Material:\n\n  a. for the avoidance of doubt, Section 2(a)(1) grants You the right\n     to extract, reuse, reproduce, and Share all or a substantial\n     portion of the contents of the database;\n\n  b. if You include all or a substantial portion of the database\n     contents in a database in which You have Sui Generis Database\n     Rights, then the database in which You have Sui Generis Database\n     Rights (but not its individual contents) is Adapted Material; and\n\n  c. You must comply with the conditions in Section 3(a) if You Share\n     all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not\nreplace Your obligations under this Public License where the Licensed\nRights include other Copyright and Similar Rights.\n\n\nSection 5 -- Disclaimer of Warranties and Limitation of Liability.\n\n  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE\n     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS\n     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF\n     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,\n     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,\n     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR\n     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,\n     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT\n     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT\n     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.\n\n  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE\n     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,\n     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,\n     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,\n     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR\n     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN\n     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR\n     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR\n     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.\n\n  c. The disclaimer of warranties and limitation of liability provided\n     above shall be interpreted in a manner that, to the extent\n     possible, most closely approximates an absolute disclaimer and\n     waiver of all liability.\n\n\nSection 6 -- Term and Termination.\n\n  a. This Public License applies for the term of the Copyright and\n     Similar Rights licensed here. However, if You fail to comply with\n     this Public License, then Your rights under this Public License\n     terminate automatically.\n\n  b. Where Your right to use the Licensed Material has terminated under\n     Section 6(a), it reinstates:\n\n       1. automatically as of the date the violation is cured, provided\n          it is cured within 30 days of Your discovery of the\n          violation; or\n\n       2. upon express reinstatement by the Licensor.\n\n     For the avoidance of doubt, this Section 6(b) does not affect any\n     right the Licensor may have to seek remedies for Your violations\n     of this Public License.\n\n  c. For the avoidance of doubt, the Licensor may also offer the\n     Licensed Material under separate terms or conditions or stop\n     distributing the Licensed Material at any time; however, doing so\n     will not terminate this Public License.\n\n  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public\n     License.\n\n\nSection 7 -- Other Terms and Conditions.\n\n  a. The Licensor shall not be bound by any additional or different\n     terms or conditions communicated by You unless expressly agreed.\n\n  b. Any arrangements, understandings, or agreements regarding the\n     Licensed Material not stated herein are separate from and\n     independent of the terms and conditions of this Public License.\n\n\nSection 8 -- Interpretation.\n\n  a. For the avoidance of doubt, this Public License does not, and\n     shall not be interpreted to, reduce, limit, restrict, or impose\n     conditions on any use of the Licensed Material that could lawfully\n     be made without permission under this Public License.\n\n  b. To the extent possible, if any provision of this Public License is\n     deemed unenforceable, it shall be automatically reformed to the\n     minimum extent necessary to make it enforceable. If the provision\n     cannot be reformed, it shall be severed from this Public License\n     without affecting the enforceability of the remaining terms and\n     conditions.\n\n  c. No term or condition of this Public License will be waived and no\n     failure to comply consented to unless expressly agreed to by the\n     Licensor.\n\n  d. Nothing in this Public License constitutes or may be interpreted\n     as a limitation upon, or waiver of, any privileges and immunities\n     that apply to the Licensor or You, including from the legal\n     processes of any jurisdiction or authority.\n\n\n=======================================================================\n\nCreative Commons is not a party to its public\nlicenses. Notwithstanding, Creative Commons may elect to apply one of\nits public licenses to material it publishes and in those instances\nwill be considered the \"Licensor.\" The text of the Creative Commons\npublic licenses is dedicated to the public domain under the CC0 Public\nDomain Dedication. Except for the limited purpose of indicating that\nmaterial is shared under a Creative Commons public license or as\notherwise permitted by the Creative Commons policies published at\ncreativecommons.org/policies, Creative Commons does not authorize the\nuse of the trademark \"Creative Commons\" or any other trademark or logo\nof Creative Commons without its prior written consent including,\nwithout limitation, in connection with any unauthorized modifications\nto any of its public licenses or any other arrangements,\nunderstandings, or agreements concerning use of licensed material. For\nthe avoidance of doubt, this paragraph does not form part of the\npublic licenses.\n\nCreative Commons may be contacted at creativecommons.org.\n"
  },
  {
    "path": "site/Makefile",
    "content": "REPO_ROOT:=${CURDIR}/..\n# setup go for managing hugo\nPATH:=$(shell cd $(REPO_ROOT) && . hack/build/setup-go.sh && echo \"$${PATH}\")\n# go1.9+ can autodetect GOROOT, but if some other tool sets it ...\nGOROOT:=\n# enable modules\nGO111MODULE=on\n# disable CGO by default for static binaries\nCGO_ENABLED=0\nexport PATH GOROOT GO111MODULE CGO_ENABLED\n# work around broken PATH export\nSPACE:=$(subst ,, )\nSHELL:=env PATH=$(subst $(SPACE),\\$(SPACE),$(PATH)) $(SHELL)\n\n# from https://github.com/kubernetes/website/blob/master/Makefile\nDOCKER       = docker\nHUGO_VERSION = 0.60.0\nDOCKER_IMAGE = jojomi/hugo:$(HUGO_VERSION)\nDOCKER_RUN   = $(DOCKER) run --rm --interactive --tty --volume $(realpath $(CURDIR)/..):/src -p 1313:1313 --workdir /src/site --entrypoint=hugo --platform linux/amd64 $(DOCKER_IMAGE)\n\nHUGO_BIN:=$(REPO_ROOT)/bin/hugo\n\n$(HUGO_BIN):\n\tgo build -o $(HUGO_BIN) github.com/gohugoio/hugo\n\nhugo: $(HUGO_BIN)\n\nserve: hugo\n\t$(HUGO_BIN) server --bind=\"0.0.0.0\" \\\n\t--ignoreCache \\\n\t--buildFuture \\\n\t--disableFastRender\n\nbuild: hugo\n\t$(HUGO_BIN)\n\n.PHONY: build serve hugo\n"
  },
  {
    "path": "site/README.md",
    "content": "# kind's docs\n\nkind's docs are built with [hugo], and can be browsed live at https://kind.sigs.k8s.io/\n\nTo browse them locally, install hugo and run `make serve` from this directory.\n\n[hugo]: https://gohugo.io\n"
  },
  {
    "path": "site/assets/css/inline.css",
    "content": "html {\n  width: 100%;\n  font-size: 18px;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased !important;\n  -moz-osx-font-smoothing: grayscale !important;\n  text-rendering: optimizeLegibility !important;\n  -webkit-tap-highlight-color: transparent;\n  height: 100%;\n  min-height: 100%;\n  margin: 0;\n  padding: 0;\n  font-size: 100%;\n  background-color: #326ce5;\n  color: white;\n  font-family: -apple-system, BlinkMacSystemFont, Roboto, Droid Sans, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;\n}\n\ncode,\npre {\n  font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Roboto Mono, Courier, monospace;\n}\n\nbody {\n  width: 100%;\n  margin: 0;\n  padding: 0;\n  font-size: 18px;\n  line-height: 1.5;\n  min-height: 100%;\n  /* sticky footer */\n  display: flex;\n  flex-direction: column;\n  color: black;\n  background-color: white;\n}\n\n#wrapper {\n  background-color: white;\n  max-width: 100%;\n  clear: both;\n  overflow-x: hidden;\n  flex: 1 0 auto;\n  margin: 0;\n  margin-left: 240px;\n}\n\n.sidebar-collapsed #wrapper {\n  margin-left: 0;\n}\n\n#content,\n#footer-content {\n  margin: 0 auto;\n  padding: 0 1em;\n  width: calc(100% - 2em);\n  max-width: 40em;\n}\n\n#footer {\n  background: #326ce5;\n  color: white;\n  padding: 1em;\n  margin-top: 1em;\n  border-top: .1em solid rgba(0, 0, 0, 0.1);\n}\n\n#footer a {\n  color: white;\n}\n\n/* page footer*/\n.footer {\n  text-align: center;\n}\n\n.footer>* {\n  font-size: .8em;\n  line-height: 1.4;\n}\n\n#content {\n  /* clear navbar */\n  padding-top: 1.5em;\n  /* push down footer */\n  min-height: calc(100vh - 4em);\n}\n\n#navbar {\n  position: fixed;\n  top: 0;\n  width: calc(100% - 240px);\n  padding: 2px 0;\n  margin-left: .1em;\n  /*background-color: #326ce5;*/\n  background-color: #326ce5;\n  border-bottom: .1em solid rgba(0, 0, 0, 0.1);\n  display: flex;\n  justify-content: space-between;\n  z-index: 99999;\n}\n\n.sidebar-collapsed #navbar {\n  width: 100%;\n  margin-left: 0;\n}\n\n#navbar-title>a {\n  font-weight: bold;\n  font-size: 1.2em;\n  padding: 0 4px;\n  vertical-align: middle;\n}\n\n#navbar-title>a,\n#github>a {\n  text-decoration: none;\n  color: white;\n}\n\n#navbar>#github {\n  filter: invert(1);\n}\n\n#sidebar-toggle {\n  color: white;\n  cursor: pointer;\n  padding: .1em .5em;\n  padding-bottom: 0;\n  user-select: none;\n}\n\n#github {\n  padding: 0 .5em;\n}\n\n#github img {\n  height: 1.4em;\n  width: 1.4em;\n  vertical-align: middle;\n}\n\n#noticebar {\n  padding: 1.5em;\n  padding-top: 2em;\n  padding-bottom: 1em;\n  margin-bottom: -1.5em;\n  border-bottom: .1em solid rgba(0, 0, 0, 0.1);\n}\n\n#noticebar>* {\n  max-width: 46rem;\n  margin-left: auto;\n  margin-right: auto;\n  margin-block-end: 0;\n  margin-block-start: 0.25em;\n}\n\n#sidebar {\n  height: 100%;\n  position: fixed;\n  display: flex;\n  flex-direction: column;\n  width: 240px;\n  border-right: .1em solid rgba(0, 0, 0, 0.1);\n  background-color: #fafafa;\n  z-index: 99999;\n  overflow-y: auto;\n}\n\n.sidebar-collapsed #sidebar {\n  display: none;\n}\n\n#sidebar-logo {\n  padding: 16px 16px;\n  padding-bottom: 10px;\n}\n\n#sidebar-title {\n  display: none;\n  margin-top: -16px;\n  padding-right: 37px;\n  margin-bottom: 10px;\n  text-align: center;\n  text-decoration: none;\n  font-size: 24px;\n  font-weight: bold;\n}\n\n#sidebar li.active-entry :first-child,\n#sidebar li.active :first-child {\n  font-weight: bold;\n}\n\n#sidebar li.active-entry :first-child {\n  color: black;\n}\n\n/* consistent link color */\na {\n  color: #326be5;\n}\n\np {\n  margin-block-start: .5em;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5 {\n  margin-block-start: 1rem;\n  margin-block-end: .25rem;\n}\n\n/* ensure images don't go off the page */\n#content img {\n  max-width: 100%;\n}\n\nhr {\n  background: #333;\n  border: 1px solid #333;\n}\n\n/* code block styling */\npre {\n  background-color: #f3f4f4 !important;\n  overflow-x: auto;\n  border-radius: 0;\n  padding: 1em;\n  font-size: 85%;\n}\n\ncode {\n  background-color: #f3f4f4;\n  border-radius: 3px;\n  padding: .1em .4em;\n  font-size: 85%;\n}\n\npre code {\n  background-color: transparent;\n  padding: 0;\n  font-size: 100%;\n  border-radius: 0;\n}\n\n/* inline copyable code snippet styling */\ntable.includecode {\n  max-width: 100%;\n  width: 100%;\n  table-layout: fixed;\n  border: 1px solid rgba(0, 0, 0, .125);\n  border-spacing: 0;\n}\n\ntable.includecode thead th {\n  border-bottom: 1px solid rgba(0, 0, 0, .125);\n  font-weight: normal;\n  font-size: 85%;\n}\n\ntable.includecode thead th {\n  text-align: right;\n}\n\ntable.includecode thead th a {\n  font-family: monospace;\n}\n\ntable.includecode thead th button {\n  vertical-align: middle;\n  background: transparent;\n  border: 0;\n  cursor: pointer;\n}\n\ntable.includecode tbody {\n  background: #f3f4f4;\n}\n\ntable.includecode pre {\n  margin: 0;\n}\n\n/* don't display this, these are textareas needed to copy to clipboard */\n.hidden-copy-text {\n  background: transparent;\n  width: 2em;\n  height: 2em;\n  position: fixed;\n  top: 0;\n  left: 0;\n}\n\n/* heading anchors */\n.hanchor {\n  font-size: 100%;\n  visibility: hidden;\n  font-size: .5em;\n  vertical-align: middle;\n  text-decoration: none !important;\n}\n\nh1:hover a,\nh2:hover a,\nh3:hover a,\nh4:hover a {\n  visibility: visible;\n}\n\n#content #TableOfContents ul {\n  margin-block-start: 0;\n  margin-block-end: 0;\n}\n\n#content ul {\n  padding-left: 1.75em;\n  margin-block-start: .5em;\n  margin-block-end: .5em;\n}\n\n#content ul ul {\n  padding-left: 1em;\n  margin-block-start: 0;\n}\n\n#sidebar ul {\n  margin-bottom: 1em;\n}\n\n#sidebar ul ul {\n  padding-left: 1.25em;\n}\n\n.page-description {\n  border-left-color: black;\n  background: #3b4d56;\n  color: white;\n  font-size: 1.2em;\n  line-height: 1.4em;\n  margin-block-start: 0;\n  margin-block-end: 0;\n}\n\n.page-description a {\n  color: #0bd8ec;\n}\n\n.page-description code {\n  color: black;\n}\n\nblockquote {\n  border-left: .5rem solid #326ce4;\n  background: #c9e6ff;\n  color: black;\n  padding: 1em 1.5em;\n  margin-inline-start: 0;\n  margin-inline-end: 0;\n}\n\nblockquote p {\n  margin-block-start: 0.75em;\n  margin-block-end: 0.75em;\n}\n\n/* style video embeds to be full-width in content */\n.video-wrapper {\n  position: relative;\n  width: 100%;\n  height: 0;\n  padding-bottom: 56.25%;\n}\n\n.video-wrapper iframe {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n\n/* mock github labels */\n.gh-label {\n  /* mimic github */\n  display: inline-block;\n  padding: 0 7px;\n  font-size: 12px;\n  font-weight: 500;\n  line-height: 18px;\n  border: 1px solid transparent;\n  border-radius: 2em;\n  background: #bfd4f2;\n  /* additional styling to allow using on a tags */\n  text-decoration: none;\n}\n\n/* mobile */\n@media (max-width: 50em) {\n  #content {\n    font-size: 16px;\n  }\n\n  #wrapper {\n    margin-left: 0;\n  }\n\n  #wrapper #navbar {\n    margin-left: 241px;\n  }\n\n  .sidebar-collapsed #wrapper #navbar {\n    margin-left: 0;\n  }\n\n  #content ul {\n    padding-left: 1.5em;\n  }\n}\n\n:target::before {\n  content: \"\";\n  display: block;\n  height: 2em;\n  margin: -2em 0 0;\n}\n\n/* https://github.com/adityatelange/hugo-PaperMod/issues/828#issuecomment-1171994855 */\n/* Fixes iOS font sizing anomaly */\ncode {\n  text-size-adjust: 100%;\n  -ms-text-size-adjust: 100%;\n  -moz-text-size-adjust: 100%;\n  -webkit-text-size-adjust: 100%;\n}"
  },
  {
    "path": "site/assets/js/inline.js",
    "content": "/* used when sidebar is manually toggled */\nfunction toggleSidebar() {\n    if (document.body.classList.contains('sidebar-collapsed')) {\n        window.localStorage.setItem('sidebar-collapsed', 'false');\n        showSideBar();\n    } else {\n        window.localStorage.setItem('sidebar-collapsed', 'true');\n        hideSideBar();\n    }\n}\n\nfunction showSideBar() {\n    document.body.classList.remove('sidebar-collapsed');\n}\n\nfunction hideSideBar() {\n    document.body.classList.add('sidebar-collapsed');\n}\n\n/* get the page width */\nfunction getWidth() {\n    return Math.max(\n        document.body.scrollWidth,\n        document.documentElement.scrollWidth,\n        document.body.offsetWidth,\n        document.documentElement.offsetWidth,\n        document.documentElement.clientWidth\n    );\n}\n\nvar oldWidth;\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n    // note: the default state of the page on load is collapsed\n    var manualCollapsed = window.localStorage.getItem('sidebar-collapsed');\n    var width = getWidth();\n    // if we're now under 900px don't unhide it\n    // otherwise only unhide if the user has not yet manually toggled it\n    // or has toggled it back to visible\n    if (width > 900 && (manualCollapsed == 'false' || manualCollapsed == null)) {\n        showSideBar();\n    }\n    // setup listener for width change\n    oldWidth = width;\n    window.addEventListener(\"resize\", function () {\n        // bail out early if it wasn't the width that changed\n        var width = getWidth();\n        if (oldWidth == width) {\n            oldWidth = width;\n            return;\n        }\n        oldWidth = width;\n        // if we're now under 900px, hide it\n        if (width <= 900) {\n            hideSideBar();\n        } else {\n            // otherwise only unhide if the user has not yet manually toggled it\n            // or has toggled it back to visible\n            var manualCollapsed = window.localStorage.getItem('sidebar-collapsed');\n            if (manualCollapsed == 'false' || manualCollapsed == null) {\n                showSideBar();\n            }\n        }\n    });\n});\n\n/* used to copy code snippets */\nfunction copyText(elementID) {\n    /* Get the text field */\n    var elem = document.getElementById(elementID);\n\n    /* Select the text field */\n    elem.select();\n    elem.setSelectionRange(0, 99999); /*For mobile devices*/\n\n    /* Copy the text inside the text field */\n    document.execCommand(\"copy\");\n}\n"
  },
  {
    "path": "site/config.toml",
    "content": "title = \"kind\"\nbaseURL = \"https://kind.sigs.k8s.io\"\nlanguageCode = \"en-us\"\n\n# we use this to disable indexing for the non-production build\nenableRobotsTXT = true\n\n# this allows us to show the source commit in the footer\nenableGitInfo = true\n\n# we don't use these currently\ndisableKinds = [\"taxonomy\", \"taxonomyTerm\"]\n\n# syntax highlighting options\n[markup]\n[markup.highlight]\ncodeFences = true\nhl_Lines = \"\"\nlineNoStart = 1\nlineNos = false\nlineNumbersInTable = true\nnoClasses = true\nstyle = \"vs\"\ntabWidth = 4\n\n# allow html in markdown\n[markup.goldmark.renderer]\nunsafe = true\n\n# enable hugo's menu system for the site, name the primary menu\nsectionPagesMenu = \"main\"\n# menu entries\n[menu]\n[[menu.main]]\nidentifier = \"ome\"\nname = \"Home\"\ntitle = \"Home\"\nurl = \"/\"\nweight = 1\n[[menu.main]]\nidentifier = \"user\"\nname = \"User Guide\"\ntitle = \"User Guide\"\nweight = 2\n[[menu.main]]\nidentifier = \"design\"\nname = \"Design\"\ntitle = \"Design\"\nurl = \"/docs/design/\"\nweight = 5\n[[menu.main]]\nidentifier = \"contributing\"\nname = \"Contributing\"\ntitle = \"contributing\"\nurl = \"/docs/contributing/\"\nweight = 6\n\n# enable auto-generated _redirects file\n[mediaTypes.\"text/netlify\"]\ndelimiter = \"\"\n\n[outputFormats.REDIRECTS]\nmediaType = \"text/netlify\"\nbaseName = \"_redirects\"\n\n[outputs]\nhome = [\"HTML\", \"REDIRECTS\"]\n\n[params]\nstable = \"v0.31.0\"\n\n# privacy settings\n[privacy]\n[privacy.youtube]\n# enable the cookie-less youtube in built-in hugo shortcode\nprivacyEnhanced = true\n"
  },
  {
    "path": "site/content/_index.md",
    "content": "---\ntitle: kind\n---\n<p style=\"text-align: center; margin-top: 2em; margin-bottom: -.75em;\"><img alt=\"kind\" src=\"./logo/logo.png\" width=\"300px\" /></p>\n\n[kind] is a tool for running local Kubernetes clusters using Docker container \"nodes\".  \nkind was primarily designed for testing Kubernetes itself, but may be used for local development or CI.\n\nIf you have [go] 1.17+ and [docker], [podman] or [nerdctl] installed `go install sigs.k8s.io/kind@{{< stableVersion >}} && kind create cluster` is all you need!\n\n<img src=\"images/kind-create-cluster.png\" />\n\nkind consists of:\n\n- Go [packages][packages] implementing [cluster creation][cluster package], [image build][build package], etc.\n- A command line interface ([`kind`][kind cli]) built on these packages.\n- Docker [image(s)][images] written to run systemd, Kubernetes, etc.\n- [`kubetest`][kubetest] integration also built on these packages (WIP)\n\nkind bootstraps each \"node\" with [kubeadm][kubeadm]. For more details see [the design documentation][design doc].\n\n**NOTE**: kind is still a work in progress, see the [1.0 roadmap].\n\n## Installation and usage\n\nFor more detailed instructions see [the user guide][user guide].\n\nYou can install kind with `go install sigs.k8s.io/kind@{{< stableVersion>}}` (for [go] [1.17+][go-supported]). This will put `kind` in\n`$(go env GOPATH)/bin`. You may need to add that directory to your `$PATH` as\nshown [here](https://golang.org/doc/code.html#GOPATH) if you encounter the error\n`kind: command not found` after installation.\n\nTo use kind, you will also need to [install docker].  \nOnce you have docker running you can create a cluster with:\n\n{{< codeFromInline lang=\"bash\" >}}\nkind create cluster\n{{< /codeFromInline >}}\n\nTo delete your cluster use:\n\n{{< codeFromInline lang=\"bash\" >}}\nkind delete cluster\n{{< /codeFromInline >}}\n\n<!--TODO(bentheelder): improve this part of the guide-->\nTo create a cluster from Kubernetes source:\n\n- ensure that Kubernetes is cloned in `$(go env GOPATH)/src/k8s.io/kubernetes`\n- build a node image and create a cluster with \n\n{{< codeFromInline lang=\"bash\" >}}\nkind build node-image\nkind create cluster --image kindest/node:latest\n{{< /codeFromInline >}}\n\nMulti-node clusters and other advanced features may be configured with a config\nfile, for more usage see [the user guide][user guide] or run `kind [command] --help`\n\n## Community\n\nPlease reach out for bugs, feature requests, and other issues!  \nThe maintainers of this project are reachable via:\n\n- [Kubernetes Slack] in the [#kind] channel\n- [filing an issue] against this repo\n- The Kubernetes [SIG-Testing Mailing List]\n\nCurrent maintainers are [@aojea] and [@BenTheElder] -- feel free to\nreach out directly if you have any questions!\n\nPull Requests are very welcome!  \nIf you're planning a new feature, please file an issue to discuss first.\n\nCheck the [issue tracker] for `help wanted` issues if you're unsure where to\nstart, or feel free to reach out to discuss. 🙂\n\nSee also: our own [contributor guide] and the Kubernetes [community page]. \n\n## Why kind?\n\n- kind supports multi-node (including HA) clusters\n- kind supports building Kubernetes release builds from source\n  - support for make / bash or docker, in addition to pre-published builds\n- kind supports Linux, macOS and Windows\n- kind is a [CNCF certified conformant Kubernetes installer](https://landscape.cncf.io/?selected=kind)\n\n### Code of conduct\n\nParticipation in the Kubernetes community is governed by the [Kubernetes Code of Conduct].\n\n<!--links-->\n[kind]: https://sigs.k8s.io/kind\n[go]: https://golang.org/\n[go-supported]: https://golang.org/doc/devel/release.html#policy\n[docker]: https://www.docker.com/\n[podman]: https://podman.io/\n[nerdctl]: https://github.com/containerd/nerdctl\n[community page]: https://kubernetes.io/community/\n[Kubernetes Code of Conduct]: https://github.com/kubernetes/community/blob/master/code-of-conduct.md\n[Go Report Card Badge]: https://goreportcard.com/badge/sigs.k8s.io/kind\n[Go Report Card]: https://goreportcard.com/report/sigs.k8s.io/kind\n[conformance tests]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md\n[packages]: https://github.com/kubernetes-sigs/kind/tree/main/pkg\n[cluster package]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/cluster\n[build package]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/build\n[kind cli]: https://github.com/kubernetes-sigs/kind/tree/main/main.go\n[images]: https://github.com/kubernetes-sigs/kind/tree/main/images\n[kubetest]: https://github.com/kubernetes/test-infra/tree/master/kubetest\n[kubeadm]: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/\n[design doc]: ./docs/design/initial\n[user guide]: ./docs/user/quick-start\n[SIG-Testing Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing\n[issue tracker]: https://github.com/kubernetes-sigs/kind/issues\n[filing an issue]: https://github.com/kubernetes-sigs/kind/issues/new\n[Kubernetes Slack]: https://slack.k8s.io/\n[#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/\n[1.0 roadmap]: /docs/contributing/1.0-roadmap\n[install docker]: https://docs.docker.com/install/\n[@BenTheElder]: https://github.com/BenTheElder\n[@aojea]: https://github.com/aojea\n[contributor guide]: /docs/contributing/getting-started\n"
  },
  {
    "path": "site/content/docs/contributing/1.0-roadmap.md",
    "content": "---\ntitle: \"1.0 Roadmap 🗺\"\nmenu:\n  main:\n    parent: \"contributing\"\n    identifier: \"1.0-roadmap\"\n    weight: 4\ntoc: true\ndescription: |-\n  New year, new roadmap 🎉\n\n  This document outlines some goals, non-goals, and future aspirations for kind\n  as a project.\n---\n## Beta Requirements\n\nTo reach \"beta\" [grade][deprecation-policy] kind needs to at minimum:\n\n- [x] Improve documentation (Though this will eternally be \"In Progress\" !)\n  - [X] create a documentation site - [#268]\n  - [x] expand examples of using kind (We can always use more, but we have more of this now)\n  - [x] cover known issues, debugging, work-arounds, etc.\n- [x] Reliably [pass][kind-conformance-dashboard] the Kubernetes [conformance tests]\n - [x] [Certify][certified] Conformance\n- [x] Support multi-node clusters - [#117]\n- [x] Support offline / air-gapped clusters\n  - [x] pre-loaded / offline CNI - [#200]\n- [x] Support mounting host directories - [#62]\n- [x] Improve Windows support\n  - [x] add Windows binaries to releases - [#155]\n  - [x] improve instructions for KUBECONFIG in particular\n- [ ] Support usage as a properly versioned, supported, and documented library. This includes following semver without every release being a major / breaking change to the API (which must be extensible without breakage), ensuring the CLI only uses a suitable public library surface, documentation and examples for the library, versioned types for public APIs (E.G. config format), etc.\n  - TODO: what exactly do we want here? Should this really be beta blocking?\n- [x] should be possible to troubleshoot kind without needing to modify kind ~or use external debugging tools~ (this should be possible now, if not perfect!)\n  - [x] consistent logging (what is logged, when should it be logged, what levels are used) (this is consistent-ish now, if not perfect)\n  - [x] errors have appropriate context (this is debatable and never perfect, but improved a lot, especially if you use `-v 1` or greater)\nfor managing clusters in test harnesses\n- [x] move API types / labels from `*.k8s.io` into [`*.x-k8s.io`][api-review-voluntary]\n- [x] Support all currently [upstream-supported Kubernetes versions][version-skew-policy]\n- [ ] come up with a plan for stable node image <-> KIND compatibility\n\n## GA Requirements\n\nTo reach \"GA\" [grade][deprecation-policy] kind needs to at minimum:\n\n- [x] Support non-AMD64 architectures (namely ARM) - [#166]\n- [ ] Automated publishing of Kubernetes release based kind \"node\" images - [#197]\n- [x] Support for runtimes other than docker/default including podman, ignite etc.\n- [x] First class support for skewed node (Kubernetes) versions (I believe this is relatively first-class now, things should work fine if you specify different node images)\n- ... TBD, more will be added here ...\n\n## Non-Goals\n\n- Supporting every possible Kubernetes configuration\n  - In order to best support offline / hermetic clusters, we will likely not\n  offer many options for CNI etc. out of the box. We may revisit this later.\n- Being \"production workload ready\" - kind is meant to be used:\n  - for testing Kubernetes itself\n  - for testing against Kubernetes (EG in CI on Travis, Circle, etc.)\n  - for \"local\" clusters on developer machines\n  - NOT to host workloads serving user traffic etc.\n- Replacing [Phippy] 🦒 -- kind isn't trying to replace all the things\nand Phippy is awesome ❤️\n\nLonger Term goals include:\n\n- Enabling a suitable local storage provider for testing applications that need\npersistent storage\n\n## Misc\n\n- [x] setup a regular Zoom meeting for the project [#244]\n- [x] achieve certified Kubernetes conformance [#245]\n\nOther goals / tasks not listed here can be found both in [the 1.0 project] and\nmore generally triaged for rough-priority in the [GitHub issues].\n\n[kind-conformance-dashboard]: https://testgrid.k8s.io/conformance-kind\n[version-skew-policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/\n[deprecation-policy]: https://kubernetes.io/docs/reference/using-api/deprecation-policy/\n[certified]: https://github.com/cncf/k8s-conformance/pull/451#issuecomment-457663982\n[conformance tests]: https://github.com/cncf/k8s-conformance\n[api-review-voluntary]: https://github.com/kubernetes/community/blob/master/sig-architecture/api-review-process.md#voluntary\n[1.0]: https://github.com/kubernetes-sigs/kind/projects/1\n[the 1.0 project]: https://github.com/kubernetes-sigs/kind/projects/1\n[GitHub issues]: https://github.com/kubernetes-sigs/kind/issues\n[#62]: https://github.com/kubernetes-sigs/kind/issues/62\n[#117]: https://github.com/kubernetes-sigs/kind/issues/117\n[#166]: https://github.com/kubernetes-sigs/kind/issues/166\n[#155]: https://github.com/kubernetes-sigs/kind/issues/155\n[#197]: https://github.com/kubernetes-sigs/kind/issues/197\n[#200]: https://github.com/kubernetes-sigs/kind/issues/200\n[#244]: https://github.com/kubernetes-sigs/kind/issues/244\n[#245]: https://github.com/kubernetes-sigs/kind/issues/245\n[#268]: https://github.com/kubernetes-sigs/kind/pull/268\n\n[Phippy]: https://phippy.io/\n"
  },
  {
    "path": "site/content/docs/contributing/development.md",
    "content": "---\ntitle: \"Development\"\nmenu:\n  main:\n    parent: \"contributing\"\n    identifier: \"development\"\n    weight: 3\ntoc: true\ndescription: |-\n  🚧  This is a work-in-progress  🚧\n\n  This page is intended to provide contributors with an introduction to developing the kind project. \n---\n\n## Overview\n\nKIND provides various utilities for development wrapped in `make`.\n\nMost scripts require more or less only `make` + `bash` on the host, and generally\nstick to POSIX utilities. Some scripts use `docker` e.g. for image building or\nto use docker images containing special tools.\n\nThis guide will introduce you to important make targets and their usage.\n\n## Building\n\n### Building `kind`\n\nInvoke `make build` and `bin/kind` will contain the freshly built `kind` binary\nupon a successful build.\n\nLike other targets, this target automatically manages the correct [`.go-version`][go-version] and doesn't require that you install any tooling (just `make` / `bash`). We accomplish this using a copy of [gimme] in `hack/third_party/gimme`.\n\n### Building The Base Image\n\n> **NOTE**: Most development should not require changes to the base image, however if your changes do, here's how to build and test it.\n\nTo build the \"base image\" for development use the `make quick` command in `images/base` directory: `make -C images/base quick`\n\nBy default, the base image will be tagged as `kindest/base:$(date +v%Y%m%d)-$(git describe --always --dirty)` format.\nIf you want to change this, you can set `TAG` environment variable.\n\n`TAG=v0.1.0 make -C images/base quick`\n\nFor \"production\" base images one of the maintainers will run `make -C images/base push` which cross-compiles for all architectures and pushes to the registry.\n\nYou generally don't need to cross build during development, and currently the cross\nbuild *must* be pushed instead of loaded locally, due to limitations in `docker buildx` (TODO: link to upstream issue).\n\nTo test out your changes take the image you built with `make quick` and use it\nas the `--base-image` flag when running `kind build node-image` / building node images. You can then create a cluster with this node image (`kind create cluster --image=kindest/node:latest`)\n\nFor \"Production\" base image updates one of the maintainers will bump `DefaultBaseImage` in `pkg/build/nodeimage/defaults.go` to point to the newly pushed image.\n\n### Building Node Images\n\nSee: [\"building images\"](/docs/user/quick-start/#building-images) in the user [quick start].\n\n## Updating Generated Code\n\nYou can regenerate checked-in generated sources with `make generate`.\nKIND does not use much generated code, but it does use a little.\nNamely kind uses [`deepcopy-gen`] to generate `DeepCopy` methods for API types.\n\nThere is also a `make update` target meant to cover all automated code generation\n+ formatting (`gofmt`).\n\n## Testing\n\n- Run `make test` to run all tests (unit + integration).\n- Run `make unit` to run only unit tests.\n- Run `make integration` to run only integration tests.\n\nLike other targets, these targets automatically manage the correct [`.go-version`][go-version] and doesn't require that you install any tooling (just `make` / `bash`).\n\n### E2E Testing\n\n🚧 More here coming soon ... 🚧\n\nTLDR: `hack/ci/e2e.sh` will run e2e tests against your local Kubernetes checkout.\n\nDepending on your changes, you may want to e2e tests.\n\nIn the future we plan to have e2e smoke tests that are cheaper / don't require\nbuilding Kubernetes.\n\n## Linting\n\nYou can run all of our lints at once with `make verify`.\n\nLints include:\n- checking that generated code is up to date\n  - you can run just this one with `hack/make-rules/verify/generated.sh`\n- [golangci-lint] with a custom config (`hack/tools/.golangci.yml`) to lint Go sources\n  - you can run just this one with `make lint`\n  - This linter is essentially an optimized combination of _many_ Go linters\n- [shellcheck] to lint our shell scripts (invoked via docker so you don't need to install it)\n  - you can run just this one with `make shellcheck`\n\n## Documentation\n\nOur docs are built with [hugo] just like [kubernetes.io](https://kubernetes.io).\n\nWe provide a Makefile for development that automatically manages a go toolchain\nand uses that to build and run hugo. You only need `make`, `bash`,\nand `curl` or `wget` installed.\n\nMarkdown content is under `site/content/` with a structure mirroring this site.\n\nStatic files are under `site/static` (e.g. images are under `site/static/images/`).\n\nFor simple content changes you can also just edit the markdown sources and send a\npull request. A build preview will be created by netlify which you can browse by\nclicking the \"details\" link next to the `deploy/netlify` status at the bottom of\nyour pull request on GitHub.\n\nThese are also predictable as `https://deploy-preview-$PR_NUMBER--k8s-kind.netlify.app/`, just replace `$PR_NUMBER` with the number of your Pull Request.\n\nFor more involved site / documentation development, you can run `make -C site serve` from the kind repo to run a local instance of the documentation, browsable at [http://localhost:1313](http://localhost:1313).\n\nThis site has a custom hugo theme under `site/layouts` & `site/assets`. It's\nmostly relatively simple but it has a few extra features:\n- The theme layout takes a `description` parameter in page [front matter]\n  - This renders the blockquote you see just below the page title, on this page with the text `This page is intended to provide contributors with an introduction to developing the kind project.`\n- We have a few useful but simple custom shortcodes\n\n### Shortcodes\n\n[Shortcodes](https://gohugo.io/content-management/shortcodes/) are a hugo feature, the kind docs use the following custom shortcodes:\n\n1. `absURL` -- When you need an absolute URL e.g. for `kubectl apply -f $URL`, this\nshortcode converts an input URL to absolute. Usage: `{{</* absURL \"some/URL\" */>}}`\n\n1. `securitygoose` -- This is a special shortcode for fun security notices. It wraps\ninner markdown content. Usage: `{{</* securitygoose */>}} Notice markdown content here {{</*/ securitygoose */>}}`\n\n1. `codeFromFile` -- Creates a nice codeblock with a copy button from a file. Usage: `{{</* codeFromFile file=\"static/examples/config-with-mounts.yaml\" lang=\"yaml\" */>}}`\n\n1. `codeFromInline` -- Creates a nice codeblock with a copy button from an inline code snippet. Usage: `{{</* codeFromInline lang=\"go\" */>}} func main() {{</*/ codeFromInline */>}}`\n\n1. `readFile` -- is used to inline file contents. Usage: `{{%/* readFile \"static/examples/ingress/contour/patch.json\" */%}}`\n\n\n## CI\n\nThe KIND project runs in / on Kubernetes' Custom CI, \"[prow]\" (prow.k8s.io).\nThis is both true for CI in the KIND repo, and in the Kubernetes repo where kind\nis used to test Kubernetes.\n\nIt's important to note that we run larger, slower Kubernetes e2e tests in the kind\nrepo to mimic what we run in the Kubernetes repo. This is because Kubernetes tests\nwith kind at HEAD to have the latest fixes for running bleeding edge Kubernetes.\nWe ensure that the tests continue to work in the kind repo before merging any code changes.\n\nWe also have some limited usage experiments with [GitHub Actions], currently\nonly for testing `podman` or `nerdctl` support in kind.\n\n### Configuration\n\nOur repo's prow configuration is at https://git.k8s.io/test-infra/config/jobs/kubernetes-sigs/kind\n\nGitHub actions are configured in `.github/workflows` in the kind repo.\n\n[gimme]: https://github.com/travis-ci/gimme\n[shellcheck]: https://shellcheck.net\n[golangci-lint]: https://github.com/golangci/golangci-lint\n[go-version]: https://sigs.k8s.io/kind/.go-version\n[quick start]: /docs/user/quick-start/\n[hugo]: https://gohugo.io\n[prow]: https://git.k8s.io/test-infra/\n[GitHub Actions]: https://github.com/features/actions\n[front matter]: https://gohugo.io/content-management/front-matter/\n"
  },
  {
    "path": "site/content/docs/contributing/getting-started.md",
    "content": "---\ntitle: \"Getting Started\"\nmenu:\n  main:\n    parent: \"contributing\"\n    identifier: \"getting started\"\n    weight: 1\ntoc: true\ndescription: |-\n  Welcome! 👋 \n\n  This guide covers how to start contributing to kind 😄\n---\n\n## 1. Familiarize Yourself With Contributing to Kubernetes Projects\n### Read the Kubernetes Community Guidelines\n\nMake sure to read you read the [Kubernetes community guidelines][community].\nIn specific, read through the [Kubernetes contributor guidelines][contributor].\n\nAdditionally, note that \n\n### Setup GitHub Account\n\nKubernetes and kind are developed on [GitHub][github] and will require\nan account to contribute.\n\n### Sign CNCF CLA\n\nThe Kubernetes project requires the [CNCF][CNCF] [CLA][CNCF-cla] be signed against\nyour GitHub account for all contributions in all subprojects.\n\nYou'll need to get the CLA signed to contribute.\n\n### Check The Kubernetes Contributor Guides\n\nYou may come back to this later, but we highly recommend reading these:\n\n- [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) \n  - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing)\n- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet)\n   - Common resources for existing developers\n\n## 2. Install Tools\n\n### Install Git\n\nOur source code is managed with [`git`][git], to develop locally you\nwill need to install `git`.\n\nYou can check if `git` is already on your system and properly installed with \nthe following command:\n\n```\ngit --version\n```\n\n### Install Docker\n\nCurrently, to create clusters you will need to install [Docker][docker].\n\nIf you haven't already, [install Docker][install docker], following the\n[official instructions][install docker].\nIf you have an existing installation, check your version and make sure you have\nthe latest Docker.\n\nTo check if `docker` has been installed:\n```\ndocker --version\n```\nThis documentation is written using Docker version 18.09.2.\n\n### Install Go (optional)\n\nKIND is written in [Go][golang], however our makefiles automatically ensure the\ncorrect version of go when building or testing.\n\nYou may still wish to install go on your machine to make it easier to integrate\ninto your editor etc. You can find the version of go we're currently using to develop kind in the [`.go-version`][go-version] file in the kind repo.\n\nInstall or upgrade [Go using the instructions for your operating system][golang].\nYou can check if Go is in your system with the following command:\n\n## 3. Read The Docs \n\nThe [design principles], [1.0 roadmap], and [initial design]\nmay be helpful to review before contributing. These docs cover some of the project\nphilosophy and direction.\n\n## 4. Reaching Out\n\nIssues are tracked on GitHub. Please check [the issue tracker][issues] to see\nif there is any existing discussion or work related to your interests.\n\nIn particular, if you're just getting started, you may want to look for issues\nlabeled <a href=\"https://github.com/kubernetes-sigs/kind/labels/good%20first%20issue\" class=\"gh-label\" style=\"background: #7057ff; color: white\">good first issue</a> or <a href=\"https://github.com/kubernetes-sigs/kind/labels/help%20wanted\" class=\"gh-label\" style=\"background: #006b75; color: white\">help wanted</a> which are standard labels in the Kubernetes\nproject.\nThe <a href=\"https://github.com/kubernetes-sigs/kind/labels/help%20wanted\" class=\"gh-label\" style=\"background: #006b75; color: white\">help wanted</a> label marks issues we're actively seeking help with while <a href=\"https://github.com/kubernetes-sigs/kind/labels/good%20first%20issue\" class=\"gh-label\" style=\"background: #7057ff; color: white\">good first issue</a> is additionally applied to a subset of issues we think will be particularly good for newcomers.\n\nIf you're interested in working on any of these, leave a comment to let us know!\n\nIf you do not see anything, please [file a new issue][file an issue].\n\n> **NOTE**: _Please_ file an enhancement / [feature request issue][fr-issue] to discuss features before filing a PR (ideally even before writing any code), we have a lot to consider with respect to our\n> existing users and future support when accepting any new feature.\n>\n> To streamline the process, please reach out and discuss the concept and design\n> / approach ASAP so the maintainers and community can get involved early.\n\nAlso -- Please reach out in general for bugs, feature requests, and other issues!  \n\nThe maintainers of this project are reachable via:\n\n- [Kubernetes Slack] in the [#kind] channel (most active, along with the community)\n- The issue tracker by [filing an issue][file an issue]\n- The Kubernetes [SIG-Testing][SIG-Testing] [Mailing List][SIG-Testing Mailing List]\n\nCurrent maintainers are [@aojea] and [@BenTheElder] -- feel free to\nreach out directly if you have any questions!\n\nSee also: the Kubernetes [community page].\n\n## 5. Next Steps\n\nOkay, so you've gotten your development environment setup, you've read all the\ncontributor guides, signed the CLA ... now what?\n\nIf you're planning to contribute code changes, you'll want to read the [development guide] next.\n\nIf you're looking to contribute documentation improvements, first: Thank you! 🎉🤗\nYou'll specifically want to see the [documentation section] of the development guide.\n\n[git]: https://git-scm.com/\n[hugo]: https://gohugo.io\n[issues]: https://github.com/kubernetes-sigs/kind/issues\n[file an issue]: https://github.com/kubernetes-sigs/kind/issues/new/choose\n[design principles]: /docs/design/principles\n[1.0 roadmap]: /docs/contributing/1.0-roadmap\n[project scope]: /docs/contributing/project-scope\n[project structure]: /docs/contributing/project-structure\n[initial design]: /docs/design/initial\n[github]: https://github.com/\n[golang]: https://golang.org/doc/install\n[docker]: https://www.docker.com/\n[install docker]: https://docs.docker.com/install/#supported-platforms\n[community]: https://github.com/kubernetes/community\n[contributor]: https://github.com/kubernetes/community/blob/master/contributors/guide/README.md\n[Kubernetes Slack]: https://slack.k8s.io/\n[#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/\n[@BenTheElder]: https://github.com/BenTheElder\n[@aojea]: https://github.com/aojea\n[community page]: https://kubernetes.io/community/\n[modules]: https://github.com/golang/go/wiki/Modules\n[SIG-Testing Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing\n[CNCF]: https://www.cncf.io/\n[CNCF-cla]: https://git.k8s.io/community/CLA.md\n[fr-issue]: https://github.com/kubernetes-sigs/kind/issues/new?labels=kind%2Ffeature&template=enhancement.md\n[SIG-Testing]: https://github.com/kubernetes/community/blob/master/sig-testing/README.md\n[go-version]: https://sigs.k8s.io/kind/.go-version\n[development guide]: /docs/contributing/development\n[documentation section]: /docs/contributing/development#documentation\n"
  },
  {
    "path": "site/content/docs/contributing/project-scope.md",
    "content": "---\ntitle: \"Project Scope\"\nmenu:\n  main:\n    parent: \"contributing\"\n    identifier: \"project-scope\"\n    weight: 2\ntoc: true\ndescription: |-\n  This document outlines some scoping and major priorities for kind.\n\n  See also: the [1.0 roadmap], and the [1.0 tracking milestone].\n\n  [1.0 roadmap]: /docs/contributing/1.0-roadmap\n  [1.0 tracking milestone]: https://github.com/kubernetes-sigs/kind/milestone/2\n---\n## Priorities (from greatest to least)\n\n### P-1: Bootstrapping the kind Project Itself\n---\n\n**Stakeholders**:\n\n- kind maintainers\n- kind contributors\n\n**Covered Work**:\n\n- Releases & tooling\n- Automated image publishing\n- Documentation bootstrapping (IE this site)\n- Enough Kubernetes testing to test kind itself (Kubernetes Conformance tests)\n- Setting up linters and other tools to verify quality\n- Setting up a recurring subproject meeting\n\n### P0: Support Testing Kubernetes\n---\n\n**Stakeholders**: \n\n- [SIG Testing][sigs]\n- [SIG Cluster-Lifecycle][sigs]\n  - the [kubeadm] subproject\n- Possibly [SIG Release][sigs] (mainly to provide easy access to alpha and beta tags)\n\n**Covered Work**:\n\n- Limited workloads / [e2e testing][e2e]\n- Cluster bring-up (IE [kubeadm])\n- Kubernetes build (and currently install, but that may be problematic for cross-platform [#166])\n- Node skew, client skew (kubectl / e2e versions)\n- Image publishing\n- Kubernetes CI tooling and [jobs][kubeadm-kind-job]\n- Most everything in the [1.0 roadmap]\n- ...\n\n### P1: Support Testing Kubernetes Applications\n---\n\n**Stakeholders**: Various projects both inside & outside the Kubernetes Org.\n\n- [cert-manager]\n- [cluster-api-provider-aws]\n- [cluster-api-provider-azure]\n- ...\n\n**Covered Work**:\n\nMost of the necessary work should be covered under \n[P1: Support Testing Kubernetes Applications](#p1-support-testing-kubernetes-applications),\nhowever there is some additional work.\n\n- Improve \"kind as a library\"\n  - better and more controllable logging\n  - generally more control over output\n  - example usage & documentation\n  - better / tighter API contracts\n- Most of the rest should be covered by improving \"kind the binary\" outlined above\n- ...\n\n### P2: Provide Cheap Bootstrap Clusters for the Cluster-API \n---\n\n**Stakeholders**:\n\n- various [cluster-api][cluster-api] [provider implementation][cluster-api provider implementations]\ndevelopers\n- various [cluster-api][cluster-api] users\n\n### P3: Extended Testing Not Covered Above\n---\n\n**Stakeholders**: \n\n- Indeterminate / many\n\nPossibly supporting various things that we cannot reasonably test today including:\n\n- \"node\" tests, e.g. reboot\n- Upgrades, downgrades\n- Anything depending on ingress\n- Anything depending on persistent storage / PVs\n- Testing the cluster-api proper with some sort of machine provisioning\n- Device plugin, e.g. GPU\n- ...\n\nSeveral of these make sense but are not possible with the current tooling and will require a reasonable amount of design and thought to do well. Some of them may not be solve-able in a good way, but are at least technologically feasible to explore.\n\n## Out of Scope\n---\n\nSome things we can likely never cover in a reasonable way:\n\n- Cloud provider / [CCM]\n- Some of the node testing (which portions exactly is currently unclear)\n- Being an alternative to \"docker compose\" etc.\n- Replacing [Phippy][phippy] ❤️ 🦒 ❤️\n- ...\n\n\n[#166]: https://github.com/kubernetes-sigs/kind/issues/166\n[1.0 roadmap]: /docs/contributing/1.0-roadmap\n[1.0 tracking milestone]: https://github.com/kubernetes-sigs/kind/milestone/2\n[phippy]: https://phippy.io\n[kubeadm]: https://github.com/kubernetes/kubeadm\n[sigs]: https://github.com/kubernetes/community/blob/master/sig-list.md\n[e2e]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-testing/e2e-tests.md\n[kubeadm-kind-job]: https://testgrid.k8s.io/sig-cluster-lifecycle-all#kubeadm-kind-master\n[cert-manager]: https://github.com/jetstack/cert-manager\n[cluster-api-provider-aws]: https://github.com/kubernetes-sigs/cluster-api-provider-aws\n[cluster-api-provider-azure]: https://github.com/kubernetes-sigs/cluster-api-provider-azure\n[cluster-api]: https://github.com/kubernetes-sigs/cluster-api\n[cluster-api provider implementations]: https://github.com/kubernetes-sigs/cluster-api#provider-implementations\n[CCM]: https://github.com/kubernetes/kubernetes/tree/master/cmd/cloud-controller-manager\n"
  },
  {
    "path": "site/content/docs/design/base-image.md",
    "content": "---\ntitle: \"Base Image\"\nmenu:\n  main:\n    parent: \"design\"\n    identifier: \"base-image\"\n---\n\nThis page used to host a doc about the initial design, this has been found confusing\nso we've updated it to clarify the current expectations. While the sources of the project\nare fully open, depending on the specifics of the node image internals is not supported.\n\nWe only support that base images will create a working node image with `kind build node-image` at the kind release they were shipped with.\n\nThe contents and implemlentation of the images are subject to change at any time\nto fix bugs, improve reliability, performance, or maintainability.\n\nDO NOT DEPEND ON THE INTERNALS OF THE BASE IMAGES.\n\nKIND provides [conformant][conformance] Kubernetes, anything else is an implementation detail.\n\nWe will not accept bugs about \"breaking changes\" to base images and you depend on the implementation details at your own peril.\n\n[conformance]: https://www.cncf.io/training/certification/software-conformance/\n"
  },
  {
    "path": "site/content/docs/design/initial.md",
    "content": "---\ntitle: \"Initial design\"\nmenu:\n  main:\n    parent: \"design\"\n    identifier: \"design-initial\"\n    weight: 2\ndescription: |-\n  This document covers some of the initial design for `kind`.\n\n  **NOTE**: Some of this is out of date relative to what is currently implemented.\n  This mostly exists for historical purposes, the [the original proposal][original proposal]\n  covers some more details.\n\n  Going forward the [design principles] may be more relevant.\n\n  [original proposal]: https://docs.google.com/document/d/1VL0shYfKl7goy5Zj4Rghpixbye4M8zs_N2gWoQTSKh0/\n  [design principles]: /docs/design/principles\n---\n## Overview\n\n`kind` or **k**ubernetes **in** **d**ocker is a suite of tooling for local \nKubernetes \"clusters\" where each \"node\" is a Docker container.\n`kind` is targeted at testing Kubernetes.\n\n`kind` is divided into go packages implementing most of the functionality, a\ncommand line for users, and a \"node\" base image. The intent is that the `kind`\nsuite of packages should eventually be importable and reusable by other\ntools (e.g. [kubetest][kubetest])\nwhile the CLI provides a quick way to use and debug these packages.\n\nFor [the original proposal][original proposal] by [Q-Lee][q-lee] see [the kubernetes-sig-testing post][sig-testing-post] (NOTE: this document is shared with [kubernetes-sig-testing][kubernetes-sig-testing]).\n\nIn short `kind` targets local clusters for testing purposes. While not all \ntesting can be performed without \"real\" clusters in \"the cloud\" with provider \nenabled CCMs, enough can that we want something that:\n\n - runs very cheap clusters that any developer can locally replicate\n - integrates with our tooling\n - is thoroughly documented and maintainable\n - is very stable, and has extensive error handling and sanity checking\n - passes all conformance tests\n\nIn practice kind looks something like this:\n<img src=\"/docs/images/diagram.png\"/>\n\n## Clusters\n\nClusters are managed by logic in [`pkg/cluster`][pkg/cluster], which the\n`kind` cli wraps.\n\nEach \"cluster\" is identified by an internal but well-known [docker object label](https://docs.docker.com/config/labels-custom-metadata/) key, with the cluster\nname / ID as the value on each \"node\" container.\n\nWe initially offload this type of state into the containers and Docker. \nSimilarly the container names are automatically managed by `kind`, though\nwe will select over labels instead of names because these are less brittle and\nare properly namespaced. Doing this also avoids us needing to manage anything\non the host filesystem, but should not degrade usage.\n\nThe `KUBECONFIG` will be bind-mounted to a temp directory, with the tooling \ncapable of detecting this from the containers and providing helpers to use it.\n\n## Images\n\nTo run Kubernetes in a container, we first need suitable container image(s).\nA single standard [base layer][base-image.md] is used, containing basic\nutilities like systemd, certificates, mount, etc.\n\nInstalling Kubernetes etc. is performed on top of this image, and may be cached\nin pre-built images. We expect to provide images with releases already installed\nfor use in integrating against Kubernetes.\n\nFor more see [node-image.md][node-image.md].\n\n## Cluster Lifecycle\n\n### Cluster Creation\n\nEach \"node\" runs as a docker container. Each container initially boots to a\npseudo \"paused\" state, with [the entrypoint][entrypoint] waiting for `SIGUSR1`.\nThis allows us to manipulate and inspect the container with `docker exec ...`\nand other tools prior to starting systemd and all of the components.\n\nThis setup includes fixing mounts and pre-loading saved docker images.\n\nOnce the nodes are sufficiently prepared, we signal the entrypoint to actually\n\"boot\" the node.\n\nWe then wait for the Docker service to be ready on the node before running\n`kubeadm` to initialize the node.\n\nOnce kubeadm has booted, we export the [KUBECONFIG][kubeconfig], then apply\nan [overlay network][overlay network].\n\nAt this point users can test Kubernetes by using the exported kubeconfig.\n\n\n### Cluster Deletion\n\nAll \"node\" containers in the cluster are tagged with docker labels identifying\nthe cluster by the chosen cluster name (default is \"kind\"), to delete a cluster\nwe can simply list and delete containers with this label.\n\n[kubetest]: https://github.com/kubernetes/test-infra/tree/master/kubetest\n[original proposal]: https://docs.google.com/document/d/1VL0shYfKl7goy5Zj4Rghpixbye4M8zs_N2gWoQTSKh0/\n[q-lee]: https://github.com/q-lee\n[sig-testing-post]: https://groups.google.com/d/msg/kubernetes-sig-testing/uVkosorBnVc/8DDC3qvMAwAJ\n[kubernetes-sig-testing]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing\n[pkg/cluster]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/cluster\n[base-image.md]: /docs/design/base-image\n[node-image.md]: /docs/design/node-image\n[entrypoint]: https://github.com/kubernetes-sigs/kind/blob/main/images/base/files/usr/local/bin/entrypoint\n[kubeconfig]: https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/\n[overlay network]: https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#pod-network\n[design principles]: /docs/design/principles\n"
  },
  {
    "path": "site/content/docs/design/node-image.md",
    "content": "---\ntitle: \"Node Image\"\nmenu:\n  main:\n    parent: \"design\"\n    identifier: \"node-image\"\n---\n\nThis page used to host a doc about the initial design, this has been found confusing\nso we've updated it to clarify the current expectations. While the sources of the project\nare fully open, depending on the specifics of the node image internals is not supported.\n\nWe only support that node images will create a working Kubernetes node at the advertised version with the kind version they\nwere released with (and best effort with other releases), see the release notes.\n\nThe contents and implemlentation of the images are subject to change at any time\nto fix bugs, improve reliability, performance, or maintainability.\n\nDO NOT DEPEND ON THE INTERNALS OF THE NODE IMAGES.\n\nKIND provides [conformant][conformance] Kubernetes, anything else is an implementation detail.\n\nWe will not accept bugs about \"breaking changes\" to node images and you depend on the implementation details at your own peril.\n\n[conformance]: https://www.cncf.io/training/certification/software-conformance/"
  },
  {
    "path": "site/content/docs/design/principles.md",
    "content": "---\ntitle: \"Principles\"\nmenu:\n  main:\n    parent: \"design\"\n    identifier: \"design-principles\"\n    weight: 1\ntoc: true\ndescription: |-\n  While developing kind the following principles should be considered.\n---\n## Degrade Gracefully\n\nAs much as possible kind should not fail, because it is to be used for testing.\nPartially degraded states can still be useful and still be debugged.\n\nAs a concrete example: We \"pre-load\" images that the cluster depends on by\npacking them into the \"[node image][node image]\". If these images fail to\nload or are not present in the node image, kind will fall back to letting the\n\"node\"s container runtime attempt to pull them.\n\nSimilarly we must at least support all officially supported Kubernetes releases,\nwhich may mean gracefully degrading functionality for older releases.\n\n## Target CRI Functionality\n\nCurrently kind only supports [containerd], with experimental support for [podman].\nIt uses these container runtimes directly to create \"node\" containers.\n\nWith the long term goal of [supporting multiple container runtimes] and\navoid unnecessary coupling, we try to target functionality covered by the\nKubernetes [CRI][CRI] (Container Runtime Interface).\n\n## Leverage Existing Tooling\n\nWhere possible we should _not_ reinvent the wheel.\n\nExamples include:\n\n- [kubeadm] is used to handle node configuration, certificates, etc.\n- [kustomize] is used to handle merging user provided config patches with our\ngenerated kubeadm configs\n- [k8s.io/apimachinery] is used to build our own configuration functionality\n- In general we re-use k8s.io [utility libraries][k8s.io/utils] and [generators][k8s.io/code-generator]\n\nRe-implementing some amount of functionality is expected, particularly\nbetween languages and for internal / insufficiently-generic components, but in general\nwe should collaborate where possible.\n\n## Avoid Breaking Users\n\nGoing forward kind will avoid breaking changes to the command line interface\nand configuration.\n\nNext we will extend this to a documented set of re-usable\npackages (To be determined, but likely IE [pkg/cluster]).\n\nWhile we are alpha grade currently, we will move to beta and respect\nthe [Kubernetes Deprecation Policy].\n\nExternally facing features should consider long-term supportability and\nextensibility.\n\n## Follow Kubernetes API Conventions\n\nAs a general rule of thumb kind prefers to implement configuration using\nKubernetes style configuration files.\n\nWhile doing this we should respect the Kubernetes [API Conventions].\n\nAdditionally we should minimize the number of flags used and avoid structured\nvalues in flags as these cannot be versioned. \n\n## Minimize Assumptions\n\nAvoid making any unnecessary assumptions. Currently we assume:\n\n- [docker] is installed on the host and the current user has permission to talk to dockerd\n  - Unless using experimental support for [podman] or [nerdctl].\n  - In the future we may instead only assume that a CRI is available. See [above](#target-cri-functionality).\n- \"node\" images follow our format\n  - However whenever we make changes we do not assume the updated contents definitely exist\n  - Metadata in the images is assumed to be correct\n- When building Kubernetes, we make the same assumptions & requirements as upstream\n\n## Be Hermetic\n\nAs an extension of minimizing assumptions, kind should be as hermetic as possible.\nIn other words:\n\n- Strive for reproducibility of operations\n- Avoid depending on external services, vendor / pre-pull dependencies\n\n## No External State\n\nState is offloaded into the \"node\" containers in the form of labels, files in\nthe container filesystem, and processes in the container. The cluster itself\nstores all state. No external state stores are used and the only stateful\nprocess is the container runtime. kind does not itself store or manage state.\n\nThis simplifies a lot of problems and eases portability, while forcing cluster\ninteractions to be consistent.\n\n## Consider Automation\n\nWhile kind strives to present a pleasant UX to users on their local machines,\n**automation for end to end testing is the original & primary use case**.\nAutomated usage should be considered for all features.\n\n\n[containerd]: https://containerd.io/\n[docker]: https://www.docker.com/\n[podman]: https://www.podman.io/\n[nerdctl]: https://github.com/containerd/nerdctl\n[node image]: /docs/design/node-image\n[supporting multiple container runtimes]: https://github.com/kubernetes-sigs/kind/issues/154\n[CRI]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md\n[kubeadm]: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/\n[kustomize]: https://github.com/kubernetes-sigs/kustomize\n[k8s.io/apimachinery]: https://github.com/kubernetes/apimachinery\n[Kubernetes Deprecation Policy]: https://kubernetes.io/docs/reference/using-api/deprecation-policy/\n[API Conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n[pkg/cluster]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/cluster\n[k8s.io/utils]: https://github.com/kubernetes/utils\n[k8s.io/code-generator]: https://github.com/kubernetes/code-generator\n"
  },
  {
    "path": "site/content/docs/user/auditing.md",
    "content": "---\ntitle: \"Auditing\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-auditing\"\n    weight: 4\ndescription: |-\n    This guide covers how to enable Kubernetes API [auditing] on a kind cluster.\n\n    [auditing]: https://kubernetes.io/docs/tasks/debug-application-cluster/audit/\n---\n\n## Overview\n\nKubernetes auditing provides a security-relevant, chronological set of records documenting the sequence of actions in a cluster. Auditing requires a file to define the [audit policy] and a backend configuration to store the logged events. Auditing supports two types of backends: log (file) & webhook. The following exercise uses the log backend.\n\nSteps:\n\n- Create the local audit-policy file\n- Mount the local audit-policy file into the kind control plane\n- Expose the control plane mounts to the API server\n- Enable the auditing API flags\n- Create a cluster\n\n## Setup\n\n### Create an `audit-policy.yaml` file\n\nThe [audit policy] defines the level of granularity outputted by the Kubernetes API server. The example below logs all requests at the \"Metadata\" level. See the [audit policy] docs for more examples. \n\n{{< codeFromInline lang=\"bash\" >}}\ncat <<EOF > audit-policy.yaml\napiVersion: audit.k8s.io/v1\nkind: Policy\nrules:\n- level: Metadata\nEOF\n{{< /codeFromInline >}}\n\n### Create a `kind-config.yaml` file.\n\nTo enable audit logging, use kind's [configuration file] to pass additional setup instructions. Kind uses `kubeadm` to provision the cluster and the configuration file has the ability to pass `kubeadmConfigPatches` for further customization.\n\n{{< codeFromInline lang=\"bash\" >}}\ncat <<EOF > kind-config.yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  kubeadmConfigPatches:\n  - |\n    kind: ClusterConfiguration\n    apiServer:\n        # enable auditing flags on the API server\n        extraArgs:\n          audit-log-path: /var/log/kubernetes/kube-apiserver-audit.log\n          audit-policy-file: /etc/kubernetes/policies/audit-policy.yaml\n        # mount new files / directories on the control plane\n        extraVolumes:\n          - name: audit-policies\n            hostPath: /etc/kubernetes/policies\n            mountPath: /etc/kubernetes/policies\n            readOnly: true\n            pathType: \"DirectoryOrCreate\"\n          - name: \"audit-logs\"\n            hostPath: \"/var/log/kubernetes\"\n            mountPath: \"/var/log/kubernetes\"\n            readOnly: false\n            pathType: DirectoryOrCreate\n  # mount the local file on the control plane\n  extraMounts:\n  - hostPath: ./audit-policy.yaml\n    containerPath: /etc/kubernetes/policies/audit-policy.yaml\n    readOnly: true\nEOF\n{{< /codeFromInline >}}\n\n## Launch a new cluster\n\n{{< codeFromInline lang=\"bash\" >}}\nkind create cluster --config kind-config.yaml\n{{< /codeFromInline >}}\n\n## View audit logs\n\nOnce the cluster is running, view the log files on the control plane in `/var/log/kubernetes/kube-apiserver-audit.log`.\n\n{{< codeFromInline lang=\"bash\" >}}\ndocker exec kind-control-plane cat /var/log/kubernetes/kube-apiserver-audit.log\n{{< /codeFromInline >}}\n\n## Troubleshooting\n\nIf logs are not present, let's ensure a few things are in place.\n\n### Is the local audit-policy file mounted in the control-plane?\n\n{{< codeFromInline lang=\"bash\" >}}\ndocker exec kind-control-plane ls /etc/kubernetes/policies\n{{< /codeFromInline >}}\n\nExpected output:\n\n```bash\naudit-policy.yaml\n```\n\n### Does the API server contain the mounts and arguments?\n\n{{< codeFromInline lang=\"bash\" >}}\ndocker exec kind-control-plane cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep audit\n{{< /codeFromInline >}}\n\nExpected output:\n\n```bash\n    - --audit-log-path=/var/log/kubernetes/kube-apiserver-audit.log\n    - --audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml\n      name: audit-logs\n      name: audit-policies\n    name: audit-logs\n    name: audit-policies\n```\n\nIf the control plane requires further debugging use `docker exec -it kind-control-plane bash` to start an interactive terminal session with the container.\n\n[audit policy]: https://kubernetes.io/docs/tasks/debug-application-cluster/audit/#audit-policy\n[configuration file]: /docs/user/configuration\n"
  },
  {
    "path": "site/content/docs/user/configuration.md",
    "content": "---\ntitle: \"Configuration\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-configuration\"\n    weight: 3\ntoc: true\ndescription: |-\n  This guide covers how to configure KIND cluster creation.\n  \n  We know this is currently a bit lacking and will expand it over time - PRs welcome!\n---\n## Getting Started\n\nTo configure kind cluster creation, you will need to create a [YAML] config file.\nThis file follows Kubernetes conventions for versioning etc. <!--todo links for this-->\n\nA minimal valid config is:\n\n```yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n```\n\nThis config merely specifies that we are configuring a KIND cluster (`kind: Cluster`)\nand that the version of KIND's config we are using is `v1alpha4` (`apiVersion: kind.x-k8s.io/v1alpha4`).\n\nAny given version of kind may support different versions which will have different\noptions and behavior. This is why we must always specify the version.\n\nThis mechanism is inspired by Kubernetes resources and component config.\n\nTo use this config, place the contents in a file `config.yaml` and then run\n`kind create cluster --config=config.yaml` from the same directory.\n\nYou can also include a full file path like `kind create cluster --config=/foo/bar/config.yaml`.\n\nThe structure of the `Cluster` type is defined by a Go struct, which is described\n[here](https://pkg.go.dev/sigs.k8s.io/kind/pkg/apis/config/v1alpha4#Cluster).\n\n### A Note On CLI Parameters and Configuration Files\n\nUnless otherwise noted, parameters passed to the CLI take precedence over their\nequivalents in a config file. For example, if you invoke:\n\n{{< codeFromInline lang=\"bash\" >}}\nkind create cluster --name my-cluster\n{{< /codeFromInline >}}\n\nThe name `my-cluster` will be used regardless of the presence of that value in\nyour config file.\n\n## Cluster-Wide Options\n\nThe following high level options are available.\n\nNOTE: not all options are documented yet!  We will fix this with time, PRs welcome!\n\n### Name Your Cluster\n\nYou can give your cluster a name by specifying it in your config:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nname: app-1-cluster\n{{< /codeFromInline >}}\n\n### Feature Gates\n\nKubernetes [feature gates] can be enabled cluster-wide across all Kubernetes\ncomponents with the following config:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nfeatureGates:\n  # any feature gate can be enabled here with \"Name\": true\n  # or disabled here with \"Name\": false\n  # not all feature gates are tested, however\n  \"CSIMigration\": true\n{{< /codeFromInline >}}\n\n### Runtime Config\n\nKubernetes API server runtime-config can be toggled using the `runtimeConfig`\nkey, which maps to the `--runtime-config` [kube-apiserver flag](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/).\nThis may be used to e.g. disable beta / alpha APIs, or even enable deprecated APIs.\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nruntimeConfig:\n  \"api/alpha\": \"false\"\n  \"apps/v1beta2\": \"true\"\n{{< /codeFromInline >}}\n\n### Networking\n\nMultiple details of the cluster's networking can be customized under the\n`networking` field.\n\n#### IP Family\n\nKIND has support for IPv4, IPv6 and dual-stack clusters, with the default being `ipv4`. You can change this by setting `ipFamily` under `networking` to `ipv6` or `dual`, see below for more requirements.\n\n##### IPv6 clusters\nYou can run IPv6 single-stack clusters using `kind`, if the host that runs the docker containers support IPv6.\nMost operating systems / distros have IPv6 enabled by default, but you can check on Linux with the following command:\n\n```sh\nsudo sysctl net.ipv6.conf.all.disable_ipv6\n```\n\nYou should see:\n\n```sh\nnet.ipv6.conf.all.disable_ipv6 = 0\n```\n\nIf you are using Docker on Windows or Mac, you will need to use an IPv4 port\nforward for the API Server from the host because IPv6 port forwards don't work\non these platforms, you can do this with the following config:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv6\n  apiServerAddress: 127.0.0.1\n{{< /codeFromInline >}}\n\nOn Linux all you need is:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv6\n{{< /codeFromInline >}}\n\n##### Dual Stack clusters\nYou can run dual stack clusters using `kind` 0.11+, on kubernetes versions 1.20+.\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: dual\n{{< /codeFromInline >}}\n\n#### API Server\n\nThe API Server listen address and port can be customized with:\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  # WARNING: It is _strongly_ recommended that you keep this the default\n  # (127.0.0.1) for security reasons. However it is possible to change this.\n  apiServerAddress: \"127.0.0.1\"\n  # By default the API server listens on a random open port.\n  # You may choose a specific port but probably don't need to in most cases.\n  # Using a random port makes it easier to spin up multiple clusters.\n  apiServerPort: 6443\n{{< /codeFromInline  >}}\n\n{{< securitygoose >}}**NOTE**: You should really think thrice before exposing your kind cluster publicly!\nkind does not ship with state of the art security or any update strategy (other than\ndisposing your cluster and creating a new one)! We strongly discourage exposing kind\nto anything other than loopback.{{</ securitygoose >}}\n\n#### Pod Subnet\n\nYou can configure the subnet used for pod IPs by setting\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  podSubnet: \"10.244.0.0/16\"\n{{< /codeFromInline >}}\n\nBy default, kind uses ```10.244.0.0/16``` pod subnet for IPv4 and ```fd00:10:244::/56``` pod subnet for IPv6.\n\n#### Service Subnet\n\nYou can configure the Kubernetes service subnet used for service IPs by setting\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  serviceSubnet: \"10.96.0.0/12\"\n{{< /codeFromInline >}}\n\nBy default, kind uses ```10.96.0.0/16``` service subnet for IPv4 and ```fd00:10:96::/112``` service subnet for IPv6.\n\n#### Disable Default CNI\n\nKIND ships with a simple networking implementation (\"kindnetd\") based around\nstandard CNI plugins (`ptp`, `host-local`, ...) and simple netlink routes.\n\nThis CNI also handles IP masquerade.\n\nYou may disable the default to install a different CNI. This is a power user\nfeature with limited support, but many common CNI manifests are known to work,\ne.g. Calico.\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  # the default CNI will not be installed\n  disableDefaultCNI: true\n{{< /codeFromInline >}}\n\n\n#### kube-proxy mode\n\nYou can configure the kube-proxy mode that will be used, between iptables, nftables (Kubernetes v1.31+), and ipvs.\nBy default iptables is used\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  kubeProxyMode: \"nftables\"\n{{< /codeFromInline >}}\n\nTo disable kube-proxy, set the mode to `\"none\"`.\n\n### Nodes\nThe `kind: Cluster` object has a `nodes` field containing a list of `node`\nobjects. If unset this defaults to:\n\n```yaml\nnodes:\n# one node hosting a control plane\n- role: control-plane\n```\n\nYou can create a multi node cluster with the following config:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n# One control plane node and three \"workers\".\n#\n# While these will not add more real compute capacity and\n# have limited isolation, this can be useful for testing\n# rolling updates etc.\n#\n# The API-server and other control plane components will be\n# on the control-plane node.\n#\n# You probably don't need this unless you are testing Kubernetes itself.\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\n- role: worker\n{{< /codeFromInline >}}\n\nMultiple `control-plane` nodes may be specified in order to test a \"high availability\"\ncontrol plane.\n\n## Per-Node Options\n\nThe following options are available for setting on each entry in `nodes`.\n\nNOTE: not all options are documented yet!  We will fix this with time, PRs welcome!\n\n### Kubernetes Version\n\nYou can set a specific Kubernetes version by setting the `node`'s container image. You can find available image tags on the [releases page](https://github.com/kubernetes-sigs/kind/releases). Please include the `@sha256:` [image digest](https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier) from the image in the release notes, as seen in this example:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55\n- role: worker\n  image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55\n{{< /codeFromInline >}}\n\n[Reference](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster) \n\n**Note**: Kubernetes versions are expressed as x.y.z, where x is the major version, y is the minor version, and z is the patch version, following [Semantic Versioning](https://semver.org/) terminology. For more information, see [Kubernetes Release Versioning.](https://github.com/kubernetes/sig-release/blob/master/release-engineering/versioning.md#kubernetes-release-versioning)\n\n### Extra Mounts\n\nExtra mounts can be used to pass through storage on the host to a kind node\nfor persisting data, mounting through code etc.\n\n{{< codeFromFile file=\"static/examples/config-with-mounts.yaml\" lang=\"yaml\" >}}\n\n\n**NOTE**: If you are using Docker for Mac or Windows check that the hostPath is\nincluded in the Preferences -> Resources -> File Sharing.\n\nFor more information see the [Docker file sharing guide.](https://docs.docker.com/docker-for-mac/#file-sharing)\n\n### Extra Port Mappings\n\nExtra port mappings can be used to port forward to the kind nodes. This is a \ncross-platform option to get traffic into your kind cluster. \n\nIf you are running Docker without the Docker Desktop Application on Linux, you can simply send traffic to the node IPs from the host without extra port mappings. \nWith the installation of the Docker Desktop Application, whether it is on macOs, Windows or Linux, you'll want to use these.\n\nYou may also want to see the [Ingress Guide].\n\n> **NOTE**: If you're running Kind on a remote host and need to send traffic to Kind node\n> IPs from a different host than where kind is running, you need to configure port-mapping.\n\n{{< codeFromFile file=\"static/examples/config-with-port-mapping.yaml\" lang=\"yaml\" >}}\n\nAn example http pod mapping host ports to a container port.\n\n{{< codeFromInline lang=\"yaml\">}}\nkind: Pod\napiVersion: v1\nmetadata:\n  name: foo\nspec:\n  containers:\n  - name: foo\n    image: hashicorp/http-echo:0.2.3\n    args:\n    - \"-text=foo\"\n    ports:\n    - containerPort: 5678\n      hostPort: 80\n{{< /codeFromInline >}}\n\n#### NodePort with Port Mappings\n\nTo use port mappings with `NodePort`, the kind node `containerPort` and the service `nodePort` needs to be equal.\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraPortMappings:\n  - containerPort: 30950\n    hostPort: 80\n{{< /codeFromInline >}}\n\nAnd then set `nodePort` to be 30950.\n\n{{< codeFromInline lang=\"yaml\">}}\nkind: Pod\napiVersion: v1\nmetadata:\n  name: foo\n  labels:\n    app: foo\nspec:\n  containers:\n  - name: foo\n    image: hashicorp/http-echo:0.2.3\n    args:\n    - \"-text=foo\"\n    ports:\n    - containerPort: 5678\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: foo\nspec:\n  type: NodePort\n  ports:\n  - name: http\n    nodePort: 30950\n    port: 5678\n  selector:\n    app: foo\n{{< /codeFromInline >}}\n\n[Ingress Guide]: /docs/user/ingress\n\n### Extra Labels\n\nExtra labels might be useful for working with\n[nodeSelectors](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).\n\nAn example label for specifying a `tier` label:\n\n{{< codeFromInline lang=\"yaml\">}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker\n  extraPortMappings:\n  - containerPort: 30950\n    hostPort: 80\n  labels:\n    tier: frontend\n- role: worker\n  labels:\n    tier: backend\n{{< /codeFromInline >}}\n\n### Kubeadm Config Patches\n\nKIND uses [`kubeadm`](/docs/design/principles/#leverage-existing-tooling) \nto configure cluster nodes.\n\nFormally  KIND runs `kubeadm init` on the first control-plane node, we can customize the flags by using the kubeadm\n[InitConfiguration](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/#config-file) \n([spec](https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3#InitConfiguration))\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  kubeadmConfigPatches:\n  - |\n    kind: InitConfiguration\n    nodeRegistration:\n      kubeletExtraArgs:\n        node-labels: \"my-label=true\"\n{{< /codeFromInline >}}\n\nIf you want to do more customization, there are four configuration types available during `kubeadm init`: `InitConfiguration`, `ClusterConfiguration`, `KubeProxyConfiguration`, `KubeletConfiguration`. For example, we could override the apiserver flags by using the kubeadm [ClusterConfiguration](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/control-plane-flags/) ([spec](https://pkg.go.dev/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3#ClusterConfiguration)):\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  kubeadmConfigPatches:\n  - |\n    kind: ClusterConfiguration\n    apiServer:\n        extraArgs:\n          enable-admission-plugins: NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook\n{{< /codeFromInline >}}\n\n> **NOTE**: When using `KubeletConfiguration`, kubeadm only reads the kubelet configuration from the **first** node, which will apply to all nodes.\n> This is a current [limitation](https://github.com/kubernetes-sigs/kind/issues/3849).\n\nAs a result, if you want to change the kubelet's configuration for any additional node, such as applying a taint, you must use `JoinConfiguration`:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nname: test\nnodes:\n- role: control-plane\n- role: worker\n  kubeadmConfigPatches:\n    - |\n      kind: JoinConfiguration\n      nodeRegistration:\n        kubeletExtraArgs:\n          register-with-taints: \"my-taint=presence:NoSchedule\"\n{{< /codeFromInline >}}\n\nOn every additional node configured in the KIND cluster, \nworker or control-plane (in HA mode),\nKIND runs `kubeadm join` which can be configured using the \n[JoinConfiguration](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/#config-file)\n([spec](https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3#JoinConfiguration))\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\n  kubeadmConfigPatches:\n  - |\n    kind: JoinConfiguration\n    nodeRegistration:\n      kubeletExtraArgs:\n        node-labels: \"my-label2=true\"\n- role: control-plane\n  kubeadmConfigPatches:\n  - |\n    kind: JoinConfiguration\n    nodeRegistration:\n      kubeletExtraArgs:\n        node-labels: \"my-label3=true\"\n{{< /codeFromInline >}}\n\nIf you need more control over patching, strategic merge and JSON6092 patches can\nbe used as well. These are specified using files in a directory, for example\n`./patches/kube-controller-manager.yaml` could be the following.\n\n{{< codeFromInline lang=\"yaml\" >}}\napiVersion: v1\nkind: Pod\nmetadata:\n  name: kube-controller-manager\n  namespace: kube-system\nspec:\n  containers:\n  - name: kube-controller-manager\n    env:\n    - name: KUBE_CACHE_MUTATION_DETECTOR\n      value: \"true\"\n{{< /codeFromInline >}}\n\nThen in your kind YAML configuration use the following.\n\n{{< codeFromInline lang=\"yaml\" >}}\nnodes:\n- role: control-plane\n  extraMounts:\n  - hostPath: ./patches\n    containerPath: /patches\n\nkubeadmConfigPatches:\n  - |\n    kind: InitConfiguration\n    patches:\n      directory: /patches\n{{< /codeFromInline >}}\n\nNote the `extraMounts` stanza. The node is a container created by\n`kind`. `kubeadm` is run inside this node container, and the local directory\nthat contains the patches has to be accessible to `kubeadm`. `extraMounts`\nplumbs a local directory through to this node container.\n\nThis example was for changing the manager in the control plane. To use a patch\nfor a worker node, use a `JoinConfiguration` patch and an `extraMounts` stanza\nfor the `worker` role.\n\n[YAML]: https://yaml.org/\n[feature gates]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/\n"
  },
  {
    "path": "site/content/docs/user/ingress.md",
    "content": "---\ntitle: \"Ingress\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-ingress\"\n    weight: 3\ndescription: |-\n  This guide covers setting up [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) on a kind cluster.\n---\n## Compatibility：\nThis guide applies to [cloud-provider-kind](https://github.com/kubernetes-sigs/cloud-provider-kind) v0.9.0+. For older versions, refer to historical docs.\n\n## Setting Up Ingress\n\nIngress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster.\n\nSince cloud-provider-kind v0.9.0, it natively supports Ingress. No third-party ingress controllers are required by default.\n\nFor third-party ingress solutions (e.g., Ingress NGINX, Contour), please follow their official documentation.\n\n> **NOTE**: Gateway API is also natively supported (along with Ingress). See the official [Ingress migration guide](https://gateway-api.sigs.k8s.io/guides/getting-started/migrating-from-ingress/) for details.\n\n## Create Cluster\n\n> **WARNING**: If you are using a [rootless container runtime], ensure your host is\n> properly configured before creating the KIND cluster. Most Ingress and Gateway controllers will\n> not work if these steps are skipped.\n\nCreate a kind cluster and run [Cloud Provider KIND] that automatically enables LoadBalancer support for Ingress. Create a cluster as follows.\n\n{{< codeFromInline lang=\"bash\" >}}\nkind create cluster\n{{< /codeFromInline >}}\n\n## Using Ingress\n\nThe following example creates simple http-echo services and an Ingress object to route to these services.\n\n```yaml\n{{% readFile \"static/examples/ingress/usage.yaml\" %}}\n```\n\nApply the configuration:\n\n{{< codeFromInline lang=\"bash\" >}}\nkubectl apply -f {{< absURL \"examples/ingress/usage.yaml\" >}}\n{{< /codeFromInline >}}\n\n### Verify Ingress Works\n\nCheck the External IP assigned to the Ingress by the built-in LoadBalancer.\n\n{{< codeFromInline lang=\"bash\" >}}\nkubectl get ingress\nNAME              CLASS     HOSTS         ADDRESS        PORTS   AGE\nexample-ingress   <none>    example.com   172.18.0.5     80      10m\n{{< /codeFromInline >}}\n\n{{< codeFromInline lang=\"bash\" >}}\n# get the Ingress IP\n\nINGRESS_IP=$(kubectl get ingress example-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n\n# should output \"foo-app\"\n\ncurl ${INGRESS_IP}/foo\n\n# should output \"bar-app\"\ncurl ${INGRESS_IP}/bar\n{{< /codeFromInline >}}\n\n[LoadBalancer]: /docs/user/loadbalancer/\n[Cloud Provider KIND]: /docs/user/loadbalancer/\n[rootless container runtime]: /docs/user/rootless/\n"
  },
  {
    "path": "site/content/docs/user/kind-example-config.yaml",
    "content": "# this config file contains all config fields with comments\n# NOTE: this is not a particularly useful config file\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n# patch the generated kubeadm config with some extra settings\nkubeadmConfigPatches:\n- |\n  apiVersion: kubelet.config.k8s.io/v1beta1\n  kind: KubeletConfiguration\n  evictionHard:\n    nodefs.available: \"0%\"\n# patch it further using a JSON 6902 patch\nkubeadmConfigPatchesJSON6902:\n- group: kubeadm.k8s.io\n  version: v1beta3\n  kind: ClusterConfiguration\n  patch: |\n    - op: add\n      path: /apiServer/certSANs/-\n      value: my-hostname\n# 1 control plane node and 3 workers\nnodes:\n# the control plane node config\n- role: control-plane\n# the three workers\n- role: worker\n- role: worker\n- role: worker\n"
  },
  {
    "path": "site/content/docs/user/known-issues.md",
    "content": "---\ntitle: \"Known Issues\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"known-issues\"\n    weight: 2\ndescription: |-\n  Having problems with kind? This guide covers some known problems and solutions / workarounds.\n\n  It may additionally be helpful to:\n\n  - check our [issue tracker]\n  - [file an issue][file an issue] (if there isn't one already)\n  - reach out and ask for help in [#kind] on the [kubernetes slack]\n\n  [issue tracker]: https://github.com/kubernetes-sigs/kind/issues\n  [file an issue]: https://github.com/kubernetes-sigs/kind/issues/new\n  [#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/\n  [kubernetes slack]: https://slack.k8s.io/\n---\n\n## Contents\n\n* [Troubleshooting Kind](#troubleshooting-kind)\n* [Kubectl Version Skew](#kubectl-version-skew) (Kubernetes limits supported version skew)\n* [Docker Installed With Snap](#docker-installed-with-snap) (snap filesystem restrictions problematic)\n* [Failure to Build Node Image](#failure-to-build-node-image) (usually need to increase resources)\n* [Failing to Properly Start Cluster](#failing-to-properly-start-cluster) (various causes)\n* [Pod Errors Due to \"too many open files\"](#pod-errors-due-to-too-many-open-files) (likely [inotify] limits which are not namespaced)\n* [Docker Permission Denied](#docker-permission-denied) (ensure you have permission to use docker)\n* [Windows Containers](#windows-containers) (unsupported / infeasible)\n* [Unsupported Architectures](#unsupported-architectures) (images not pre-built yet)\n* [Unable to Pull Images](#unable-to-pull-images) (various)\n* [Chrome OS](#chrome-os) (needs KubeletInUserNamespace)\n* [AppArmor](#apparmor) (may break things, consider disabling)\n* [IPv6 Port Forwarding](#ipv6-port-forwarding) (docker doesn't seem to implement this correctly)\n* [Couldn't find an alternative telinit implementation to spawn](#docker-init-daemon-config)\n* [Fedora](#fedora) (various)\n* [Failed to get rootfs info](#failed-to-get-rootfs-info--stat-failed-on-dev)\n* [Docker Desktop for macOS and Windows](#docker-desktop-for-macos-and-windows)\n* [Older Linux Distributions](#older-linux-distributions)\n* [Failure to Create Cluster on WSL2](#failure-to-create-cluster-on-wsl2)\n* [Local Subnet Clashes](#local-subnet-clashes)\n\n## Troubleshooting Kind\n\nIf the cluster fails to create, try again with the `--retain` option (preserving the failed container),\nthen run `kind export logs` to export the logs from the container to a temporary directory on the host.\n\n## Kubectl Version Skew\n\nYou may have problems interacting with your kind cluster if your client(s) are\nskewed too far from the kind node version. Kubernetes [only supports limited skew][version skew]\nbetween clients and the API server.\n\nThis is a issue that frequently occurs when running `kind` alongside Docker For Mac.\n\nThis problem is related to a bug in [docker on macOS][for-mac#3663]\n\nIf you see something like the following error message:\n\n```bash\n$ kubectl edit deploy -n kube-system kubernetes-dashboard\nerror: SchemaError(io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus): invalid object doesn't have additional properties\n```\n\nYou can check your client and server versions by running:\n{{< codeFromInline lang=\"bash\" >}}\nkubectl version\n{{< /codeFromInline >}}\n\nIf there is a mismatch between the server and client versions, you should install a newer client version.\n\nIf you are using Mac, you can install kubectl via homebrew by running:\n{{< codeFromInline lang=\"bash\" >}}\nbrew install kubernetes-cli\n{{< /codeFromInline >}}\n\nAnd overwrite the symlinks created by Docker For Mac by running:\n{{< codeFromInline lang=\"bash\" >}}\nbrew link --overwrite kubernetes-cli\n{{< /codeFromInline >}}\n\n[for-mac#3663]: https://github.com/docker/for-mac/issues/3663\n\n## Docker Installed with Snap\n\nIf you installed Docker with [snap], it is likely that `docker` commands do not\nhave access to `$TMPDIR`. This may break some kind commands which depend\non using temp directories (`kind build ...`).\n\nCurrently a workaround for this is setting the `TMPDIR` environment variable to\na directory snap does have access to when working with kind.\nThis can for example be some directory under `$HOME`.\n\n## Failure to build node image\n\nBuilding kind's node image may fail due to running out of memory on Docker for Mac or Docker for Windows.\nSee [kind#229][kind#229].\n\nIf you see something like this:\n\n```txt\n    cmd/kube-scheduler\n    cmd/kube-proxy\n/usr/local/go/pkg/tool/linux_amd64/link: signal: killed\n!!! [0116 08:30:53] Call tree:\n!!! [0116 08:30:53]  1: /go/src/k8s.io/kubernetes/hack/lib/golang.sh:614 kube::golang::build_some_binaries(...)\n!!! [0116 08:30:53]  2: /go/src/k8s.io/kubernetes/hack/lib/golang.sh:758 kube::golang::build_binaries_for_platform(...)\n!!! [0116 08:30:53]  3: hack/make-rules/build.sh:27 kube::golang::build_binaries(...)\n!!! [0116 08:30:53] Call tree:\n!!! [0116 08:30:53]  1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...)\n!!! [0116 08:30:53] Call tree:\n!!! [0116 08:30:53]  1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...)\nmake: *** [all] Error 1\nMakefile:92: recipe for target 'all' failed\n!!! [0116 08:30:54] Call tree:\n!!! [0116 08:30:54]  1: build/../build/common.sh:518 kube::build::run_build_command_ex(...)\n!!! [0116 08:30:54]  2: build/release-images.sh:38 kube::build::run_build_command(...)\nmake: *** [quick-release-images] Error 1\nERRO[08:30:54] Failed to build Kubernetes: failed to build images: exit status 2\nError: error building node image: failed to build kubernetes: failed to build images: exit status 2\nUsage:\n  kind build node-image [flags]\n\nFlags:\n      --base-image string   name:tag of the base image to use for the build (default \"kindest/base:v20181203-d055041\")\n  -h, --help                help for node-image\n      --image string        name:tag of the resulting image to be built (default \"kindest/node:latest\")\n      --type string         build type, default is docker (default \"docker\")\n\nGlobal Flags:\n  -q, --quiet             silence all stderr output\n  -v, --verbosity int32   info log verbosity, higher value produces more output\n\nerror building node image: failed to build kubernetes: failed to build images: exit status 2\n```\n\nThen you may try increasing the resource limits for the Docker engine on Mac or Windows.\n\nIt is recommended that you allocate at least 8GB of RAM to build Kubernetes.\n\nOpen the **Preferences** (macOS) or **Settings** (Windows) menu.\n\nOn macOS:\n\n<img src=\"/docs/user/images/docker-pref-1.png\" alt=\"Docker Preferences on macOS\" />\n\nOn Windows:\n\n<img src=\"/docs/user/images/docker-pref-1-win.png\" alt=\"Docker Preferences on Windows\" />\n\nGo to the **Advanced** settings page, and change the settings there, see\n[changing Docker's resource limits][Docker resource lims].\n\nOn macOS:\n\n<img width=\"400px\" src=\"/docs/user/images/docker-pref-build.png\" alt=\"Setting 8Gb of memory in Docker for Mac\" />\n\nOn Windows:\n\n<img width=\"400px\" src=\"/docs/user/images/docker-pref-build-win.png\" alt=\"Setting 8Gb of memory in Docker for Windows\" />\n\n## Failing to properly start cluster\n\nThis issue is similar to a\n[failure while building the node image](#failure-to-build-node-image).\nIf the cluster creation process was successful but you are unable to see any\nKubernetes resources running, for example:\n\n```txt\n$ docker ps\nCONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS              PORTS                      NAMES\nc0261f7512fd        kindest/node:v1.12.2   \"/usr/local/bin/entr…\"   About a minute ago   Up About a minute   0.0.0.0:64907->64907/tcp   kind-1-control-plane\n$ docker exec -it c0261f7512fd /bin/sh\n# docker ps -a\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n#\n```\n\nor `kubectl` being unable to connect to the cluster,\n\n```txt\n$ kind export kubeconfig\n$ kubectl cluster-info\n\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\nUnable to connect to the server: EOF\n```\n\nThen as in [kind#156][kind#156], you may solve this issue by claiming back some\nspace on your machine by removing unused data or images left by the Docker\nengine by running:\n{{< codeFromInline lang=\"bash\" >}}\ndocker system prune\n{{< /codeFromInline >}}\n\nAnd / or:\n{{< codeFromInline lang=\"bash\" >}}\ndocker image prune\n{{< /codeFromInline >}}\n\nYou can verify the issue by exporting the logs (`kind export logs`) and looking\nat the kubelet logs, which may have something like the following:\n\n```txt\nDec 07 00:37:53 kind-1-control-plane kubelet[688]: I1207 00:37:53.229561     688 eviction_manager.go:340] eviction manager: must evict pod(s) to reclaim ephemeral-storage\nDec 07 00:37:53 kind-1-control-plane kubelet[688]: E1207 00:37:53.229638     688 eviction_manager.go:351] eviction manager: eviction thresholds have been met, but no pods are active to evict\n```\n\n## Pod errors due to \"too many open files\"\n\nThis may be caused by running out of [inotify](https://linux.die.net/man/7/inotify) resources. Resource limits are defined by `fs.inotify.max_user_watches` and `fs.inotify.max_user_instances` system variables. For example, in Ubuntu these default to 8192 and 128 respectively, which is not enough to create a cluster with many nodes.\n\nTo increase these limits temporarily run the following commands on the host:\n{{< codeFromInline lang=\"bash\" >}}\nsudo sysctl fs.inotify.max_user_watches=524288\nsudo sysctl fs.inotify.max_user_instances=512\n{{< /codeFromInline >}}\n\nTo make the changes persistent, edit the file `/etc/sysctl.conf` and add these lines:\n{{< codeFromInline lang=\"bash\" >}}\nfs.inotify.max_user_watches = 524288\nfs.inotify.max_user_instances = 512\n{{< /codeFromInline >}}\n\n## Docker permission denied\n\nWhen using `kind`, we assume that the user you are executing kind as has permission to use docker.\nIf you initially ran Docker CLI commands using `sudo`, you may see the following error, which indicates that your `~/.docker/` directory was created with incorrect permissions due to the `sudo` commands.\n\n```txt\nWARNING: Error loading config file: /home/user/.docker/config.json\nopen /home/user/.docker/config.json: permission denied\n```\n\nTo fix this problem, either follow the docker's docs [manage docker as a non root user][manage docker as a non root user],\nor try to use `sudo` before your commands (if you get `command not found` please check [this comment about sudo with kind][sudo with kind]).\n\n## Docker init daemon config\n\nPlease make sure that when you use `kind`, you can't have `\"init\": true` in your `/etc/docker/daemon.json` because that will\ncause `/sbin/init` to show the following cryptic message *Couldn't find an alternative telinit implementation to spawn*.\nThis has to to with `/sbin/init` not running as process id 1.\n\n## Windows Containers\n\n[Docker Desktop for Windows][docker desktop for windows] supports running both Linux (the default) and Windows Docker containers.\n\n`kind` for Windows requires Linux containers. To switch between Linux and Windows containers see [this page][switch between windows and linux containers].\n\nWindows containers are not like Linux containers and do not support running docker in docker and therefore cannot support kind.\n\n## Unsupported Architectures\n\nKIND currently ships pre-built images for AMD64 and ARM64 architectures.\nIn the future we may support others, but currently demand has been low and the cost to build\nhas been high.\n\nTo use kind on other architectures, you need to first build a base image\nand then build a node image.\n\nRun `images/base/build.sh` and then taking note of the built image name use `kind build node-image --base-image=kindest/base:tag-i-built`.\n\nThere are more details about how to do this in the [Quick Start] guide.\n\n## Unable to pull images\n\nWhen using named KIND instances you may sometimes see your images failing to pull correctly on pods. This will usually manifest itself with the following output when doing a `kubectl describe pod my-pod`\n\n```txt\nFailed to pull image \"docker.io/my-custom-image:tag\": rpc error: code = Unknown desc = failed to resolve image \"docker.io/library/my-custom-image:tag\": no available registry endpoint: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed\n```\n\nIf this image has been loaded onto your kind cluster using the command `kind load docker-image my-custom-image` then you have likely not provided the name parameter.\n\nRe-run the command this time adding the `--name my-cluster-name` param:\n\n`kind load docker-image my-custom-image --name my-cluster-name`\n\n## Chrome OS\n\nTo run Kubernetes inside Chrome OS the LXC container must allow nesting. In Crosh session (ctrl+alt+t):\n\n```txt\ncrosh> vmc launch termina\n(termina) chronos@localhost ~ $ lxc config set penguin security.nesting true\n(termina) chronos@localhost ~ $ lxc restart penguin\n```\n\nThen KIND cluster must use KubeletInUserNamespace feature gate (available since Kubernetes 1.22):\n\n```yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nfeatureGates:\n  KubeletInUserNamespace: true\n```\n\n## AppArmor\n\nIf your host has [AppArmor] enabled you may run into [moby/moby/issues/7512](https://github.com/moby/moby/issues/7512#issuecomment-51845976).\n\nYou will likely need to disable apparmor on your host or at least any profile(s)\nrelated to applications you are trying to run in KIND.\n\nSee Previous Discussion: [kind#1179]\n\n## IPv6 Port Forwarding\n\nDocker assumes that all the IPv6 addresses should be reachable, hence doesn't implement\nport mapping using NAT [moby#17666].\n\nYou will likely need to use Kubernetes services like NodePort or LoadBalancer to access\nyour workloads inside the cluster via the nodes IPv6 addresses.\n\nSee Previous Discussion: [kind#1326]\n\n## Failed to get rootfs info / \"stat failed on /dev/...\"\n\nOn some systems, creating a cluster times out with these errors in kubelet.log (device varies):\n\n```txt\nstat failed on /dev/nvme0n1p3 with error: no such file or directory\n\"Failed to start ContainerManager\" err=\"failed to get rootfs info: failed to get device for dir \\\"/var/lib/kubelet\\\": could not find device with major: 0, minor: 40 in cached partitions map\"\n```\n\nKubernetes needs access to storage device nodes in order to do some stuff, e.g. tracking free disk space. Therefore, Kind needs to mount the necessary device nodes from the host into the control-plane container — however, it cannot always determine which device Kubernetes requires, since this varies with the host OS and filesystem. For example, the error above occurred with a BTRFS filesystem on Fedora Desktop 35.\n\nThis can be worked around by including the necessary device as an extra mount in the cluster configuration file.\n\n```yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraMounts:\n    - hostPath: /dev/nvme0n1p3\n      containerPath: /dev/nvme0n1p3\n      propagation: HostToContainer\n```\n\nTo identify the device that must be listed, two variations have been observed.\n\n* The device reported in the error message is a symlink (e.g. `/dev/mapper/luks-903aad3d-...`) — in this case, the config file should refer to the target of that symlink (e.g. `/dev/dm-0`).\n* The device reported in the error message is a regular block device (e.g. `/dev/nvme0n1p3`) — in this case, use the device reported.\n\nSee Previous Discussion: [kind#2411]\n\n## Fedora\n\n### Firewalld\n\nOn Fedora 32 [firewalld] moved to nftables backend by default.\nThis seems to be incompatible with Docker, leading to KIND cluster nodes not\nbeing able to reach each other.\n\nYou can work around this by changing the `FirewallBackend` in the `/etc/firewalld/firewalld.conf` file from `nftables` to `iptables` and restarting firewalld.\n\n```console\nsed -i /etc/firewalld/firewalld.conf 's/FirewallBackend=.*/FirewallBackend=iptables/'\nsystemctl restart firewalld\n```\n\nSee [#1547 (comment)](https://github.com/kubernetes-sigs/kind/issues/1547#issuecomment-623756313)\nand [Docker and Fedora 32 article](https://fedoramagazine.org/docker-and-fedora-32/)\n\n### SELinux\n\nOn Fedora 33 an update to the SELinux policy causes `kind create cluster` to fail with an error like\n\n```sh\ndocker: Error response from daemon: open /dev/dma_heap: permission denied.\n```\n\nAlthough the policy has been fixed in Fedora 34, the fix has not been backported to Fedora 33 as of June 28, 2021. Putting SELinux in permissive mode (`setenforce 0`) is one known workaround. This disables SELinux until the next boot. For more details, see [kind#2296].\n\n## Docker Desktop for macOS and Windows\n\nDocker containers cannot be executed natively on macOS and Windows, therefore\nDocker Desktop runs them in a Linux VM. As a consequence, the container networks\nare not exposed to the host and you cannot reach the kind nodes via IP.\n\nYou may be able to work around this limitation by configuring [extra port\nmappings](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings),\nleveraging [cloud-provider-kind](https://github.com/kubernetes-sigs/cloud-provider-kind),\nusing a network proxy, or other solution specific to your environment.\n\n## Older Linux Distributions\n\nKIND uses a cgroup setting of `cgroupns=private`. The cgroup namespace functionality was added in 2016, so some of the\nolder Linux distributions, using older kernels, do not have the required functionality for KIND to work. Notably, distros\nlike Red Hat Enterprise Linux 7 and its clones.\n\nAttempting to create a KIND cluster on a system with an older kernel will result in a failure, with an error message similar to:\n\n```txt\nCommand Output: WARNING: Your kernel does not support cgroup namespaces.  Cgroup namespace setting discarded.\n```\n\nUsing KIND in these environments will require upgrading your OS to a more recent version that supports cgroup namespaces.\nAnother option is to run a virtual machine using a newer kernel.\n\n## Failure to Create Cluster on WSL2\n\nSome Linux kernel options for WSL2 do not have cgroup configured in a way that\nKIND and other Linux-focused tools may expect. This may result in a failure\nmessage when attempting to create a cluster, similar to:\n\n```txt\nunable to start container process: error adding pid 655569 to cgroups\n```\n\nThe KIND development team is not able to provide support with Windows and WSL, so\nthe project relies on community support and feedback. It has been noted that the\nsteps detailed in [https://github.com/spurin/wsl-cgroupsv2](https://github.com/spurin/wsl-cgroupsv2)\nhave been necessary to resolve this issue.\n\n## Local Subnet Clashes\n\nKIND creates a separate docker network named `kind` that will be configured with default IPAM settings. If you are using the default IPAM configuration in your `daemon.json` you\nmay have conflicts with existing networks (like VPNs, labs, etc) that route the 172.17.x.x networks. To resolve this you can reconfigure the daemon-wide IPAM so that all \nnetworks will be created in subnets that do not have these conflicts.\n\nAn example configuration that you can add to your `daemon.json` is below. This would configure `10.253.0.0/16` as the defauld CIDR with each individual network receiving a /24\nsubnet to use for allocation.\n\n```json\n\"default-address-pools\": [\n  {\n    \"base\": \"10.253.0.0/16\",\n    \"size\": 24\n  }\n]\n```\n\nFor more information on the Docker Engine config file check out [these docs](https://docs.docker.com/engine/daemon/).\n\n[kind#156]: https://github.com/kubernetes-sigs/kind/issues/156\n[kind#229]: https://github.com/kubernetes-sigs/kind/issues/229\n[kind#1179]: https://github.com/kubernetes-sigs/kind/issues/1179\n[kind#1326]: https://github.com/kubernetes-sigs/kind/issues/1326\n[kind#2296]: https://github.com/kubernetes-sigs/kind/issues/2296\n[kind#2411]: https://github.com/kubernetes-sigs/kind/issues/2411\n[moby#17666]: https://github.com/moby/moby/issues/17666\n[Docker resource lims]: https://docs.docker.com/docker-for-mac/#advanced\n[snap]: https://snapcraft.io/\n[manage docker as a non root user]: https://docs.docker.com/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user\n[sudo with kind]: https://github.com/kubernetes-sigs/kind/issues/713#issuecomment-512665315\n[docker desktop for windows]: https://hub.docker.com/editions/community/docker-ce-desktop-windows\n[switch between windows and linux containers]: https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers\n[version skew]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-version-skew\n[Quick Start]: /docs/user/quick-start\n[AppArmor]: https://en.wikipedia.org/wiki/AppArmor\n[firewalld]: https://firewalld.org/\n[inotify]: https://en.wikipedia.org/wiki/Inotify\n"
  },
  {
    "path": "site/content/docs/user/loadbalancer.md",
    "content": "---\ntitle: \"LoadBalancer\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-loadbalancer\"\n    weight: 3\ndescription: |-\n    This guide covers how to get service of type LoadBalancer working in a kind cluster using [Cloud Provider KIND].\n\n    This guide complements Cloud Provider KIND [installation docs].\n\n    [Cloud Provider KIND]: https://github.com/kubernetes-sigs/cloud-provider-kind\n    [installation docs]: https://github.com/kubernetes-sigs/cloud-provider-kind?tab=readme-ov-file#install\n\n    [Ingress Guide]: /docs/user/ingress\n    [Configuration Guide]: /docs/user/configuration#extra-port-mappings\n\n---\n\n## Installing Cloud Provider KIND\n\nCloud Provider KIND can be installed using golang\n\n{{< codeFromInline lang=\"bash\" >}}\ngo install sigs.k8s.io/cloud-provider-kind@latest\n{{< /codeFromInline >}}\n\nor downloading one of the [released binaries](https://github.com/kubernetes-sigs/cloud-provider-kind/releases).\n\nCloud Provider KIND runs as a standalone binary in your host and connects to your KIND cluster and provisions new Load Balancer containers for your Services. It requires privileges to open ports on the system and to connect to the container runtime.\n\n## Using LoadBalancer\n\nThe following example creates a loadbalancer service that routes to two http-echo pods, one that outputs foo and the other outputs bar.\n\n```yaml\n{{% readFile \"static/examples/loadbalancer/usage.yaml\" %}}\n```\n\nApply the contents\n\n{{< codeFromInline lang=\"yaml\" >}}\nkubectl apply -f https://kind.sigs.k8s.io/examples/loadbalancer/usage.yaml\n{{< /codeFromInline>}}\n\nNow verify that the loadbalancer works by sending traffic to it's external IP and port.\n\n{{< codeFromInline lang=\"bash\" >}}\nLB_IP=$(kubectl get svc/foo-service -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')\n{{< /codeFromInline >}}\n\n```bash\n# should output foo and bar on separate lines \nfor _ in {1..10}; do\n  curl ${LB_IP}:5678\ndone\n```\n"
  },
  {
    "path": "site/content/docs/user/local-registry.md",
    "content": "---\ntitle: \"Local Registry\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-local-registry\"\n    weight: 3\ndescription: |-\n  This guide covers how to configure KIND with a local container image registry.\n\n  In the future this will be replaced by [a built-in feature](https://github.com/kubernetes-sigs/kind/issues/1213), and this guide will\n  cover usage instead.\n---\n## Create A Cluster And Registry\n\nThe following shell script will create a local docker registry and a kind cluster\nwith it enabled.\n\n{{< codeFromFile file=\"static/examples/kind-with-registry.sh\" >}}\n\n## Using The Registry\n\nThe registry can be used like this.\n\n1. First we'll pull an image `docker pull gcr.io/google-samples/hello-app:1.0`\n2. Then we'll tag the image to use the local registry `docker tag gcr.io/google-samples/hello-app:1.0 localhost:5001/hello-app:1.0`\n3. Then we'll push it to the registry `docker push localhost:5001/hello-app:1.0`\n4. And now we can use the image `kubectl create deployment hello-server --image=localhost:5001/hello-app:1.0`\n\nIf you build your own image and tag it like `localhost:5001/image:foo` and then use\nit in kubernetes as `localhost:5001/image:foo`. \n\nIf for some reason you have code running *inside* of a pod within the cluster that\nneeds to use this registry directly (e.g. to build and push an image) then that\ncode will need to use the `kind-registry:5000` HTTP endpoint directly, as the\ncode running inside your pod will not see the containerd config.\n\nPod manifests / pod specs / pod YAML should use `localhost:5001`,\nwhich will be rerouted to match the same name as the host via the containerd config.\n\n<!--TODO: consider a shared guide for this which we can use across the docs-->\n> **NOTE**: A bit about \"localhost\" and containers ...\n> \"localhost\" resolves to a loopback IP, which are network-namespace local.\n> Network-namespace local means that the `127.0.0.1` / `localhost` / `::1`\n> inside your container is NOT the same as the one on your host\n> (unless using `hostNetwork: true` / `--net=host`).\n>\n> KIND nodes are *not* `--net=host` and most pods are not, so really these\n> \"`localhost`s\" are local to your pod container, to your kind node, and to your\n> host machine in most cases.\n>\n> However we can tell containerd that `localhost:5001` should route to the\n> registry container such that the same name works on both the host and your machine.\n>\n> We do this so you can conveniently push and pull from a \"local\"\n> address on your host and in your pod YAML.\n"
  },
  {
    "path": "site/content/docs/user/private-registries.md",
    "content": "---\ntitle: \"Private Registries\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-private-registries\"\n    weight: 3\ntoc: true\ndescription: |-\n  This guide discusses how to use kind with image registries that\n  require authentication.\n\n  There are multiple ways to do this, which we try to cover here.\n---\n## Use ImagePullSecrets\n\nKubernetes supports configuring pods to use `imagePullSecrets` for pulling\nimages. If possible, this is the preferable and most portable route.\n\nSee [the upstream kubernetes docs for this][imagePullSecrets],\nkind does not require any special handling to use this.\n\nIf you already have the config file locally but would still like to use secrets,\nread through kubernetes' docs for [creating a secret from a file][imagePullFileSecrets].\n\n## Pull to the Host and Side-Load\n\nkind can [load an image][loading an image] from the host with the `kind load ...`\ncommands. If you configure your host with credentials to pull the desired \nimage(s) and then load them to the nodes you can avoid needing to authenticate \non the nodes.\n\n\n## Add Credentials to the Nodes\n\nGenerally the upstream docs for [using a private registry] apply, with kind\nthere are two options for this.\n\n### Mount a Config File to Each Node\n\nIf you pre-create a docker config.json containing credential(s) on the host\nyou can mount it to each kind node.\n\nAssuming your file is at `/path/to/my/secret.json`, the kind config would be:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraMounts:\n  - containerPath: /var/lib/kubelet/config.json\n    hostPath: /path/to/my/secret.json\n{{< /codeFromInline >}}\n\n#### Use an Access Token\n\nA credential can be programmatically added to the nodes at runtime.\n\nIf you do this then kubelet must be restarted on each node to pick up the new credentials.\n\nAn example shell snippet for generating a [gcr.io][GCR] cred file on your host machine\nusing Access Tokens:\n\n{{< codeFromFile file=\"static/examples/kind-gcr.sh\" >}}\n\n#### Use a Service Account\n\nAccess tokens are short lived, so you may prefer to use a Service Account and keyfile instead.\nFirst, either download the key from the console or generate one with gcloud:\n\n```\ngcloud iam service-accounts keys create <output.json> --iam-account <account email>\n```\n\nThen, replace the `gcloud auth print-access-token | ...` line from the [access token snippet](#use-an-access-token) with:\n\n```\ncat <output.json> | docker login -u _json_key --password-stdin https://gcr.io\n```\n\nSee Google's [upstream docs][keyFileAuthentication] on key file authentication for more details.\n\n[keyFileAuthentication]: https://cloud.google.com/container-registry/docs/advanced-authentication#json_key_file\n[imagePullSecrets]: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod\n[imagePullFileSecrets]: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials\n[loading an image]: /docs/user/quick-start/#loading-an-image-into-your-cluster\n[using a private registry]: https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry\n[GCR]: https://cloud.google.com/container-registry/\n\n#### Use a Certificate\n\nIf you have a registry authenticated with certificates, and both certificates and keys\nreside on your host folder, it is possible to mount and use them into the `containerd` plugin\npatching the default configuration, like in the example:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n  - role: control-plane\n    # This option mounts the host docker registry folder into\n    # the control-plane node, allowing containerd to access them. \n    extraMounts:\n      - containerPath: /etc/docker/certs.d/registry.dev.example.com\n        hostPath: /etc/docker/certs.d/registry.dev.example.com\n# NOTE: the following patch is not necessary with images from kind v0.27.0+\n# It may enable some older images to work similarly\ncontainerdConfigPatches:\n- |-\n  [plugins.\"io.containerd.grpc.v1.cri\".registry]\n    config_path = \"/etc/containerd/certs.d\"\n{{< /codeFromInline >}}"
  },
  {
    "path": "site/content/docs/user/quick-start.md",
    "content": "---\ntitle: \"Quick Start\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"user-quick-start\"\n    weight: 1\ntoc: true\ndescription: |-\n  This guide covers getting started with the `kind` command.\n\n  **If you are having problems please see the [known issues] guide.**\n\n  [known issues]: /docs/user/known-issues\n---\n## Installation\n\n> **NOTE**: `kind` does not require [`kubectl`](https://kubernetes.io/docs/reference/kubectl/overview/),\n> but you will not be able to perform some of the examples in our docs without it.\n> To install `kubectl` see the upstream [kubectl installation docs](https://kubernetes.io/docs/tasks/tools/install-kubectl/).\n\nIf you are a go developer you may find the [go install option](#installing-with-go-install) convenient.\n\nOtherwise we supply downloadable [release binaries](#installing-from-release-binaries), community-managed [packages](#installing-with-a-package-manager), and a [source installation guide](#installing-from-source).\n\nStable tagged releases (currently {{< stableVersion >}}) are generally strongly recommended for CI usage in particular.\n\nYou may need to install the latest code from source at HEAD if you are developing Kubernetes itself at HEAD / the latest sources.\n\n### Installing From Release Binaries\n\nPre-built binaries are available on our [releases page](https://github.com/kubernetes-sigs/kind/releases).\n\nTo install, download the binary for your platform from \"Assets\", then rename it to `kind` (or perhaps `kind.exe` on Windows) and place this\ninto your `$PATH` at your preferred binary installation directory.\n\nOn Linux:\n\n{{< codeFromInline lang=\"bash\" >}}\n# For AMD64 / x86_64\n[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-linux-amd64\n# For ARM64\n[ $(uname -m) = aarch64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-linux-arm64\nchmod +x ./kind\nsudo mv ./kind /usr/local/bin/kind\n{{< /codeFromInline >}}\n\nOn macOS:\n\n{{< codeFromInline lang=\"bash\" >}}\n# For Intel Macs\n[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-darwin-amd64\n# For M1 / ARM Macs\n[ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-darwin-arm64\nchmod +x ./kind\nmv ./kind /some-dir-in-your-PATH/kind\n{{< /codeFromInline >}}\n\nOn Windows in [PowerShell](https://en.wikipedia.org/wiki/PowerShell):\n\n{{< codeFromInline lang=\"powershell\" >}}\ncurl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-windows-amd64\nMove-Item .\\kind-windows-amd64.exe c:\\some-dir-in-your-PATH\\kind.exe\n{{< /codeFromInline >}}\n\n### Installing From Source\n\nIn addition to the pre-built binary + package manager installation options listed\nabove you can install kind from source with `go install sigs.k8s.io/kind@{{< stableVersion >}}` or clone this repo\nand run `make build` from the repository.\n\n#### Installing With `make`\n\nUsing `make build` does not require installing Go and will build kind reproducibly,\nthe binary will be in `bin/kind` inside your clone of the repo.\n\nYou should only need `make` and standard userspace utilities to run this build,\nit will automatically obtain the correct go version with our vendored copy of [`gimme`](https://github.com/travis-ci/gimme).\n\nYou can then call `./bin/kind` to use it, or copy `bin/kind` into some directory in your system `PATH` to\nuse it as `kind` from the command line.\n\n`make install` will attempt to mimic `go install` and has the same path requirements as `go install` below.\n\n#### Installing with `go install`\n\nWhen installing with [Go](https://golang.org/) please use the latest stable Go release. At least go1.16 or greater is required.\n\nTo install use: `go install sigs.k8s.io/kind@{{< stableVersion >}}`.\n\nIf you are building from a local source clone, use `go install .` from the top-level directory of the clone.\n\n`go install` will typically put the `kind` binary inside the `bin` directory under `go env GOPATH`, see\nGo's [\"Compile and install packages and dependencies\"](https://golang.org/cmd/go/#hdr-Compile_and_install_packages_and_dependencies)\nfor more on this.\nYou may need to add that directory to your `$PATH` if you encounter the error\n`kind: command not found` after installation, you can find a guide for adding a directory to your `PATH` at https://gist.github.com/nex3/c395b2f8fd4b02068be37c961301caa7#file-path-md.\n\n### Installing With A Package Manager\n\nThe kind community has enabled installation via the following package managers.\n\n> **NOTE**: The following are community supported efforts. The `kind` maintainers are not involved in the creation\n> of these packages, and the upstream community makes no claims on the validity, safety, or content of them.\n\nOn macOS via Homebrew:\n\n{{< codeFromInline lang=\"bash\" >}}\nbrew install kind\n{{< /codeFromInline >}}\n\nOn macOS via MacPorts:\n\n{{< codeFromInline lang=\"bash\" >}}\nsudo port selfupdate && sudo port install kind\n{{< /codeFromInline >}}\n\nOn Windows via Chocolatey (https://chocolatey.org/packages/kind)\n{{< codeFromInline lang=\"powershell\" >}}\nchoco install kind\n{{< /codeFromInline >}}\n\nOn Windows via Scoop (https://scoop.sh/#/apps?q=kind&id=faec311bb7c6b4a174169c8c02358c74a78a10c2)\n{{< codeFromInline lang=\"powershell\" >}}\nscoop bucket add main\nscoop install main/kind\n{{< /codeFromInline >}}\n\nOn Windows via Winget (https://github.com/microsoft/winget-pkgs/tree/master/manifests/k/Kubernetes/kind)\n{{< codeFromInline lang=\"powershell\" >}}\nwinget install Kubernetes.kind\n{{< /codeFromInline >}}\n\n## Creating a Cluster\n\nCreating a Kubernetes cluster is as simple as `kind create cluster`.\n\nThis will bootstrap a Kubernetes cluster using a pre-built\n[node image][node image]. Prebuilt images are hosted at[`kindest/node`][kindest/node], but to find images suitable for a given release currently you should check the [release notes] for your given kind version (check with `kind version`) where\nyou'll find a complete listing of images created for a kind release.\n\nTo specify another image use the `--image` flag -- `kind create cluster --image=...`.\n\nUsing a different image allows you to change the Kubernetes version of the created\ncluster.\n\nIf you desire to build the node image yourself with a custom version see the\n[building images](#building-images) section.\n\nBy default, the cluster will be given the name `kind`.\nUse the `--name` flag to assign the cluster a different context name.\n\nIf you want the `create cluster` command to block until the control plane\nreaches a ready status, you can use the `--wait` flag and specify a timeout.\nTo use `--wait` you must specify the units of the time to wait. For example, to\nwait for 30 seconds, do `--wait 30s`, for 5 minutes do `--wait 5m`, etc.\n\nMore usage can be discovered with `kind create cluster --help`.\n\nkind can auto-detect the [docker], [podman], or [nerdctl] installed and choose the available one. If you want to turn off the auto-detect, use the environment variable `KIND_EXPERIMENTAL_PROVIDER=docker`, `KIND_EXPERIMENTAL_PROVIDER=podman` or `KIND_EXPERIMENTAL_PROVIDER=nerdctl` to\nselect the runtime.\n\n> **NOTE**: podman and nerdctl operate in [rootless mode](/docs/user/rootless) by default. Extra\n> setup is needed for KIND clusters to be fully functional.\n\n## Interacting With Your Cluster\n\nAfter [creating a cluster](#creating-a-cluster), you can use [kubectl][kubectl]\nto interact with it by using the [configuration file generated by kind][access multiple clusters].\n\nBy default, the cluster access configuration is stored in ${HOME}/.kube/config\nif $KUBECONFIG environment variable is not set.\n\nIf $KUBECONFIG environment variable is set, then it is used as a list of paths\n(normal path delimiting rules for your system). These paths are merged. When a value\nis modified, it is modified in the file that defines the stanza. When a value is created,\nit is created in the first file that exists. If no files in the chain exist,\nthen it creates the last file in the list.\n\nYou can use the `--kubeconfig` flag when creating the cluster, then only that file is loaded.\nThe flag may only be set once and no merging takes place.\n\nTo see all the clusters you have created, you can use the `get clusters`\ncommand.\n\nFor example, let's say you create two clusters:\n```\nkind create cluster # Default cluster context name is `kind`.\n...\nkind create cluster --name kind-2\n```\n\nWhen you list your kind clusters, you will see something like the following:\n```\nkind get clusters\nkind\nkind-2\n```\n\nIn order to interact with a specific cluster, you only need to specify the\ncluster name as a context in kubectl:\n```\nkubectl cluster-info --context kind-kind\nkubectl cluster-info --context kind-kind-2\n```\n\n## Deleting a Cluster\n\nIf you created a cluster with `kind create cluster` then deleting is equally\nsimple:\n```\nkind delete cluster\n```\n\nIf the flag `--name` is not specified, kind will use the default cluster\ncontext name `kind` and delete that cluster.\n\n> **Note**: By design, requesting to delete a cluster that does not exist\n> will not return an error. This is intentional and is a means to have an\n> idempotent way of cleaning up resources.\n\n## Loading an Image Into Your Cluster\n\nYou can load one or more images into your kind cluster:\n\n```bash\nkind load docker-image my-app:latest\n```\n\n```bash\nkind load docker-image my-app:latest my-db:latest my-cache:latest\n```\n\nNote: If using a named cluster you will need to specify the name of the cluster:\n\n```bash\nkind load docker-image my-app:latest --name test-cluster\n```\n\nAdditionally, image archives can be loaded with:\n`kind load image-archive /my-image-archive.tar`\n\nThis allows a workflow like:\n```\ndocker build -t my-custom-image:unique-tag ./my-image-dir\nkind load docker-image my-custom-image:unique-tag\nkubectl apply -f my-manifest-using-my-image.yaml\n```\n\n> **NOTE**: You can get a list of images present on a cluster node by\nusing `docker exec`:\n> ```\n> docker exec -it my-node-name crictl images\n> ```\n> Where `my-node-name` is the name of the Docker container (e.g. `kind-control-plane`).\n\n> **NOTE**: The Kubernetes default pull policy is `IfNotPresent` unless\nthe image tag is `:latest` or omitted (and implicitly `:latest`) in which case the default policy is `Always`.\n`IfNotPresent` causes the Kubelet to skip pulling an image if it already exists.\n> If you want those images loaded into node to work as expected, please:\n>\n> - don't use a `:latest` tag\n>\n> and / or:\n>\n> - specify `imagePullPolicy: IfNotPresent` or `imagePullPolicy: Never` on your container(s).\n>\n> See [Kubernetes imagePullPolicy][Kubernetes imagePullPolicy] for more information.\n\n\nSee also: [Using kind with Private Registries][Private Registries].\n\n## Building Images\n\n> **NOTE**: If you're using Docker Desktop, be sure to read [Settings for Docker Desktop](#settings-for-docker-desktop) first.\n\nkind runs a local Kubernetes cluster by using Docker containers as \"nodes\".\nkind uses the [`node-image`][node image] to run Kubernetes artifacts, such\nas `kubeadm` or `kubelet`.\nThe `node-image` in turn is built off the [`base-image`][base image], which\ninstalls all the dependencies needed for Docker and Kubernetes to run in a\ncontainer.\n\nCurrently, kind supports one default way to build a `node-image`\nif you have the [Kubernetes][kubernetes] source in your host machine\n(`$GOPATH/src/k8s.io/kubernetes`), by using `source`.\n\nYou can also specify a different path to kubernetes source using \n```\nkind build node-image /path/to/kubernetes/source\n```\n\n> **NOTE**: Building Kubernetes node-images requires everything building upstream\n> Kubernetes requires, we wrap the upstream build. This includes Docker with buildx.\n> See: https://git.k8s.io/community/contributors/devel/development.md#building-kubernetes-with-docker\n\nOne shortcut to use a kubernetes release is to specify the version\ndirectly to pick up the official tar-gzipped files:\n```\nkind build node-image v1.30.0\n```\n\nIf you prefer to use existing tar-gzipped files like the ones from the kubernetes\nrelease, you can specify those as well from a URL or local directory, for example:\n```\nkind build node-image https://dl.k8s.io/v1.30.0/kubernetes-server-linux-arm64.tar.gz\nkind build node-image $HOME/Downloads/kubernetes-server-linux-amd64.tar.gz\n```\n\nTo clear any confusion, you can specify the type of build explicitly\nusing `--type` parameter, please see the following examples:\n```\nkind build node-image --type url https://dl.k8s.io/v1.30.0/kubernetes-server-linux-arm64.tar.gz\nkind build node-image --type file $HOME/Downloads/kubernetes-server-linux-amd64.tar.gz\nkind build node-image --type release v1.30.0\nkind build node-image --type source $HOME/go/src/k8s.io/kubernetes/\n```\n> **NOTE**: modes other than source directory namely `url`, `file` and `release` are only\n> available in kind v0.24 and above.\n\n### Settings for Docker Desktop\n\nIf you are building Kubernetes (for example - `kind build node-image`) on MacOS or Windows then you need a minimum of 6GB of RAM\ndedicated to the virtual machine (VM) running the Docker engine. 8GB is recommended.\n\nTo change the resource limits for the Docker on Mac, you'll need to open the\n**Preferences** menu.\n\n<img src=\"/docs/user/images/docker-pref-1.png\"/>\n\nNow, go to the **Advanced** settings page, and change the\nsettings there, see [changing Docker's resource limits][Docker resource lims].\n\n<img src=\"/docs/user/images/docker-pref-build.png\" alt=\"Setting 8Gb of memory in Docker for Mac\" />\n\nTo change the resource limits for the Docker on Windows, you'll need to right-click the Moby\nicon on the taskbar, and choose \"Settings\". If you see \"Switch to Linux Containers\", then you'll need\nto do that first before opening \"Settings\"\n\n<img src=\"/docs/user/images/docker-pref-1-win.png\"/>\n\nNow, go to the **Advanced** settings page, and change the\nsettings there, see [changing Docker's resource limits][Docker resource lims].\n\n<img src=\"/docs/user/images/docker-pref-build-win.png\" alt=\"Setting 8Gb of memory in Docker for Windows\" />\n\n\nYou may also try removing any unused data left by the Docker engine - e.g.,\n`docker system prune`.\n\n## Advanced\n\n\n### Configuring Your kind Cluster\n\nFor a sample kind configuration file see [kind-example-config][kind-example-config].\nTo specify a configuration file when creating a cluster, use the `--config`\nflag:\n\n```\nkind create cluster --config kind-example-config.yaml\n```\n\n#### Multi-node clusters\n\nIn particular, many users may be interested in multi-node clusters. A simple\nconfiguration for this can be achieved with the following config file contents:\n```yaml\n# three node (two workers) cluster config\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: worker\n- role: worker\n```\n\n#### Control-plane HA\nYou can also have a cluster with multiple control-plane nodes:\n```yaml\n# a cluster with 3 control-plane nodes and 3 workers\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n- role: control-plane\n- role: control-plane\n- role: worker\n- role: worker\n- role: worker\n```\n\n#### Mapping ports to the host machine\nYou can map extra ports from the nodes to the host machine with `extraPortMappings`:\n```yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraPortMappings:\n  - containerPort: 80\n    hostPort: 80\n    listenAddress: \"0.0.0.0\" # Optional, defaults to \"0.0.0.0\"\n    protocol: udp # Optional, defaults to tcp\n```\nThis can be useful if using `NodePort` services or daemonsets exposing host ports.\n\nNote: binding the `listenAddress` to `127.0.0.1` may affect your ability to access the service.\n\nYou may want to see the [Ingress Guide] and [LoadBalancer Guide].\n\n[Ingress Guide]: /docs/user/ingress\n[LoadBalancer Guide]: /docs/user/loadbalancer\n\n#### Setting Kubernetes version\nYou can also set a specific Kubernetes version by setting the `node`'s container image. You can find available image tags on the [releases page](https://github.com/kubernetes-sigs/kind/releases). Please use the `sha256` shasum for your desired kubernetes version, as seen in this example:\n\n```yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55\n- role: worker\n  image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55\n```\n\n### Enable Feature Gates in Your Cluster\n\nFeature gates are a set of key=value pairs that describe alpha or experimental features. In order to enable a gate you have to [customize your kubeadm configuration][customize control plane with kubeadm], and it will depend on what gate and component you want to enable. An example kind config can be:\n\n{{< codeFromInline lang=\"yaml\" >}}\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nfeatureGates:\n  FeatureGateName: true\n{{< /codeFromInline >}}\n\n### Configure kind to use a proxy\nIf you are running kind in an environment that requires a proxy, you may need to configure kind to use it.\n\nYou can configure kind to use a proxy using one or more of the following [environment variables][proxy environment variables] (uppercase takes precedence):\n\n* `HTTP_PROXY` or `http_proxy`\n* `HTTPS_PROXY` or `https_proxy`\n* `NO_PROXY` or `no_proxy`\n\n> **NOTE**: If you set a proxy it would be passed along to everything in the kind nodes. `kind` will automatically append certain addresses into `NO_PROXY` before passing it to the nodes so that Kubernetes components connect to each other directly, but you may need to configure\n> additional addresses depending on your usage.\n\n### Exporting Cluster Logs\nkind has the ability to export all kind related logs for you to explore.\nTo export all logs from the default cluster (context name `kind`):\n```\nkind export logs\nExported logs to: /tmp/396758314\n```\n\nLike all other commands, if you want to perform the action on a cluster with a\ndifferent context name use the `--name` flag.\n\nAs you can see, kind placed all the logs for the cluster `kind` in a\ntemporary directory. If you want to specify a location then simply add the path\nto the directory after the command:\n```\nkind export logs ./somedir\nExported logs to: ./somedir\n```\n\nThe structure of the logs will look more or less like this:\n```\n.\n├── docker-info.txt\n└── kind-control-plane/\n    ├── containers\n    ├── docker.log\n    ├── inspect.json\n    ├── journal.log\n    ├── kubelet.log\n    ├── kubernetes-version.txt\n    └── pods/\n```\nThe logs contain information about the Docker host, the containers running\nkind, the Kubernetes cluster itself, etc.\n\n[modules]: https://github.com/golang/go/wiki/Modules\n[go-supported]: https://golang.org/doc/devel/release.html#policy\n[docker]: https://www.docker.com/\n[podman]: https://podman.io/\n[nerdctl]: https://github.com/containerd/nerdctl\n[known issues]: /docs/user/known-issues\n[releases]: https://github.com/kubernetes-sigs/kind/releases\n[node image]: /docs/design/node-image\n[base image]: /docs/design/base-image\n[kind-example-config]: https://raw.githubusercontent.com/kubernetes-sigs/kind/main/site/content/docs/user/kind-example-config.yaml\n[kubernetes]: https://github.com/kubernetes/kubernetes\n[kindest/node]: https://hub.docker.com/r/kindest/node/\n[kubectl]: https://kubernetes.io/docs/reference/kubectl/overview/\n[Docker resource lims]: https://docs.docker.com/docker-for-mac/#advanced\n[install docker]: https://docs.docker.com/install/\n[proxy environment variables]: https://docs.docker.com/network/proxy/#use-environment-variables\n[CGO]: https://golang.org/cmd/cgo/\n[Kubernetes imagePullPolicy]: https://kubernetes.io/docs/concepts/containers/images/#updating-images\n[Private Registries]: /docs/user/private-registries\n[customize control plane with kubeadm]: https://kubernetes.io/docs/setup/independent/control-plane-flags/\n[access multiple clusters]: https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/\n[release notes]: https://github.com/kubernetes-sigs/kind/releases\n"
  },
  {
    "path": "site/content/docs/user/resources.md",
    "content": "---\ntitle: \"Resources\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"resources\"\n    weight: 4\ntoc: true\ndescription: |-\n  This page contains references to additional external resources for learning about KIND and how to use it.\n---\n## Using KIND in CI\n\nThe [kind-ci/examples] project is a work-in-progress project to give working\nexamples of using KIND in various continuous integration environments / platforms.\n\n## Tutorials and Guides\n\nHere are a useful external guides / tutorials covering things not yet covered in our docs:\n\n<!--please do not insert every single guide on the internet here-->\n<!--ideally many of these guides will eventually have upstream equivalents on this site-->\n<!--in the meantime, content that is not covered upstream in particular can be very helpful here-->\n\n### How to use KIND with MetallLB\n\n> **NOTE**: We now have a kind integrated loadbalancer solution, see [the loadbalancer page](/docs/user/loadbalancer/).\n\nhttps://mauilion.dev/posts/kind-metallb/\n\n### How to Test a Kubernetes PR with KIND\n\nhttps://mauilion.dev/posts/kind-k8s-testing/\n\n### Using Contour Ingress with KIND\n\nhttps://projectcontour.io/kindly-running-contour/\n\n### Local Ingress Domains for your Kind Cluster\n\nhttps://mjpitz.com/blog/2020/10/21/local-ingress-domains-kind/\n\n### Connect directly to Docker-for-Mac containers via IP address\n\nhttps://golangexample.com/connect-directly-to-docker-for-mac-containers-via-ip-address/\n\n### Developing for Kubernetes with KinD\n\nhttps://docs.gitlab.com/charts/development/kind/\n\n### Using CRI-O with KIND\n\n> **NOTE**: Depending on implementation details of the node image is not supported, only that the node images contain what kind needs to run Kubernetes at a given version.\n>\n> Installing CRI-O is possible but not supported.\n\nhttps://github.com/cri-o/cri-o/blob/release-1.31/tutorials/crio-in-kind.md#cri-o-in-kind\n\n## KubeCon Talks\n\nThe authors have given the following talks relating to KIND:\n\n### Keep Calm and Load Balance on KIND - Benjamin Elder & Antonio Ojea\n\nAt KubeCon EU 2024 we spoke about [Cloud Provider KIND and how to use Load Balancers Services with KIND](https://sched.co/1YhhY)\n\n{{< youtube id=\"U6_-y24rJnI\" class=\"video-wrapper\" >}}\n\n### Deep Dive: KIND - Benjamin Elder & Antonio Ojea\n\nAt KubeCon US 2019 we spoke about [KIND internals and the challenges ahead on the road to 1.0][kind-deep-dive].\n\n{{< youtube id=\"tT-GiZAr6eQ\" class=\"video-wrapper\" >}}\n\n### A Kind Workflow for Contributing to Kubernetes - Benjamin Elder & Duffie Cooley & James Munnelly & Patrick Lang\n\nAt KubeCon US 2019 we provided a hands on tutorial [for contributing and testing your Kubernetes code with KIND][kind-workflow-for-contributing-to-kubernetes].\n\n{{< youtube id=\"BPVO2mcfjJk\" class=\"video-wrapper\" >}}\n\n### Testing your K8s apps with KIND - Benjamin Elder & James Munnelly\n\nAt KubeCon EU 2019 we spoke about [KIND and testing your Kubernetes Applications][testing-k8s-apps-with-kind].\n\n{{< youtube id=\"8KtmevMFfxA\" class=\"video-wrapper\" >}}\n\n### Deep Dive: Testing SIG - Benjamin Elder & James Munnelly\n\nAt KubeCon EU 2019 we spoke about KIND and how we use it to test Kubernetes for the [SIG Testing Deep Dive][sig-testing-deep-dive-kind].\n\n{{< youtube id=\"6m9frvTxK0o\" class=\"video-wrapper\" >}}\n\n### Behind Your PR: How Kubernetes Uses Kubernetes to Run Kubernetes CI - Sen Lu & Benjamin Elder\n\nAt KubeCon NA 2018 we spoke with [Sen Lu][@krzyzacy] about The Kubernetes Project's\ntesting tools and infrastructure, including a brief discussion of KIND and running\nit on Kubernetes's Kubernetes-based CI infrastructure.\n\n{{< youtube id=\"pz0lpl6h-Gc\" class=\"video-wrapper\" >}}\n\n\n[@krzyzacy]: https://github.com/krzyzacy\n[kind-ci/examples]: https://github.com/kind-ci/examples\n[testing-k8s-apps-with-kind]: https://kccnceu19.sched.com/event/MPYy/testing-your-k8s-apps-with-kind-benjamin-elder-google-james-munnelly-jetstackio\n[sig-testing-deep-dive-kind]: https://kccnceu19.sched.com/event/MPkC/deep-dive-testing-sig-benjamin-elder-google-james-munnelly-jetstack\n[kind-deep-dive]: https://kccncna19.sched.com/event/Uah7/deep-dive-kind-benjamin-elder-google-antonio-ojea-garcia-suse\n[kind-workflow-for-contributing-to-kubernetes]: https://kccncna19.sched.com/event/Uaek/tutorial-a-kind-workflow-for-contributing-to-kubernetes-benjamin-elder-google-duffie-cooley-vmware-james-munnelly-jetstack-patrick-lang-microsoft-limited-available-seating-first-come-first-served-basis\n"
  },
  {
    "path": "site/content/docs/user/rootless.md",
    "content": "---\ntitle: \"Rootless\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"rootless\"\n    weight: 3\n---\nStarting with kind 0.11.0, [Rootless Docker](https://docs.docker.com/go/rootless/), [Rootless Podman](https://github.com/containers/podman/blob/master/docs/tutorials/rootless_tutorial.md) and [Rootless nerdctl](https://github.com/containerd/nerdctl/blob/main/docs/rootless.md) can be used as the node provider of kind.\n\n## Provider requirements\n\n- Docker: 20.10 or later\n- Podman: 3.0 or later\n- nerdctl: 1.7 or later\n\n## Host requirements\n\n### cgroup v2\n\nThe host needs to be running with cgroup v2, which is the default for many Linux disributions:\n\n- Ubuntu: 21.10 and later.\n- Fedora: 31 and later.\n- Arch: April 2021 release and later.\n\nYou can verify the cgroup version used by your controller runtime with the following procedure:\n\n- `docker`: Run `docker info` and look for `Cgroup Version: 2` in the output.\n- `podman`: Run `podman info` and look for `cgroupVersion: v2` in the output.\n- `nerdctl`: Run `nerdctl info` and look for `Cgroup Version: 2` in the output.\n\nIf the `info` output prints `Cgroup Version: 1` or equivalent, try the following to enable cgroup v2:\n\n1. In `/etc/default/grub`, add the line `GRUB_CMDLINE_LINUX=\"systemd.unified_cgroup_hierarchy=1\"`\n2. Run `sudo update-grub` to enable cgroup v2.\n\nYour host will also need to enable [cgroup delegation](https://systemd.io/CGROUP_DELEGATION/) of the `cpu` controller for\nuser services. This is enabled by default for distributions running `systemd` version 252 and higher.\n\nTo enable cgroup delegation for all the controllers, do the following:\n\n1. Check your version of `systemd` by running `systemctl --version`. If the output prints\n   `systemd 252` or higher, no further action is needed. Example output below from a Fedora host:\n\n   ```sh\n   $ systemctl --version\n   systemd 257 (257.9-2.fc42)\n   ```\n\n2. For systems with older versions of `systemd`, first create the directory\n   `/etc/systemd/system/user@.service.d/` if it is not present.\n\n   ```sh\n   sudo mkdir -p /etc/systemd/system/user@.service.d/\n   ```\n\n3. Next, create the file `/etc/systemd/system/user@.service.d/delegate.conf` with the following content:\n\n   ```ini\n   [Service]\n   Delegate=yes\n   ```\n\n4. Reload systemd for these changes to take effect:\n\n   ```sh\n   sudo systemctl daemon-reload\n   ```\n\n5. If using docker, reload the user docker daemon:\n\n   ```sh\n   systemctl --user restart docker\n   ```\n\n### Networking\n\nContainers running in rootless mode may not loaded with host-level iptable modules.\nThis breaks the behavior of most networking components, such as Ingress and Gateway controllers.\n\nTo load the iptable modules, do the following:\n\n1. First, use `lsmod` to check which kernel modules are loaded by default for user processes on\n   your system. Use `grep` to find which iptable modules are loaded:\n\n   ```sh\n   lsmod | grep \"ip.*table\"\n   ```\n\n2. Check the output for the following kernel modules:\n   - `ip6_tables`\n   - `ip6table_nat`\n   - `ip_tables`\n   - `iptable_nat`\n\n3. If one or more of the kernel modules above are not present, your system needs to load these at\n   startup for each process. First, run the following command to add these missing modules:\n   \n   ```sh\n   sudo tee /etc/modules-load.d/iptables.conf > /dev/null <<'EOF'\n   ip6_tables\n   ip6table_nat\n   ip_tables\n   iptable_nat\n   EOF\n   ```\n\n4. Check that the new module loading configuration is correct. You should see the following output:\n\n   ```sh\n   $ cat /etc/modules-load.d/iptables.conf \n   ip6_tables\n   ip6table_nat\n   ip_tables\n   iptable_nat\n   ```\n\n5. Next, restart the `systemd-modules-load` service to make these changes effective immediately:\n\n   ```sh\n   sudo systemctl restart systemd-modules-load.service\n   ```\n\nAlternatively, restart your system to ensure these changes take effect.\n\n### Increase PID Limits\n\nKIND nodes are represented as individual containers on their hosts. Runtimes such as podman set\ndefault [process id limits](https://docs.podman.io/en/v4.3/markdown/options/pids-limit.html#pids-limit-limit)\nthat may be too low for the node or for a pod running on the node. The Ingress NGINX Controller is\n[particularly susceptible](https://github.com/kubernetes-sigs/kind/issues/3451) to this issue.\n\nTo increase the PID limit, do the following:\n\n1. If using podman, edit your `containers.conf` file (generally located in\n   `/etc/containers/containers.conf` or `~/.config/containers/containers.conf`) to increase the PIDs\n   limit to a desired value (default 4096 on most systems):\n\n    ```ini\n    [containers]\n    pids_limit = 65536\n    ```\n\n2. Re-recreate the KIND cluster for these changes to take effect:\n\n   ```sh\n   kind delete cluster && kind create cluster\n   ```\n\n### Increase inotify Limits\n\nAs documented in [known issues](/docs/user/known-issues/#pod-errors-due-to-too-many-open-files), pods may\nfail by reaching inotify watch and instance limits. Ingress controllers such as NGINX and Contour\nare particularly susceptible to this issue.\n\nTo increase the inotify limits, do the following:\n\n1. As root, create a `.conf` file in `/etc/systctl.d` that increases the `fs.inotify` max user settings:\n\n   ```\n   fs.inotify.max_user_watches = 524288\n   fs.inotify.max_user_instances = 512\n   ```\n\n2. Reload `sysctl` for these changes to take effect:\n\n   ```sh\n   sudo sysctl --system\n   ```\n\nAlternatively, restart your system for these changes to take effect.\n\n### Allow Binding to Privileged Ports\n\nIf you use the `extraPortMappings` method to provide ingress to your KIND cluster, you can allow\nthe KIND node container to bind to ports 80 and 443 on the host. User containers cannot bind to\nports below 1024 by default as they are considered privileged.\n\nYou can avoid this issue by binding the node to a non-privileged host port, such as 8080 or 8443:\n\n```yaml\n# kind config.yaml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraPortMappings:\n  - containerPort: 80\n    hostPort: 8080\n    protocol: TCP\n  - containerPort: 443\n    hostPort: 8443\n    protocol: TCP\n```\n\nNote that with this configuration, requests to your cluster ingress will need to add the\nappropriate port number. In the example above, HTTP requests must use `localhost:8080` in the URL.\n\nTo allow a KIND node to bind to ports 80 and/or 443 on the host, do the following:\n\n1. As root, create a `.conf` file in `/etc/systctl.d` that lowers the privileged port start number:\n\n   ```\n   # Allow unprivileged binding to HTTP port 80\n   # Use 443 if you only need binding to the default HTTPS port\n   net.ipv4.ip_unprivileged_port_start=80\n   ```\n\n2. Reload `sysctl` for these changes to take effect:\n\n   ```sh\n   sudo sysctl --system\n   ```\n\nAlternatively, restart your system for these changes to take effect.\n\n## Restrictions\n\nThe restrictions of Rootless Docker apply to kind clusters as well.\n\ne.g.\n- OverlayFS cannot be used unless the host is using kernel >= 5.11, or Ubuntu/Debian kernel\n- Cannot mount block storage\n- Cannot mount NFS\n\n## Creating a kind cluster with Rootless Docker\n\nTo create a kind cluster with Rootless Docker, just run:\n```console\n$ export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock\n$ kind create cluster\n```\n\n## Creating a kind cluster with Rootless Podman\n\nTo create a kind cluster with Rootless Podman, just run:\n```console\n$ KIND_EXPERIMENTAL_PROVIDER=podman kind create cluster\n```\n\nOn some distributions, you might need to use systemd-run to start kind into its own cgroup scope:\n```console\n$ systemd-run --scope --user kind create cluster\n```\n\nor\n\n```console\n$ systemd-run --scope --user -p \"Delegate=yes\" kind create cluster\n```\n\nIf you still get the error `running kind with rootless provider requires setting systemd property \"Delegate=yes\"` even with [host requirements](#host-requirements) configured.\n\n## Creating a kind cluster with Rootless nerdctl\n\n**Note: containerd v1.7+ is required**\n\nTo create a kind cluster with nerdctl, just run:\n```console\n$ KIND_EXPERIMENTAL_PROVIDER=nerdctl kind create cluster\n```\n\n## Tips\n- To enable OOM watching, allow `dmesg` by running `sysctl -w kernel.dmesg_restrict=0`.\n"
  },
  {
    "path": "site/content/docs/user/using-wsl2.md",
    "content": "---\ntitle: \"Using WSL2\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"using-wsl2\"\n    weight: 3\ndescription: |-\n  Kind can run using Windows Subsystem for Linux 2 (WSL2) on Windows 10 May 2020 Update (build 19041). \n  \n  All the tools needed to build or run kind work in WSL2, but some extra steps are needed to switch to WSL2. This page covers these steps in brief but also links to the official documentation if you would like more details.\n---\n\n## Getting Windows 10 or 11\n\nDownload the latest ISO at https://www.microsoft.com/en-us/software-download/.\nChoose the latest Windows 10 or Windows 11 release.\n\n### Installing on a virtual machine\n\nRequired Settings\n\n- Supported processor and operating system, see [Enable Nested Virtualization](https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/nested-virtualization) guide for Windows\n  - Intel processors require Windows 10/Windows Server 2016 or greater and the processor must support VT-x and extended page tables (also known as [second level address translation](https://en.wikipedia.org/wiki/Second_Level_Address_Translation))\n  - AMD processors require Windows 11/Windows Server 2022 or greater and the processor generation must be AMD EPYC or Ryzen or newer\n- At least 8GB of memory\n  - It's best to use a static memory allocation, not dynamic. The VM will automatically use paging inside so you don't want it to page on the VM host.\n- Enable nested virtualization support. On Hyper-V, you need to run this from an admin PowerShell prompt - `Set-VMProcessor -VMName ... -ExposeVirtualizationExtensions $true`\n- Attach the ISO to a virtual DVD drive\n- Create a virtual disk with at least 80GB of space\n\nNow, start up the VM. Watch carefully for the \"Press any key to continue installation...\" screen so you don't miss it. Windows Setup will start automatically.\n\n### Installing on a physical machine\n\nIf you're using a physical machine, you can mount the ISO, copy the files to a FAT32 formatted USB disk, and boot from that instead. Be sure the machine is configured to boot using UEFI (not legacy BIOS), and has Intel VT or AMD-V enabled for the hypervisor.\n\n### Tips during setup\n\n- You can skip the product key page\n- On the \"Sign in with Microsoft\" screen, look for the \"offline account\" button.\n\n## Setting up WSL2\n\nIf you want the full details, see the [Installation Instructions for WSL2](https://docs.microsoft.com/en-us/windows/wsl/wsl2-install). This is the TL;DR version.\n\nOnce your Windows machine is ready, you need to do a few more steps to set up WSL2\n\n1. Open a PowerShell window as an admin, then run\n\n    {{< codeFromInline lang=\"powershell\" >}}\nEnable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform, Microsoft-Windows-Subsystem-Linux\n{{< /codeFromInline >}}\n\n1. Reboot when prompted.\n1. After the reboot, set WSL to default to WSL2. Open an admin PowerShell window and run\n    {{< codeFromInline lang=\"powershell\" >}}\nwsl --set-default-version 2\n{{< /codeFromInline >}}\n1. Now, you can install your Linux distro of choice by searching the Windows Store. If you don't want to use the Windows Store, then follow the steps in the WSL docs for [manual install](https://docs.microsoft.com/en-us/windows/wsl/install-manual).\n1. Start up your distro with the shortcut added to the start menu\n\n## Setting up Docker in WSL2 with Docker Desktop\n\nInstall Docker with WSL2 backend here: https://docs.docker.com/docker-for-windows/wsl/\n\n## Setting up Docker in WSL2 without Docker Desktop\n\nAlternatively, docker can be installed in WSL2 without using Docker Desktop.\nSee for example: https://dev.to/bowmanjd/install-docker-on-windows-wsl-without-docker-desktop-34m9\n\nNow, move on to the [Quick Start](/docs/user/quick-start) to set up your cluster with kind.\n\n## Accessing a Kubernetes Service running in WSL2\n\n1. prepare cluster config with exported node port\n    {{< codeFromInline lang=\"yaml\" >}}\n# cluster-config.yml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  extraPortMappings:\n  - containerPort: 30000\n    hostPort: 30000\n    protocol: TCP\n{{< /codeFromInline >}}\n\n1. create cluster `kind create cluster --config=cluster-config.yml`\n1. create deployment `kubectl create deployment nginx --image=nginx --port=80`\n1. create service `kubectl create service nodeport nginx --tcp=80:80 --node-port=30000`\n1. access service `curl localhost:30000`\n\nAlternatively, see [Helpful Tips for WSL2](#helpful-tips-for-wsl2)\n\n## Kubernetes Service with Session Affinity\n\nIf you want to create a Kubernetes Service with `sessionAffinity: ClientIP` it will not be accessible (and neither will any Service created afterwards).\nWSL2 kernel is missing `xt_recent` kernel module, which is used by Kube Proxy to implement session affinity. You need to compile a custom kernel to enable this feature.\n\n1. Build a kernel with `xt_recent` kernel module enabled\n    {{< codeFromInline lang=\"bash\" >}}\ndocker run --name wsl-kernel-builder --rm -it ubuntu:latest bash\n\nWSL_COMMIT_REF=linux-msft-wsl-6.6.87.2-1 # change this line to the version you want to build\n\n# Install dependencies\napt update\napt install -y git build-essential flex bison libssl-dev libelf-dev bc dwarves python3 cpio\n\n# Checkout WSL2 Kernel repo\nmkdir src\ncd src\ngit init\ngit remote add origin https://github.com/microsoft/WSL2-Linux-Kernel.git\ngit config --local gc.auto 0\ngit -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +${WSL_COMMIT_REF}:refs/remotes/origin/build/linux-msft-wsl-5.15.y\ngit checkout --progress --force -B build/linux-msft-wsl-5.15.y refs/remotes/origin/build/linux-msft-wsl-5.15.y\n\n# Enable xt_recent kernel module\nsed -i 's/# CONFIG_NETFILTER_XT_MATCH_RECENT is not set/CONFIG_NETFILTER_XT_MATCH_RECENT=y/' Microsoft/config-wsl\n\n# Compile the kernel \nmake -j2 KCONFIG_CONFIG=Microsoft/config-wsl\n\n# From the host terminal copy the newly built kernel\ndocker cp wsl-kernel-builder:/src/arch/x86/boot/bzImage .\n{{< /codeFromInline >}}\n1. Configure WSL to use newly built kernel: https://docs.microsoft.com/en-us/windows/wsl/wsl-config#configure-global-options-with-wslconfig\n\n   Create a `.wslconfig` file in `C:\\Users\\<your-user-name>\\`:\n    {{< codeFromInline lang=\"toml\" >}}\n[wsl2]\nkernel=c:\\\\path\\\\to\\\\your\\\\kernel\\\\bzImage\n{{< /codeFromInline >}}\n\n## Helpful Tips for WSL2\n\n- If you want to terminate the WSL2 instance to save memory or \"reboot\", open an admin PowerShell prompt and run `wsl --terminate <distro>`. Closing a WSL2 window doesn't shut it down automatically.\n- You can check the status of all installed distros with `wsl --list --verbose`.\n- If you had a distro installed with WSL1, you can convert it to WSL2 with `wsl --set-version <distro> 2`\n- Alternative of [Accessing a Kubernetes Service running in WSL2](#accessing-a-kubernetes-service-running-in-wsl2) or [Setting Up An Ingress Controller](/docs/user/ingress/#setting-up-an-ingress-controller) for accessing workloads is using `kubectl port-forward --address=0.0.0.0`.\n- See the [Known Issues](/docs/user/known-issues/) page for additional Windows-related issues and concerns.\n"
  },
  {
    "path": "site/content/docs/user/working-offline.md",
    "content": "---\ntitle: \"Working Offline\"\nmenu:\n  main:\n    parent: \"user\"\n    identifier: \"working-offline\"\n    weight: 3\ndescription: |-\n  This guide covers how to work with KIND in an offline / airgapped environment.\n\n  You should first [install kind][installation documentation] before continuing.\n\n  [installation documentation]: https://kind.sigs.k8s.io/docs/user/quick-start#installation\n---\n## Using a pre-built [node image][node image]\n\nKIND provides some pre-built images,\nthese images contain everything necessary to create a cluster and can be used in an offline environment.\n\nYou can find available image tags on the [releases page][releases page].\nPlease include the `@sha256:` [image digest][image digest] from the image in the release notes.\n\nYou can pull it when you have network access,\nor pull it on another machine and then transfer it to the target machine.\n\n```\n➜  ~ docker pull kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62\nsha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62: Pulling from kindest/node\ncc5a81c29aab: Pull complete \n81c62728355f: Pull complete \ned9cffdd962a: Pull complete \n6a46f000fce2: Pull complete \n6bd890da28be: Pull complete \n0d88bd219ffe: Pull complete \naf5240f230f0: Pull complete \nDigest: sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62\nStatus: Downloaded newer image for kindest/node@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62\ndocker.io/kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62\n```\n\nYou can [save node image][docker save] to a tarball.\n\n```\n➜  ~ docker save -o kind.v1.17.0.tar kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62\n# or\n➜  ~ docker save kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 | gzip > kind.v1.17.0.tar.gz\n```\n\nWhen you transport image tarball to the machine,\nyou can load the node image by [`docker load`][docker load] command.\n\n```\n➜  ~ docker load -i kind.v1.17.0.tar\nLoaded image ID: sha256:ec6ab22d89efc045f4da4fc862f6a13c64c0670fa7656fbecdec5307380f9cb0\n# or\n➜  ~ docker load -i kind.v1.17.0.tar.gz\nLoaded image ID: sha256:ec6ab22d89efc045f4da4fc862f6a13c64c0670fa7656fbecdec5307380f9cb0\n```\n\nAnd [create a tag][docker tag] for it.\n\n```\n➜  ~ docker image tag kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 kindest/node:v1.17.0\n➜  ~ docker image ls kindest/node\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nkindest/node        v1.17.0             ec6ab22d89ef        3 weeks ago         1.23GB\n```\n\nFinally, you can create a cluster by specifying the `--image` flag.\n\n```\n➜  ~ kind create cluster --image kindest/node:v1.17.0\nCreating cluster \"kind\" ...\n ✓ Ensuring node image (kindest/node:v1.17.0) 🖼\n ✓ Preparing nodes 📦  \n ✓ Writing configuration 📜 \n ✓ Starting control-plane 🕹️ \n ✓ Installing CNI 🔌 \n ✓ Installing StorageClass 💾 \nSet kubectl context to \"kind-kind\"\nYou can now use your cluster with:\n\nkubectl cluster-info --context kind-kind\n\nHave a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂\n```\n\n## Building the [node image][node image]\n\nIn addition to using pre-built node image, \nKIND also provides the ability to build [node image][node image] from Kubernetes source code.\n\nPlease note that during the image building process, you need to download many dependencies.\nIt is recommended that you build at least once online to ensure that these dependencies are downloaded to your local.\nSee [building the node image][building the node image] for more detail.\n\nThe node-image in turn is built off the [base image][base image].\n\n### Prepare Kubernetes source code\n\nYou can clone Kubernetes source code.\n\n```sh\n➜  ~ mkdir -p $GOPATH/src/k8s.io\n➜  ~ cd $GOPATH/src/k8s.io\n➜  ~ git clone https://github.com/kubernetes/kubernetes\n```\n\n### Building image\n\n```sh\n➜  ~ kind build node-image --image kindest/node:main $GOPATH/src/k8s.io/kubernetes \nStarting to build Kubernetes\n...\nImage build completed.\n```\n\nWhen the image build is complete, you can create a cluster by passing the `--image` flag.\n\n```sh\n➜  ~ kind create cluster --image kindest/node:main\nCreating cluster \"kind\" ...\n ✓ Ensuring node image (kindest/node:main) 🖼\n ✓ Preparing nodes 📦  \n ✓ Writing configuration 📜 \n ✓ Starting control-plane 🕹️ \n ✓ Installing CNI 🔌 \n ✓ Installing StorageClass 💾 \nSet kubectl context to \"kind-kind\"\nYou can now use your cluster with:\n\nkubectl cluster-info --context kind-kind\n\nHave a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂\n```\n\n## HA cluster\n\nIf you want to create a control-plane HA cluster\nthen you need to create a config file and use this file to start the cluster.\n\n```sh\n➜  ~ cat << EOF | kind create cluster --config=-\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\n# 3 control plane node and 1 workers\nnodes:\n- role: control-plane\n- role: control-plane\n- role: control-plane\n- role: worker\nEOF\n```\n\nNote that in an offline environment, in addition to preparing the node image,\nyou also need to prepare HAProxy image in advance.\n\nYou can find the specific tag currently in use at [loadbalancer source code][loadbalancer source code].\n\n\n\n\n\n\n\n[installation documentation]: https://kind.sigs.k8s.io/docs/user/quick-start#installation\n[node image]: https://kind.sigs.k8s.io/docs/design/node-image\n[releases page]: https://github.com/kubernetes-sigs/kind/releases\n[image digest]: https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier\n[docker save]: https://docs.docker.com/engine/reference/commandline/save/\n[docker load]: https://docs.docker.com/engine/reference/commandline/load/\n[docker tag]: https://docs.docker.com/engine/reference/commandline/tag/\n[base image]: https://kind.sigs.k8s.io/docs/design/base-image/\n[building the node image]: https://kind.sigs.k8s.io/docs/user/quick-start/#building-images\n[loadbalancer source code]: https://github.com/kubernetes-sigs/kind/blob/main/pkg/cluster/internal/loadbalancer/const.go#L20\n"
  },
  {
    "path": "site/data/apiVersions.yaml",
    "content": "- v1alpha4\n"
  },
  {
    "path": "site/go.mod",
    "content": "module sigs.k8s.io/kind/site\n\ngo 1.18\n\nrequire github.com/gohugoio/hugo v0.111.3\n\nrequire (\n\tcloud.google.com/go v0.101.0 // indirect\n\tcloud.google.com/go/compute v1.6.1 // indirect\n\tcloud.google.com/go/iam v0.3.0 // indirect\n\tcloud.google.com/go/storage v1.22.0 // indirect\n\tgithub.com/Azure/azure-pipeline-go v0.2.3 // indirect\n\tgithub.com/Azure/azure-storage-blob-go v0.14.0 // indirect\n\tgithub.com/Azure/go-autorest v14.2.0+incompatible // indirect\n\tgithub.com/Azure/go-autorest/autorest v0.11.20 // indirect\n\tgithub.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect\n\tgithub.com/Azure/go-autorest/autorest/date v0.3.0 // indirect\n\tgithub.com/Azure/go-autorest/logger v0.2.1 // indirect\n\tgithub.com/Azure/go-autorest/tracing v0.6.0 // indirect\n\tgithub.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 // indirect\n\tgithub.com/PuerkitoBio/purell v1.1.1 // indirect\n\tgithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.5.0 // indirect\n\tgithub.com/armon/go-radix v1.0.0 // indirect\n\tgithub.com/aws/aws-sdk-go v1.43.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.9.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/config v1.7.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.4.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.2.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.4.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.7.0 // indirect\n\tgithub.com/aws/smithy-go v1.8.0 // indirect\n\tgithub.com/bep/clock v0.3.0 // indirect\n\tgithub.com/bep/debounce v1.2.0 // indirect\n\tgithub.com/bep/gitmap v1.1.2 // indirect\n\tgithub.com/bep/goat v0.5.0 // indirect\n\tgithub.com/bep/godartsass v0.16.0 // indirect\n\tgithub.com/bep/golibsass v1.1.0 // indirect\n\tgithub.com/bep/gowebp v0.2.0 // indirect\n\tgithub.com/bep/lazycache v0.2.0 // indirect\n\tgithub.com/bep/overlayfs v0.6.0 // indirect\n\tgithub.com/bep/tmc v0.5.1 // indirect\n\tgithub.com/clbanning/mxj/v2 v2.5.7 // indirect\n\tgithub.com/cli/safeexec v1.0.0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect\n\tgithub.com/disintegration/gift v1.2.1 // indirect\n\tgithub.com/dlclark/regexp2 v1.7.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.0 // indirect\n\tgithub.com/evanw/esbuild v0.17.0 // indirect\n\tgithub.com/frankban/quicktest v1.14.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/getkin/kin-openapi v0.110.0 // indirect\n\tgithub.com/ghodss/yaml v1.0.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.5 // indirect\n\tgithub.com/go-openapi/swag v0.19.5 // indirect\n\tgithub.com/gobuffalo/flect v0.3.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 // indirect\n\tgithub.com/gohugoio/locales v0.14.0 // indirect\n\tgithub.com/gohugoio/localescompressed v1.0.1 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.0.0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/protobuf v1.5.2 // indirect\n\tgithub.com/google/go-cmp v0.5.9 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/google/wire v0.5.0 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.3.0 // indirect\n\tgithub.com/googleapis/go-type-adapters v1.0.0 // indirect\n\tgithub.com/gorilla/websocket v1.5.0 // indirect\n\tgithub.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.1 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.1 // indirect\n\tgithub.com/invopop/yaml v0.1.0 // indirect\n\tgithub.com/jdkato/prose v1.2.1 // indirect\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/kr/pretty v0.3.1 // indirect\n\tgithub.com/kr/text v0.2.0 // indirect\n\tgithub.com/kyokomi/emoji/v2 v2.2.11 // indirect\n\tgithub.com/magefile/mage v1.14.0 // indirect\n\tgithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect\n\tgithub.com/marekm4/color-extractor v1.2.0 // indirect\n\tgithub.com/mattn/go-ieproxy v0.0.1 // indirect\n\tgithub.com/mattn/go-isatty v0.0.17 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.9 // indirect\n\tgithub.com/mitchellh/hashstructure v1.1.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect\n\tgithub.com/muesli/smartcrop v0.3.0 // indirect\n\tgithub.com/niklasfasching/go-org v1.6.6 // indirect\n\tgithub.com/olekukonko/tablewriter v0.0.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.0.6 // indirect\n\tgithub.com/rogpeppe/go-internal v1.9.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect\n\tgithub.com/sanity-io/litter v1.5.5 // indirect\n\tgithub.com/spf13/afero v1.9.3 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/cobra v1.6.1 // indirect\n\tgithub.com/spf13/fsync v0.9.0 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/tdewolff/minify/v2 v2.12.4 // indirect\n\tgithub.com/tdewolff/parse/v2 v2.6.5 // indirect\n\tgithub.com/yuin/goldmark v1.5.4 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.uber.org/atomic v1.10.0 // indirect\n\tgocloud.dev v0.24.0 // indirect\n\tgolang.org/x/crypto v0.3.0 // indirect\n\tgolang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect\n\tgolang.org/x/image v0.5.0 // indirect\n\tgolang.org/x/net v0.7.0 // indirect\n\tgolang.org/x/oauth2 v0.2.0 // indirect\n\tgolang.org/x/sync v0.1.0 // indirect\n\tgolang.org/x/sys v0.5.0 // indirect\n\tgolang.org/x/text v0.7.0 // indirect\n\tgolang.org/x/tools v0.4.0 // indirect\n\tgolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect\n\tgoogle.golang.org/api v0.76.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect\n\tgoogle.golang.org/grpc v1.46.0 // indirect\n\tgoogle.golang.org/protobuf v1.28.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "site/go.sum",
    "content": "bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY=\ncloud.google.com/go v0.89.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go v0.101.0 h1:g+LL+JvpvdyGtcaD2xw2mSByE/6F9s471eJSoaysM84=\ncloud.google.com/go v0.101.0/go.mod h1:hEiddgDb77jDQ+I80tURYNJEnuwPzFU8awCFFRLKjW0=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=\ncloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=\ncloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=\ncloud.google.com/go/kms v0.1.0/go.mod h1:8Qp8PCAypHg4FdmlyW1QRAv09BGQ9Uzh7JnmIZxPk+c=\ncloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/pubsub v1.16.0/go.mod h1:6A8EfoWZ/lUvCWStKGwAWauJZSiuV0Mkmu6WilK/TxQ=\ncloud.google.com/go/secretmanager v0.1.0/go.mod h1:3nGKHvnzDUVit7U0S9KAKJ4aOsO1xtwRG+7ey5LK1bM=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=\ncloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4=\ncloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8=\ncloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE=\ncloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=\ncontrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=\ncontrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=\ncontrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=\ngithub.com/Azure/azure-amqp-common-go/v3 v3.1.1/go.mod h1:YsDaPfaO9Ub2XeSKdIy2DfwuiQlHQCauHJwSqtrkECI=\ngithub.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=\ngithub.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=\ngithub.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-sdk-for-go v57.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-service-bus-go v0.10.16/go.mod h1:MlkLwGGf1ewcx5jZadn0gUEty+tTg0RaElr6bPf+QhI=\ngithub.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM=\ngithub.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=\ngithub.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=\ngithub.com/Azure/go-amqp v0.13.11/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk=\ngithub.com/Azure/go-amqp v0.13.12/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk=\ngithub.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=\ngithub.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=\ngithub.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=\ngithub.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M=\ngithub.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=\ngithub.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg=\ngithub.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.4.3 h1:DOhB+nXkF7LN0JfBGB5YtCF6QLK8mLe4psaHF7ZQEKM=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.4.3/go.mod h1:yAQ2b6eP/CmLPnmLvxtT1ALIY3OR1oFcCqVBi8vHiTc=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=\ngithub.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=\ngithub.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU=\ngithub.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=\ngithub.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk=\ngithub.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=\ngithub.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=\ngithub.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=\ngithub.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=\ngithub.com/aws/aws-sdk-go v1.43.5 h1:N7arnx54E4QyW69c45UW5o8j2DCSjzpoxzJW3yU6OSo=\ngithub.com/aws/aws-sdk-go v1.43.5/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=\ngithub.com/aws/aws-sdk-go-v2 v1.9.0 h1:+S+dSqQCN3MSU5vJRu1HqHrq00cJn6heIMU7X9hcsoo=\ngithub.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=\ngithub.com/aws/aws-sdk-go-v2/config v1.7.0 h1:J2cZ7qe+3IpqBEXnHUrFrOjoB9BlsXg7j53vxcl5IVg=\ngithub.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.4.0 h1:kmvesfjY861FzlCU9mvAfe01D9aeXcG2ZuC+k9F2YLM=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0 h1:OxTAgH8Y4BXHD6PGCJ8DHx2kaZPCQfSTqmDsdRZFezE=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.2.2 h1:d95cddM3yTm4qffj3P6EnP+TzX1SSkWaQypXSgT/hpA=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0 h1:VNJ5NLBteVXEwE2F1zEXVmyIH58mZ6kIQGJoC7C+vkg=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.5.0/go.mod h1:w7JuP9Oq1IKMFQPkNe3V6s9rOssXzOVEMNEqK1L1bao=\ngithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0/go.mod h1:B+7C5UKdVq1ylkI/A6O8wcurFtaux0R1njePNPtKwoA=\ngithub.com/aws/aws-sdk-go-v2/service/ssm v1.10.0/go.mod h1:4dXS5YNqI3SNbetQ7X7vfsMlX6ZnboJA2dulBwJx7+g=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.4.0 h1:sHXMIKYS6YiLPzmKSvDpPmOpJDHxmAUgbiF49YNVztg=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.7.0 h1:1at4e5P+lvHNl2nUktdM2/v+rpICg/QSEr9TO/uW9vU=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM=\ngithub.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=\ngithub.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/bep/clock v0.3.0 h1:vfOA6+wVb6pPQEiXow9f/too92vNTLe9MuwO13PfI0M=\ngithub.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlwk=\ngithub.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=\ngithub.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=\ngithub.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840=\ngithub.com/bep/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY=\ngithub.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=\ngithub.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c=\ngithub.com/bep/godartsass v0.16.0 h1:nTpenrZBQjVSjLkCw3AgnYmBB2czauTJa4BLLv448qg=\ngithub.com/bep/godartsass v0.16.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=\ngithub.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw=\ngithub.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=\ngithub.com/bep/gowebp v0.2.0 h1:ZVfK8i9PpZqKHEmthQSt3qCnnHycbLzBPEsVtk2ch2Q=\ngithub.com/bep/gowebp v0.2.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=\ngithub.com/bep/lazycache v0.2.0 h1:HKrlZTrDxHIrNKqmnurH42ryxkngCMYLfBpyu40VcwY=\ngithub.com/bep/lazycache v0.2.0/go.mod h1:xUIsoRD824Vx0Q/n57+ZO7kmbEhMBOnTjM/iPixNGbg=\ngithub.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=\ngithub.com/bep/overlayfs v0.6.0/go.mod h1:NFjSmn3kCqG7KX2Lmz8qT8VhPPCwZap3UNogXawoQHM=\ngithub.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=\ngithub.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=\ngithub.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=\ngithub.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0=\ngithub.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=\ngithub.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=\ngithub.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=\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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\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/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=\ngithub.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=\ngithub.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=\ngithub.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=\ngithub.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=\ngithub.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=\ngithub.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=\ngithub.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=\ngithub.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/evanw/esbuild v0.17.0 h1:gGx9TCZDO9k9x1PJdizx6syIpUq29RwrtHWlgDIdQH8=\ngithub.com/evanw/esbuild v0.17.0/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=\ngithub.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=\ngithub.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=\ngithub.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=\ngithub.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=\ngithub.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=\ngithub.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=\ngithub.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/getkin/kin-openapi v0.110.0 h1:1GnJALxsltcSzCMqgtqKlLhYQeULv3/jesmV2sC5qE0=\ngithub.com/getkin/kin-openapi v0.110.0/go.mod h1:QtwUNt0PAAgIIBEvFWYfB7dfngxtAaqCX1zYHMZDeK8=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=\ngithub.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=\ngithub.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=\ngithub.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk=\ngithub.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=\ngithub.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 h1:Nj29Qbkt0bZ/bJl8eccfxQp3NlU/0IW1v9eyYtQ53XQ=\ngithub.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=\ngithub.com/gohugoio/hugo v0.111.3 h1:m98NJv/5ivJLkQ4u3vPYsrAfBTnDIefZPGhnw/7xW80=\ngithub.com/gohugoio/hugo v0.111.3/go.mod h1:1gb2es3022plbaNiZjhBTdpXN2cepIeqvBnL/NHnKLY=\ngithub.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=\ngithub.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4=\ngithub.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo=\ngithub.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0=\ngithub.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95 h1:sgew0XCnZwnzpWxTt3V8LLiCO7OQi3C6dycaE67wfkU=\ngithub.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/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.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=\ngithub.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=\ngithub.com/google/go-replayers/httpreplay v1.0.0 h1:8SmT8fUYM4nueF+UnXIX8LJxNTb1vpPuknXz+yTWzL4=\ngithub.com/google/go-replayers/httpreplay v1.0.0/go.mod h1:LJhKoTwS5Wy5Ld/peq8dFFG5OfJyHEz7ft+DsTUv25M=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE=\ngithub.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=\ngithub.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=\ngithub.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA=\ngithub.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=\ngithub.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\ngithub.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=\ngithub.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690 h1:XWjCrg/HJRLZCbvsUxS5R/9JhwiiwNctEsRvZ1Vjz5k=\ngithub.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690/go.mod h1:8Qu9UmnhCRunfRv365Z3w+mT/WfLGKJiK+vugY9qNCU=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=\ngithub.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=\ngithub.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=\ngithub.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU=\ngithub.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA=\ngithub.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=\ngithub.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kyokomi/emoji/v2 v2.2.11 h1:Pf/ZWVTbnAVkHOLJLWjPxM/FmgyPe+d85cv/OLP5Yus=\ngithub.com/kyokomi/emoji/v2 v2.2.11/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=\ngithub.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=\ngithub.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=\ngithub.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/marekm4/color-extractor v1.2.0 h1:DCU/FXg3PlAwig7W5PRZshiX5x38k0aNPTxYZ6/fZb0=\ngithub.com/marekm4/color-extractor v1.2.0/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=\ngithub.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=\ngithub.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=\ngithub.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\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/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=\ngithub.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=\ngithub.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=\ngithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=\ngithub.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=\ngithub.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc=\ngithub.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=\ngithub.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=\ngithub.com/niklasfasching/go-org v1.6.6 h1:U6+mJ80p3weR4oP+Z+Pb2EVkSbt1MUwweBbUcF1hVqQ=\ngithub.com/niklasfasching/go-org v1.6.6/go.mod h1:o3pMQpO9n6RNBXz2Oc2DiRkaVwjns0JElyKiG7yXwA4=\ngithub.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=\ngithub.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=\ngithub.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=\ngithub.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=\ngithub.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=\ngithub.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=\ngithub.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=\ngithub.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=\ngithub.com/spf13/fsync v0.9.0 h1:f9CEt3DOB2mnHxZaftmEOFWjABEvKM/xpf3cUwJrGOY=\ngithub.com/spf13/fsync v0.9.0/go.mod h1:fNtJEfG3HiltN3y4cPOz6MLjos9+2pIEqLIgszqhp/0=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE=\ngithub.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk=\ngithub.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=\ngithub.com/tdewolff/parse/v2 v2.6.5 h1:lYvWBk55GkqKl0JJenGpmrgu/cPHQQ6/Mm1hBGswoGQ=\ngithub.com/tdewolff/parse/v2 v2.6.5/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=\ngithub.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=\ngithub.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=\ngithub.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=\ngithub.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=\ngithub.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=\ngo.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=\ngo.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngo.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngocloud.dev v0.24.0 h1:cNtHD07zQQiv02OiwwDyVMuHmR7iQt2RLkzoAgz7wBs=\ngocloud.dev v0.24.0/go.mod h1:uA+als++iBX5ShuG4upQo/3Zoz49iIPlYUWHV5mM8w8=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\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-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=\ngolang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=\ngolang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=\ngolang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=\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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\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-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/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-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=\ngolang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=\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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/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 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=\ngolang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=\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=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7SrU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.76.0 h1:UkZl25bR1FHNqtK/EKs3vCdpZtUO6gea3YElTwc8pQg=\ngoogle.golang.org/api v0.76.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 h1:G1IeWbjrqEq9ChWxEuRPJu6laA67+XgTFHVSAvepr38=\ngoogle.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0/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=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nnhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=\nnhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "site/layouts/docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n{{ partial \"header.html\" . }}\n<body ontouchstart=\"return true\" class=\"sidebar-collapsed\">\n{{ partial \"sidebar.html\" . }}\n<div id=\"wrapper\">{{ partial \"navbar.html\" . }}\n<!-- render the content as markdown, using a regex to add heading links-->\n<div id=\"content\">{{ partial \"fancymarkdown.html\" . }}</div>\n{{ partial \"footer.html\" . }}\n</div>\n</body>\n</html>"
  },
  {
    "path": "site/layouts/docs/section.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n{{ partial \"header.html\" . }}\n<body ontouchstart=\"return true\" class=\"sidebar-collapsed\">\n{{ partial \"sidebar.html\" . }}\n<div id=\"wrapper\">{{ partial \"navbar.html\" . }}\n<!-- render the content as markdown, using a regex to add heading links-->\n<div id=\"content\">{{ partial \"fancymarkdown.html\" . }}</div>\n{{ partial \"footer.html\" . }}\n</div>\n</body>\n</html>"
  },
  {
    "path": "site/layouts/docs/single.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n{{ partial \"header.html\" . }}\n<body ontouchstart=\"return true\" class=\"sidebar-collapsed\">\n{{ partial \"sidebar.html\" . }}\n<div id=\"wrapper\">{{ partial \"navbar.html\" . }}\n<!-- render the content as markdown, using a regex to add heading links-->\n<div id=\"content\">{{ partial \"fancymarkdown.html\" . }}</div>\n{{ partial \"footer.html\" . }}\n</div>\n</body>\n</html>"
  },
  {
    "path": "site/layouts/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n{{ partial \"header.html\" . }}\n<body ontouchstart=\"return true\" class=\"sidebar-collapsed\">\n{{ partial \"sidebar.html\" . }}\n<div id=\"wrapper\">{{ partial \"navbar.html\" . }}\n<!-- render the content as markdown, using a regex to add heading links-->\n<div id=\"content\">{{ partial \"fancymarkdown.html\" . }}</div>\n{{ partial \"footer.html\" . }}\n</div>\n</body>\n</html>"
  },
  {
    "path": "site/layouts/index.redirects",
    "content": "{{- $apiVersions := site.Data.apiVersions -}}\n{{- range $apiVersions }}\n/{{ . }}     https://godoc.org/sigs.k8s.io/kind/pkg/apis/config/{{ . }}\n{{- end }}\n/dl/v* https://github.com/kubernetes-sigs/kind/releases/download/v:splat\n/dl/* https://storage.googleapis.com/k8s-staging-kind/:splat"
  },
  {
    "path": "site/layouts/partials/fancymarkdown.html",
    "content": "{{ if not (eq .Site.Title .Params.title) }}\n<h1>{{ .Params.title }}</h1>\n\n{{ end }}\n{{ if .Params.description }}\n<blockquote class=\"page-description\">{{ .Params.description | markdownify }}</blockquote>\n{{end}}\n{{ if .Params.toc }}\n<h2 id=\"contents\">Contents<a href=\"#contents\" class=\"hanchor\" arialabel=\"Anchor\"> 🔗︎</a></h2>\n<aside>\n{{.TableOfContents}}\n</aside>\n\n{{ end }}\n{{ .Content | replaceRE \"(<h[1-9] id=\\\"([^\\\"]+)\\\".+)(</h[1-9]+>)\" `${1}<a href=\"#${2}\" class=\"hanchor\" ariaLabel=\"Anchor\"> 🔗&#xFE0E;</a> ${3}` | safeHTML }}\n"
  },
  {
    "path": "site/layouts/partials/footer.html",
    "content": "<div id=\"footer\" class=\"footer\">\n  <div class=\"footer-content\">\n    <p class=\"copyright\">© {{now.Year}} The Kubernetes Authors | Documentation Distributed under <a class=\"nowrap\" href=\"https://creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a> | <a href=\"https://github.com/kubernetes-sigs/kind/tree/main/site/static/examples\">Examples</a> Distributed under <a class=\"nowrap\" href=\"https://www.apache.org/licenses/LICENSE-2.0\">Apache-2.0</a></p>\n    {{if .GitInfo}}<p>Last Updated on {{.GitInfo.AuthorDate.Format \"2006-01-02 15:04:05 -0700\" }} in <span id=\"commit\"><a href=\"https://github.com/kubernetes-sigs/kind/commit/{{.GitInfo.Hash}}\">{{.GitInfo.AbbreviatedHash}}</a></span></p>{{end}}\n  </div>\n</div>"
  },
  {
    "path": "site/layouts/partials/header.html",
    "content": "<head>\n  <title>{{ .Site.Title }}{{ if and .Params.title (not (eq .Site.Title .Params.Title)) }} – {{ .Params.title }}{{ end }}</title>\n  {{ partialCached \"inlinecss.html\" . }}\n  {{ partialCached \"inlinescript.html\" . }}\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"{{ \"apple-touch-icon.png\" | relURL }}\">\n  <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"{{ \"favicon-32x32.png\" | relURL }}\">\n  <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"{{ \"favicon-16x16.png\" | relURL }}\">\n  <link rel=\"manifest\" href=\"{{ \"site.webmanifest\" | relURL }}\">\n  <link rel=\"mask-icon\" href=\"{{ \"safari-pinned-tab.svg\" | relURL }}\" color=\"#5bbad5\">\n  <meta name=\"msapplication-TileColor\" content=\"#2d89ef\">\n  <meta name=\"theme-color\" content=\"#ffffff\">\n</head>"
  },
  {
    "path": "site/layouts/partials/inlinecss.html",
    "content": "{{ with resources.Get \"css/inline.css\" | resources.Minify }}\n<style>{{ .Content | safeCSS }}</style>\n{{ end }}"
  },
  {
    "path": "site/layouts/partials/inlinescript.html",
    "content": "{{ with resources.Get \"js/inline.js\" | resources.Minify }}\n<script>{{ .Content | safeJS }}</script>\n{{ end }}"
  },
  {
    "path": "site/layouts/partials/navbar.html",
    "content": "<div id=\"navbar\" class=\"ua\">\n  <span id=\"sidebar-toggle\" onclick=\"toggleSidebar()\">&#9776;</span><span id=\"navbar-title\"><a href=\"{{.Site.BaseURL}}\">kind</a> </span><span id=\"github\"><a href=\"https://github.com/kubernetes-sigs/kind/\"><img alt=\"github\" title=\"github\" src=\"{{ \"third_party/GitHub-Mark-120px-plus.png\" | relURL }}\"></a></span>\n</div>\n"
  },
  {
    "path": "site/layouts/partials/sidebar.html",
    "content": "<div id=\"sidebar\">\n  <a href=\"{{.Site.BaseURL}}\" id=\"sidebar-logo\"><img src=\"{{ \"logo/logo.png\" | relURL }}\" width=\"100%\" /></a>\n  <div style=\"background-color: #0000001a; width: 100%; height: 1px; min-height: .1em;\"></div>\n  <ul>\n    {{ $currentPage := . }}\n    {{ range .Site.Menus.main }}\n      {{ if .HasChildren }}\n        <li class=\"{{ if $currentPage.HasMenuCurrent \"main\" . }}active{{ end }}\">{{ .Pre }}<span>{{ .Name }}</span></li>\n        <ul class=\"sub-menu\">\n          {{ range .Children }}\n            <li class=\"{{ if $currentPage.IsMenuCurrent \"main\" . }}active-entry{{ end }}\">\n              <a href=\"{{ .URL }}\">{{ .Name }}</a>\n            </li>\n          {{ end }}\n        </ul>\n      {{ else }}\n        {{ if .Name }}\n          <li class=\"{{ if $currentPage.IsMenuCurrent \"main\" . }}active-entry{{ end }}\">\n            <a href=\"{{ .URL }}\">\n              {{ .Pre }}\n              <span>{{ .Name }}</span>\n            </a>\n          </li>\n        {{end}}\n      {{ end }}\n    {{ end }}\n  </ul>\n</div>"
  },
  {
    "path": "site/layouts/robots.txt",
    "content": "User-agent: *\n{{ if ne ( getenv \"HUGO_ENV\" ) \"production\" -}}\nDisallow: /\n{{- else -}}\nAllow: /\n{{- end -}}"
  },
  {
    "path": "site/layouts/shortcodes/absURL.html",
    "content": "{{/* the one input should be a relative url */}}\n{{- .Get 0 | absURL }}\n"
  },
  {
    "path": "site/layouts/shortcodes/codeFromFile.html",
    "content": "{{ $file := .Get \"file\" }}\n{{ $lang := \"\" }}\n{{ $suffix := findRE \"(\\\\.[^.]+)$\" $file 1 }}\n{{ with  $suffix }}\n{{ $lang = (index . 0 | strings.TrimPrefix \".\") }}\n{{ end }}\n{{ with .Get \"lang\" }}{{ $lang = . }}{{ end }}\n{{ $virtualPath :=  strings.TrimPrefix `static/` $file }}\n<table class=\"includecode\" id=\"code-{{ $virtualPath }}\">\n  <thead>\n    <tr>\n      <th>\n        <a href=\"/{{ $virtualPath }}\">{{ $virtualPath }}</a>\n        <button onclick='copyText(\"code-{{ $virtualPath }}-hidden-copy-text\");' title=\"Copy to clipboard\">\n          <img src=\"{{ \"copycode.svg\" | relURL }}\" alt=\"Copy\">\n        </button>\n      </th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>{{- highlight (trim ($file | readFile) \"\\n\") $lang \"\" | -}}</td>\n    </tr>\n  </tbody>\n  <textarea class=\"hidden-copy-text\" id=\"code-{{ $virtualPath }}-hidden-copy-text\">{{ ($file | readFile) }}</textarea>\n</table>\n"
  },
  {
    "path": "site/layouts/shortcodes/codeFromInline.html",
    "content": "{{ $code := trim .Inner  \"\\n\"  }}\n{{ $lang := \"\" }}\n{{ with .Get \"lang\" }}{{ $lang = . }}{{ end }}\n{{ $hash := md5 $code }}\n<table class=\"includecode\" id=\"inline-code-{{ $hash }}\">\n  <thead>\n  <tr>\n    <th>\n      <button onclick='copyText(\"inline-code-{{ $hash }}-hidden-copy-text\");' title=\"Copy to clipboard\">\n        <img src=\"{{ \"copycode.svg\" | relURL }}\" alt=\"Copy\">\n      </button>\n    </th>\n  </tr>\n  </thead>\n  <tbody>\n  <tr>\n    <td>{{ highlight $code $lang \"\" }}</td>\n  </tr>\n  </tbody>\n  <textarea class=\"hidden-copy-text\" id=\"inline-code-{{ $hash }}-hidden-copy-text\">{{ $code }}</textarea>\n</table>\n"
  },
  {
    "path": "site/layouts/shortcodes/minify.html",
    "content": "{{ $file := .Get \"file\" }}\n{{- $contents := readFile $file -}}\n{{ $res := $contents | resources.FromString $file }}\n{{- safeHTML (($res | resources.Minify).Content) -}}\n"
  },
  {
    "path": "site/layouts/shortcodes/readFile.html",
    "content": "{{/* the one input should be a file path */}}\n{{- .Get 0 | readFile | safeHTML -}}"
  },
  {
    "path": "site/layouts/shortcodes/securitygoose.html",
    "content": "<blockquote style=\"background-color: yellow; border-color: black\"><img alt=\"security goose says\" title=\"HONK\" src=\"{{ \"third_party/shiny_goose.png\" | relURL }}\" style=\"max-width: 6em; float: right;\" /><p style=\"font-weight: bold; text-decoration: underline; padding: 0; margin: 0; margin-top: -.25em; font-size: 1.25em;\">Security Goose Says:</p>{{ .Inner | markdownify }}</blockquote>\n"
  },
  {
    "path": "site/layouts/shortcodes/stableVersion.html",
    "content": "{{- site.Params.stable -}}\n"
  },
  {
    "path": "site/static/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#2d89ef</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "site/static/examples/LICENSE",
    "content": "                                 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": "site/static/examples/config-with-mounts.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  # add a mount from /path/to/my/files on the host to /files on the node\n  extraMounts:\n  - hostPath: /path/to/my/files\n    containerPath: /files\n  #\n  # add an additional mount leveraging *all* of the config fields\n  #\n  # generally you only need the two fields above ...\n  #\n  - hostPath: /path/to/my/other-files/\n    containerPath: /other-files\n    # optional: if set, the mount is read-only.\n    # default false\n    readOnly: true\n    # optional: if set, the mount needs SELinux relabeling.\n    # default false\n    selinuxRelabel: false\n    # optional: set propagation mode (None, HostToContainer or Bidirectional)\n    # see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation\n    # default None\n    #\n    # WARNING: You very likely do not need this field.\n    #\n    # This field controls propagation of *additional* mounts created\n    # *at runtime* underneath this mount.\n    #\n    # On MacOS with Docker Desktop, if the mount is from macOS and not the\n    # docker desktop VM, you cannot use this field. You can use it for\n    # mounts to the linux VM.\n    propagation: None\n"
  },
  {
    "path": "site/static/examples/config-with-port-mapping.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n  # port forward 80 on the host to 80 on this node\n  extraPortMappings:\n  - containerPort: 80\n    hostPort: 80\n    # optional: set the bind address on the host\n    # 0.0.0.0 is the current default\n    listenAddress: \"127.0.0.1\"\n    # optional: set the protocol to one of TCP, UDP, SCTP.\n    # TCP is the default\n    protocol: TCP\n"
  },
  {
    "path": "site/static/examples/ingress/deploy-ingress-nginx.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n  name: ingress-nginx\n---\napiVersion: v1\nautomountServiceAccountToken: true\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx\n  namespace: ingress-nginx\n---\napiVersion: v1\nautomountServiceAccountToken: true\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission\n  namespace: ingress-nginx\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx\n  namespace: ingress-nginx\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  - pods\n  - secrets\n  - endpoints\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingresses/status\n  verbs:\n  - update\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingressclasses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - coordination.k8s.io\n  resourceNames:\n  - ingress-nginx-leader\n  resources:\n  - leases\n  verbs:\n  - get\n  - update\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - create\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - discovery.k8s.io\n  resources:\n  - endpointslices\n  verbs:\n  - list\n  - watch\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission\n  namespace: ingress-nginx\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - get\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  - endpoints\n  - nodes\n  - pods\n  - secrets\n  - namespaces\n  verbs:\n  - list\n  - watch\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - nodes\n  verbs:\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingresses/status\n  verbs:\n  - update\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingressclasses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - discovery.k8s.io\n  resources:\n  - endpointslices\n  verbs:\n  - list\n  - watch\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission\nrules:\n- apiGroups:\n  - admissionregistration.k8s.io\n  resources:\n  - validatingwebhookconfigurations\n  verbs:\n  - get\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx\n  namespace: ingress-nginx\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: ingress-nginx\nsubjects:\n- kind: ServiceAccount\n  name: ingress-nginx\n  namespace: ingress-nginx\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission\n  namespace: ingress-nginx\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: ingress-nginx-admission\nsubjects:\n- kind: ServiceAccount\n  name: ingress-nginx-admission\n  namespace: ingress-nginx\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: ingress-nginx\nsubjects:\n- kind: ServiceAccount\n  name: ingress-nginx\n  namespace: ingress-nginx\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: ingress-nginx-admission\nsubjects:\n- kind: ServiceAccount\n  name: ingress-nginx-admission\n  namespace: ingress-nginx\n---\napiVersion: v1\ndata: null\nkind: ConfigMap\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-controller\n  namespace: ingress-nginx\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-controller\n  namespace: ingress-nginx\nspec:\n  ipFamilies:\n  - IPv4\n  ipFamilyPolicy: SingleStack\n  ports:\n  - appProtocol: http\n    name: http\n    port: 80\n    protocol: TCP\n    targetPort: http\n  - appProtocol: https\n    name: https\n    port: 443\n    protocol: TCP\n    targetPort: https\n  selector:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n  type: LoadBalancer\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-controller-admission\n  namespace: ingress-nginx\nspec:\n  ports:\n  - appProtocol: https\n    name: https-webhook\n    port: 443\n    targetPort: webhook\n  selector:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-controller\n  namespace: ingress-nginx\nspec:\n  minReadySeconds: 0\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      app.kubernetes.io/component: controller\n      app.kubernetes.io/instance: ingress-nginx\n      app.kubernetes.io/name: ingress-nginx\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/component: controller\n        app.kubernetes.io/instance: ingress-nginx\n        app.kubernetes.io/name: ingress-nginx\n        app.kubernetes.io/part-of: ingress-nginx\n        app.kubernetes.io/version: 1.12.1\n    spec:\n      containers:\n      - args:\n        - /nginx-ingress-controller\n        - --election-id=ingress-nginx-leader\n        - --controller-class=k8s.io/ingress-nginx\n        - --ingress-class=nginx\n        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller\n        - --validating-webhook=:8443\n        - --validating-webhook-certificate=/usr/local/certificates/cert\n        - --validating-webhook-key=/usr/local/certificates/key\n        - --watch-ingress-without-class=true\n        - --publish-status-address=localhost\n        env:\n        - name: POD_NAME\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: POD_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: LD_PRELOAD\n          value: /usr/local/lib/libmimalloc.so\n        image: registry.k8s.io/ingress-nginx/controller:v1.12.1@sha256:9724476b928967173d501040631b23ba07f47073999e80e34b120e8db5f234d5\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          preStop:\n            exec:\n              command:\n              - /wait-shutdown\n        livenessProbe:\n          failureThreshold: 5\n          httpGet:\n            path: /healthz\n            port: 10254\n            scheme: HTTP\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        name: controller\n        ports:\n        - containerPort: 80\n          hostPort: 80\n          name: http\n          protocol: TCP\n        - containerPort: 443\n          hostPort: 443\n          name: https\n          protocol: TCP\n        - containerPort: 8443\n          name: webhook\n          protocol: TCP\n        readinessProbe:\n          failureThreshold: 3\n          httpGet:\n            path: /healthz\n            port: 10254\n            scheme: HTTP\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        resources:\n          requests:\n            cpu: 100m\n            memory: 90Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_BIND_SERVICE\n            drop:\n            - ALL\n          readOnlyRootFilesystem: false\n          runAsGroup: 82\n          runAsNonRoot: true\n          runAsUser: 101\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /usr/local/certificates/\n          name: webhook-cert\n          readOnly: true\n      dnsPolicy: ClusterFirst\n      nodeSelector:\n        kubernetes.io/os: linux\n      serviceAccountName: ingress-nginx\n      terminationGracePeriodSeconds: 0\n      tolerations:\n      - effect: NoSchedule\n        key: node-role.kubernetes.io/master\n        operator: Equal\n      - effect: NoSchedule\n        key: node-role.kubernetes.io/control-plane\n        operator: Equal\n      volumes:\n      - name: webhook-cert\n        secret:\n          secretName: ingress-nginx-admission\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission-create\n  namespace: ingress-nginx\nspec:\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/component: admission-webhook\n        app.kubernetes.io/instance: ingress-nginx\n        app.kubernetes.io/name: ingress-nginx\n        app.kubernetes.io/part-of: ingress-nginx\n        app.kubernetes.io/version: 1.12.1\n      name: ingress-nginx-admission-create\n    spec:\n      containers:\n      - args:\n        - create\n        - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc\n        - --namespace=$(POD_NAMESPACE)\n        - --secret-name=ingress-nginx-admission\n        env:\n        - name: POD_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4@sha256:a9f03b34a3cbfbb26d103a14046ab2c5130a80c3d69d526ff8063d2b37b9fd3f\n        imagePullPolicy: IfNotPresent\n        name: create\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65532\n          runAsNonRoot: true\n          runAsUser: 65532\n          seccompProfile:\n            type: RuntimeDefault\n      nodeSelector:\n        kubernetes.io/os: linux\n      restartPolicy: OnFailure\n      serviceAccountName: ingress-nginx-admission\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission-patch\n  namespace: ingress-nginx\nspec:\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/component: admission-webhook\n        app.kubernetes.io/instance: ingress-nginx\n        app.kubernetes.io/name: ingress-nginx\n        app.kubernetes.io/part-of: ingress-nginx\n        app.kubernetes.io/version: 1.12.1\n      name: ingress-nginx-admission-patch\n    spec:\n      containers:\n      - args:\n        - patch\n        - --webhook-name=ingress-nginx-admission\n        - --namespace=$(POD_NAMESPACE)\n        - --patch-mutating=false\n        - --secret-name=ingress-nginx-admission\n        - --patch-failure-policy=Fail\n        env:\n        - name: POD_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4@sha256:a9f03b34a3cbfbb26d103a14046ab2c5130a80c3d69d526ff8063d2b37b9fd3f\n        imagePullPolicy: IfNotPresent\n        name: patch\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65532\n          runAsNonRoot: true\n          runAsUser: 65532\n          seccompProfile:\n            type: RuntimeDefault\n      nodeSelector:\n        kubernetes.io/os: linux\n      restartPolicy: OnFailure\n      serviceAccountName: ingress-nginx-admission\n---\napiVersion: networking.k8s.io/v1\nkind: IngressClass\nmetadata:\n  labels:\n    app.kubernetes.io/component: controller\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: nginx\nspec:\n  controller: k8s.io/ingress-nginx\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  labels:\n    app.kubernetes.io/component: admission-webhook\n    app.kubernetes.io/instance: ingress-nginx\n    app.kubernetes.io/name: ingress-nginx\n    app.kubernetes.io/part-of: ingress-nginx\n    app.kubernetes.io/version: 1.12.1\n  name: ingress-nginx-admission\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: ingress-nginx-controller-admission\n      namespace: ingress-nginx\n      path: /networking/v1/ingresses\n      port: 443\n  failurePolicy: Fail\n  matchPolicy: Equivalent\n  name: validate.nginx.ingress.kubernetes.io\n  rules:\n  - apiGroups:\n    - networking.k8s.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - ingresses\n  sideEffects: None\n"
  },
  {
    "path": "site/static/examples/ingress/usage.yaml",
    "content": "kind: Pod\napiVersion: v1\nmetadata:\n  name: foo-app\n  labels:\n    app: foo\nspec:\n  containers:\n  - command:\n    - /agnhost\n    - serve-hostname\n    - --http=true\n    - --port=8080\n    image: registry.k8s.io/e2e-test-images/agnhost:2.39\n    name: foo-app\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: foo-service\nspec:\n  selector:\n    app: foo\n  ports:\n  # Default port used by the image\n  - port: 8080\n---\nkind: Pod\napiVersion: v1\nmetadata:\n  name: bar-app\n  labels:\n    app: bar\nspec:\n  containers:\n  - command:\n    - /agnhost\n    - serve-hostname\n    - --http=true\n    - --port=8080\n    image: registry.k8s.io/e2e-test-images/agnhost:2.39\n    name: bar-app\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: bar-service\nspec:\n  selector:\n    app: bar\n  ports:\n  # Default port used by the image\n  - port: 8080\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: example-ingress\nspec:\n  rules:\n  - http:\n      paths:\n      - pathType: Prefix\n        path: /foo\n        backend:\n          service:\n            name: foo-service\n            port:\n              number: 8080\n      - pathType: Prefix\n        path: /bar\n        backend:\n          service:\n            name: bar-service\n            port:\n              number: 8080\n---\n"
  },
  {
    "path": "site/static/examples/kind-gcr.sh",
    "content": "#!/bin/sh\nset -o errexit\n\n# desired cluster name; default is \"kind\"\nKIND_CLUSTER_NAME=\"${KIND_CLUSTER_NAME:-kind}\"\n\n# create a temp file for the docker config\necho \"Creating temporary docker client config directory ...\"\nDOCKER_CONFIG=$(mktemp -d)\nexport DOCKER_CONFIG\ntrap 'echo \"Removing ${DOCKER_CONFIG}/*\" && rm -rf ${DOCKER_CONFIG:?}' EXIT\n\necho \"Creating a temporary config.json\"\n# This is to force the omission of credsStore, which is automatically\n# created on supported system. With credsStore missing, \"docker login\"\n# will store the password in the config.json file.\n# https://docs.docker.com/engine/reference/commandline/login/#credentials-store\ncat <<EOF >\"${DOCKER_CONFIG}/config.json\"\n{\n \"auths\": { \"gcr.io\": {} }\n}\nEOF\n# login to gcr in DOCKER_CONFIG using an access token\n# https://cloud.google.com/container-registry/docs/advanced-authentication#access_token\necho \"Logging in to GCR in temporary docker client config directory ...\"\ngcloud auth print-access-token | \\\n  docker login -u oauth2accesstoken --password-stdin https://gcr.io\n\n# setup credentials on each node\necho \"Moving credentials to kind cluster name='${KIND_CLUSTER_NAME}' nodes ...\"\nfor node in $(kind get nodes --name \"${KIND_CLUSTER_NAME}\"); do\n  # the -oname format is kind/name (so node/name) we just want name\n  node_name=${node#node/}\n  # copy the config to where kubelet will look\n  docker cp \"${DOCKER_CONFIG}/config.json\" \"${node_name}:/var/lib/kubelet/config.json\"\n  # restart kubelet to pick up the config\n  docker exec \"${node_name}\" systemctl restart kubelet.service\ndone\n\necho \"Done!\"\n"
  },
  {
    "path": "site/static/examples/kind-with-registry.sh",
    "content": "#!/bin/sh\nset -o errexit\n\n# 1. Create registry container unless it already exists\nreg_name='kind-registry'\nreg_port='5001'\nif [ \"$(docker inspect -f '{{.State.Running}}' \"${reg_name}\" 2>/dev/null || true)\" != 'true' ]; then\n  docker run \\\n    -d --restart=always -p \"127.0.0.1:${reg_port}:5000\" --network bridge --name \"${reg_name}\" \\\n    registry:2\nfi\n\n# 2. Create kind cluster with containerd registry config dir enabled\n#\n# NOTE: the containerd config patch is not necessary with images from kind v0.27.0+\n# It may enable some older images to work similarly.\n# If you're only supporting newer relases, you can just use `kind create cluster` here.\n#\n# See:\n# https://github.com/kubernetes-sigs/kind/issues/2875\n# https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration\n# See: https://github.com/containerd/containerd/blob/main/docs/hosts.md\ncat <<EOF | kind create cluster --config=-\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\ncontainerdConfigPatches:\n- |-\n  [plugins.\"io.containerd.grpc.v1.cri\".registry]\n    config_path = \"/etc/containerd/certs.d\"\nEOF\n\n# 3. Add the registry config to the nodes\n#\n# This is necessary because localhost resolves to loopback addresses that are\n# network-namespace local.\n# In other words: localhost in the container is not localhost on the host.\n#\n# We want a consistent name that works from both ends, so we tell containerd to\n# alias localhost:${reg_port} to the registry container when pulling images\nREGISTRY_DIR=\"/etc/containerd/certs.d/localhost:${reg_port}\"\nfor node in $(kind get nodes); do\n  docker exec \"${node}\" mkdir -p \"${REGISTRY_DIR}\"\n  cat <<EOF | docker exec -i \"${node}\" cp /dev/stdin \"${REGISTRY_DIR}/hosts.toml\"\n[host.\"http://${reg_name}:5000\"]\nEOF\ndone\n\n# 4. Connect the registry to the cluster network if not already connected\n# This allows kind to bootstrap the network but ensures they're on the same network\nif [ \"$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' \"${reg_name}\")\" = 'null' ]; then\n  docker network connect \"kind\" \"${reg_name}\"\nfi\n\n# 5. Document the local registry\n# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry\ncat <<EOF | kubectl apply -f -\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: local-registry-hosting\n  namespace: kube-public\ndata:\n  localRegistryHosting.v1: |\n    host: \"localhost:${reg_port}\"\n    help: \"https://kind.sigs.k8s.io/docs/user/local-registry/\"\nEOF\n"
  },
  {
    "path": "site/static/examples/loadbalancer/usage.yaml",
    "content": "kind: Pod\napiVersion: v1\nmetadata:\n  name: foo-app\n  labels:\n    app: http-echo\nspec:\n  containers:\n  - command:\n    - /agnhost\n    - serve-hostname\n    - --http=true\n    - --port=8080\n    image: registry.k8s.io/e2e-test-images/agnhost:2.39\n    name: foo-app\n---\nkind: Pod\napiVersion: v1\nmetadata:\n  name: bar-app\n  labels:\n    app: http-echo\nspec:\n  containers:\n  - command:\n    - /agnhost\n    - serve-hostname\n    - --http=true\n    - --port=8080\n    image: registry.k8s.io/e2e-test-images/agnhost:2.39\n    name: bar-app\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: foo-service\nspec:\n  type: LoadBalancer\n  selector:\n    app: http-echo\n  ports:\n  - port: 5678\n    targetPort: 8080\n"
  },
  {
    "path": "site/static/logo/LICENSE",
    "content": "# The kind logo files are licensed under a choice of either Apache-2.0 or CC-BY-4.0 (Creative Commons Attribution 4.0 International).\n"
  },
  {
    "path": "site/static/site.webmanifest",
    "content": "{\n    \"name\": \"\",\n    \"short_name\": \"\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"/android-chrome-512x512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ffffff\",\n    \"background_color\": \"#ffffff\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "site/tools.go",
    "content": "//go:build tools\n// +build tools\n\n/*\nPackage site is used to track binary dependencies with go modules\nhttps://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module\nNamely: hugo\n*/\npackage site\n\nimport (\n\t_ \"github.com/gohugoio/hugo\"\n)\n"
  }
]